Merge changes I958029a4,I45653246,I49161c8f,I1c862e86,I5e8c0bbe, ...

* changes:
  Add action to query <Set Audio Volume Level> support
  Add feature action for sending <Give Features>
  Update CEC network with feature support on receiving <Report Features>
  Create class for building and parsing <Report Features>
  Validate all messages on construction
  Keep DeviceFeatures updated in local devices.
  Add CEC feature tracking to HdmiDeviceInfo
  Refactor HdmiDeviceInfo construction to use a builder
diff --git a/Android.bp b/Android.bp
index 070bb73..e96c731 100644
--- a/Android.bp
+++ b/Android.bp
@@ -434,7 +434,6 @@
     name: "framework-ike-shared-srcs",
     visibility: ["//packages/modules/IPsec"],
     srcs: [
-        "core/java/android/net/annotations/PolicyDirection.java",
         "core/java/com/android/internal/util/HexDump.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
         "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 3fb1fad..12a8654 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1556,7 +1556,7 @@
                     Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias);
                 }
                 for (int c = 0; c < mControllers.size(); ++c) {
-                    mControllers.get(c).onUidBiasChangedLocked(uid, newBias);
+                    mControllers.get(c).onUidBiasChangedLocked(uid, prevBias, newBias);
                 }
             }
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index f733849..63781ae 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -18,6 +18,14 @@
 
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.NonNull;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
@@ -27,6 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
@@ -42,10 +51,26 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
+    @GuardedBy("mLock")
     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
+    /**
+     * List of jobs that started while the UID was in the TOP state.
+     */
+    @GuardedBy("mLock")
+    private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
+
+    private final PowerTracker mPowerTracker;
+
+    /**
+     * Helper set to avoid too much GC churn from frequent calls to
+     * {@link #maybeReportNewChargingStateLocked()}.
+     */
+    private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>();
 
     public BatteryController(JobSchedulerService service) {
         super(service);
+        mPowerTracker = new PowerTracker();
+        mPowerTracker.startTracking();
     }
 
     @Override
@@ -54,8 +79,15 @@
             final long nowElapsed = sElapsedRealtimeClock.millis();
             mTrackedTasks.add(taskStatus);
             taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
-            taskStatus.setChargingConstraintSatisfied(nowElapsed,
-                    mService.isBatteryCharging() && mService.isBatteryNotLow());
+            if (taskStatus.hasChargingConstraint()) {
+                if (hasTopExemptionLocked(taskStatus)) {
+                    taskStatus.setChargingConstraintSatisfied(nowElapsed,
+                            mPowerTracker.isPowerConnected());
+                } else {
+                    taskStatus.setChargingConstraintSatisfied(nowElapsed,
+                            mService.isBatteryCharging() && mService.isBatteryNotLow());
+                }
+            }
             taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow());
         }
     }
@@ -66,9 +98,32 @@
     }
 
     @Override
+    @GuardedBy("mLock")
+    public void prepareForExecutionLocked(JobStatus jobStatus) {
+        if (DEBUG) {
+            Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
+        }
+
+        final int uid = jobStatus.getSourceUid();
+        if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) {
+            if (DEBUG) {
+                Slog.d(TAG, jobStatus.toShortString() + " is top started job");
+            }
+            mTopStartedJobs.add(jobStatus);
+        }
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+        mTopStartedJobs.remove(jobStatus);
+    }
+
+    @Override
     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
             mTrackedTasks.remove(taskStatus);
+            mTopStartedJobs.remove(taskStatus);
         }
     }
 
@@ -90,33 +145,124 @@
         });
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
+        if (prevBias == JobInfo.BIAS_TOP_APP || newBias == JobInfo.BIAS_TOP_APP) {
+            maybeReportNewChargingStateLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean hasTopExemptionLocked(@NonNull JobStatus taskStatus) {
+        return mService.getUidBias(taskStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
+                || mTopStartedJobs.contains(taskStatus);
+    }
+
     @GuardedBy("mLock")
     private void maybeReportNewChargingStateLocked() {
+        final boolean powerConnected = mPowerTracker.isPowerConnected();
         final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
         final boolean batteryNotLow = mService.isBatteryNotLow();
         if (DEBUG) {
-            Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
+            Slog.d(TAG, "maybeReportNewChargingStateLocked: "
+                    + powerConnected + "/" + stablePower + "/" + batteryNotLow);
         }
         final long nowElapsed = sElapsedRealtimeClock.millis();
-        boolean reportChange = false;
         for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
             final JobStatus ts = mTrackedTasks.valueAt(i);
-            reportChange |= ts.setChargingConstraintSatisfied(nowElapsed, stablePower);
-            reportChange |= ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow);
+            if (ts.hasChargingConstraint()) {
+                if (hasTopExemptionLocked(ts)
+                        && ts.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
+                    // If the job started while the app was on top or the app is currently on top,
+                    // let the job run as long as there's power connected, even if the device isn't
+                    // officially charging.
+                    // For user requested/initiated jobs, users may be confused when the task stops
+                    // running even though the device is plugged in.
+                    // Low priority jobs don't need to be exempted.
+                    if (ts.setChargingConstraintSatisfied(nowElapsed, powerConnected)) {
+                        mChangedJobs.add(ts);
+                    }
+                } else if (ts.setChargingConstraintSatisfied(nowElapsed, stablePower)) {
+                    mChangedJobs.add(ts);
+                }
+            }
+            if (ts.hasBatteryNotLowConstraint()
+                    && ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow)) {
+                mChangedJobs.add(ts);
+            }
         }
         if (stablePower || batteryNotLow) {
             // If one of our conditions has been satisfied, always schedule any newly ready jobs.
             mStateChangedListener.onRunJobNow(null);
-        } else if (reportChange) {
+        } else if (mChangedJobs.size() > 0) {
             // Otherwise, just let the job scheduler know the state has changed and take care of it
             // as it thinks is best.
-            mStateChangedListener.onControllerStateChanged(mTrackedTasks);
+            mStateChangedListener.onControllerStateChanged(mChangedJobs);
+        }
+        mChangedJobs.clear();
+    }
+
+    private final class PowerTracker extends BroadcastReceiver {
+        /**
+         * Track whether there is power connected. It doesn't mean the device is charging.
+         * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
+         * charging.
+         */
+        private boolean mPowerConnected;
+
+        PowerTracker() {
+        }
+
+        void startTracking() {
+            IntentFilter filter = new IntentFilter();
+
+            filter.addAction(Intent.ACTION_POWER_CONNECTED);
+            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+            mContext.registerReceiver(this, filter);
+
+            // Initialize tracker state.
+            BatteryManagerInternal batteryManagerInternal =
+                    LocalServices.getService(BatteryManagerInternal.class);
+            mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+        }
+
+        boolean isPowerConnected() {
+            return mPowerConnected;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                final String action = intent.getAction();
+
+                if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (mPowerConnected) {
+                        return;
+                    }
+                    mPowerConnected = true;
+                } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (!mPowerConnected) {
+                        return;
+                    }
+                    mPowerConnected = false;
+                }
+
+                maybeReportNewChargingStateLocked();
+            }
         }
     }
 
     @Override
     public void dumpControllerStateLocked(IndentingPrintWriter pw,
             Predicate<JobStatus> predicate) {
+        pw.println("Power connected: " + mPowerTracker.isPowerConnected());
         pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
         pw.println("Not low: " + mService.isBatteryNotLow());
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 9fb7ab5..c678755 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -517,7 +517,7 @@
 
     @GuardedBy("mLock")
     @Override
-    public void onUidBiasChangedLocked(int uid, int newBias) {
+    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
         UidStats uidStats = mUidStats.get(uid);
         if (uidStats != null && uidStats.baseBias != newBias) {
             uidStats.baseBias = newBias;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 9749c80..428f2cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -40,7 +40,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
-import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,9 +80,6 @@
      */
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
-    /** Cached list of UIDs in the TOP state. */
-    @GuardedBy("mLock")
-    private final SparseBooleanArray mTopUids = new SparseBooleanArray();
     private final ThresholdAlarmListener mThresholdAlarmListener;
 
     /**
@@ -186,15 +182,9 @@
 
     @GuardedBy("mLock")
     @Override
-    public void onUidBiasChangedLocked(int uid, int newBias) {
+    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
         final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP;
-        final boolean wasTop = mTopUids.get(uid);
-        if (isNowTop) {
-            mTopUids.put(uid, true);
-        } else {
-            // Delete entries of non-top apps so the set doesn't get too large.
-            mTopUids.delete(uid);
-        }
+        final boolean wasTop = prevBias == JobInfo.BIAS_TOP_APP;
         if (isNowTop != wasTop) {
             mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget();
         }
@@ -314,7 +304,8 @@
         //   3. The app is not open but has an active widget (we can't tell if a widget displays
         //      status/data, so this assumes the prefetch job is to update the data displayed on
         //      the widget).
-        final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid());
+        final boolean appIsOpen =
+                mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP;
         final boolean satisfied;
         if (!appIsOpen) {
             final int userId = jobStatus.getSourceUserId();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 509fb69..2a2d602 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -144,7 +144,7 @@
      * important the UID is.
      */
     @GuardedBy("mLock")
-    public void onUidBiasChangedLocked(int uid, int newBias) {
+    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
     }
 
     protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
diff --git a/api/api.go b/api/api.go
index 3b0e300..e4c1b96 100644
--- a/api/api.go
+++ b/api/api.go
@@ -111,7 +111,7 @@
 	props.Tools = []string{"metalava"}
 	props.Out = []string{filename}
 	props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --api $(out)")
-	props.Srcs = createSrcs(txt.BaseTxt, txt.Modules, txt.ModuleTag)
+	props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...)
 	props.Dists = []android.Dist{
 		{
 			Targets: []string{"droidcore"},
@@ -134,7 +134,7 @@
 	props.Tools = []string{"merge_zips"}
 	props.Out = []string{"current.srcjar"}
 	props.Cmd = proptools.StringPtr("$(location merge_zips) $(out) $(in)")
-	props.Srcs = createSrcs(":api-stubs-docs-non-updatable", modules, "{.public.stubs.source}")
+	props.Srcs = append([]string{":api-stubs-docs-non-updatable"}, createSrcs(modules, "{.public.stubs.source}")...)
 	props.Visibility = []string{"//visibility:private"} // Used by make module in //development, mind
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
@@ -150,7 +150,7 @@
 	props.Out = []string{"annotations.zip"}
 	props.Cmd = proptools.StringPtr("$(location merge_annotation_zips) $(genDir)/out $(in) && " +
 		"$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out")
-	props.Srcs = createSrcs(":android-non-updatable-doc-stubs{.annotations.zip}", modules, "{.public.annotations.zip}")
+	props.Srcs = append([]string{":android-non-updatable-doc-stubs{.annotations.zip}"}, createSrcs(modules, "{.public.annotations.zip}")...)
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
 
@@ -172,7 +172,7 @@
 	// Note: order matters: first parameter is the full api-versions.xml
 	// after that the stubs files in any order
 	// stubs files are all modules that export API surfaces EXCEPT ART
-	props.Srcs = createSrcs(":framework-doc-stubs{.api_versions.xml}", modules, ".stubs{.jar}")
+	props.Srcs = append([]string{":framework-doc-stubs{.api_versions.xml}"}, createSrcs(modules, ".stubs{.jar}")...)
 	props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
@@ -182,46 +182,12 @@
 	modules = removeAll(modules, []string{art, conscrypt, i18n})
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api")
-	props.Static_libs = appendStr(modules, ".stubs.module_lib")
+	props.Static_libs = transformArray(modules, "", ".stubs.module_lib")
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func appendStr(modules []string, s string) []string {
-	a := make([]string, 0, len(modules))
-	for _, module := range modules {
-		a = append(a, module+s)
-	}
-	return a
-}
-
-func createSrcs(base string, modules []string, tag string) []string {
-	a := make([]string, 0, len(modules)+1)
-	a = append(a, base)
-	for _, module := range modules {
-		a = append(a, ":"+module+tag)
-	}
-	return a
-}
-
-func removeAll(s []string, vs []string) []string {
-	for _, v := range vs {
-		s = remove(s, v)
-	}
-	return s
-}
-
-func remove(s []string, v string) []string {
-	s2 := make([]string, 0, len(s))
-	for _, sv := range s {
-		if sv != v {
-			s2 = append(s2, sv)
-		}
-	}
-	return s2
-}
-
 func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
 	var textFiles []MergedTxtDefinition
 	// Two module libraries currently do not support @SystemApi so only have the public scope.
@@ -287,3 +253,36 @@
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
 	return module
 }
+
+// Various utility methods below.
+
+// Creates an array of ":<m><tag>" for each m in <modules>.
+func createSrcs(modules []string, tag string) []string {
+	return transformArray(modules, ":", tag)
+}
+
+// Creates an array of "<prefix><m><suffix>", for each m in <modules>.
+func transformArray(modules []string, prefix, suffix string) []string {
+	a := make([]string, 0, len(modules))
+	for _, module := range modules {
+		a = append(a, prefix+module+suffix)
+	}
+	return a
+}
+
+func removeAll(s []string, vs []string) []string {
+	for _, v := range vs {
+		s = remove(s, v)
+	}
+	return s
+}
+
+func remove(s []string, v string) []string {
+	s2 := make([]string, 0, len(s))
+	for _, sv := range s {
+		if sv != v {
+			s2 = append(s2, sv)
+		}
+	}
+	return s2
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 4f15fa9..385899a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17,6 +17,7 @@
     field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
     field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
     field public static final String ACCESS_NOTIFICATION_POLICY = "android.permission.ACCESS_NOTIFICATION_POLICY";
+    field public static final String ACCESS_SUPPLEMENTAL_APIS = "android.permission.ACCESS_SUPPLEMENTAL_APIS";
     field public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE";
     field public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
     field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
@@ -18330,10 +18331,12 @@
     ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature);
     ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher);
     ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac);
-    ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
+    ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
+    ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
     method public javax.crypto.Cipher getCipher();
-    method @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
+    method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method public javax.crypto.Mac getMac();
+    method @Nullable public android.security.identity.PresentationSession getPresentationSession();
     method public java.security.Signature getSignature();
   }
 
@@ -20149,6 +20152,24 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAntennaInfo.SphericalCorrections> CREATOR;
   }
 
+  public final class GnssAutomaticGainControl implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0) public long getCarrierFrequencyHz();
+    method public int getConstellationType();
+    method @FloatRange(from=0xffffd8f0, to=10000) public double getLevelDb();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAutomaticGainControl> CREATOR;
+  }
+
+  public static final class GnssAutomaticGainControl.Builder {
+    ctor public GnssAutomaticGainControl.Builder();
+    ctor public GnssAutomaticGainControl.Builder(@NonNull android.location.GnssAutomaticGainControl);
+    method @NonNull public android.location.GnssAutomaticGainControl build();
+    method @NonNull public android.location.GnssAutomaticGainControl.Builder setCarrierFrequencyHz(@IntRange(from=0) long);
+    method @NonNull public android.location.GnssAutomaticGainControl.Builder setConstellationType(int);
+    method @NonNull public android.location.GnssAutomaticGainControl.Builder setLevelDb(@FloatRange(from=0xffffd8f0, to=10000) double);
+  }
+
   public final class GnssCapabilities implements android.os.Parcelable {
     method public int describeContents();
     method public boolean hasAntennaInfo();
@@ -20205,7 +20226,7 @@
     method public double getAccumulatedDeltaRangeMeters();
     method public int getAccumulatedDeltaRangeState();
     method public double getAccumulatedDeltaRangeUncertaintyMeters();
-    method public double getAutomaticGainControlLevelDb();
+    method @Deprecated public double getAutomaticGainControlLevelDb();
     method @FloatRange(from=0, to=63) public double getBasebandCn0DbHz();
     method @Deprecated public long getCarrierCycles();
     method public float getCarrierFrequencyHz();
@@ -20227,7 +20248,7 @@
     method public int getState();
     method public int getSvid();
     method public double getTimeOffsetNanos();
-    method public boolean hasAutomaticGainControlLevelDb();
+    method @Deprecated public boolean hasAutomaticGainControlLevelDb();
     method public boolean hasBasebandCn0DbHz();
     method @Deprecated public boolean hasCarrierCycles();
     method public boolean hasCarrierFrequencyHz();
@@ -20289,11 +20310,21 @@
   public final class GnssMeasurementsEvent implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.location.GnssClock getClock();
+    method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
     method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
   }
 
+  public static final class GnssMeasurementsEvent.Builder {
+    ctor public GnssMeasurementsEvent.Builder();
+    ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent);
+    method @NonNull public android.location.GnssMeasurementsEvent build();
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
+  }
+
   public abstract static class GnssMeasurementsEvent.Callback {
     ctor public GnssMeasurementsEvent.Callback();
     method public void onGnssMeasurementsReceived(android.location.GnssMeasurementsEvent);
@@ -20965,6 +20996,7 @@
     method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
     method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
     method public int getAllowedCapturePolicy();
+    method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAudioDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method public int getAudioHwSyncForSession(int);
     method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
     method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
@@ -27724,6 +27756,8 @@
     ctor public VcnCellUnderlyingNetworkTemplate.Builder();
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
@@ -27784,6 +27818,10 @@
 
   public abstract class VcnUnderlyingNetworkTemplate {
     method public int getMetered();
+    method public int getMinEntryDownstreamBandwidthKbps();
+    method public int getMinEntryUpstreamBandwidthKbps();
+    method public int getMinExitDownstreamBandwidthKbps();
+    method public int getMinExitUpstreamBandwidthKbps();
     field public static final int MATCH_ANY = 0; // 0x0
     field public static final int MATCH_FORBIDDEN = 2; // 0x2
     field public static final int MATCH_REQUIRED = 1; // 0x1
@@ -27797,6 +27835,8 @@
     ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
     method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
     method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
     method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
   }
 
@@ -38029,6 +38069,51 @@
     ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
   }
 
+  public class CredentialDataRequest {
+    method @NonNull public java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> getDeviceSignedEntriesToRequest();
+    method @NonNull public java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> getIssuerSignedEntriesToRequest();
+    method @Nullable public byte[] getReaderSignature();
+    method @Nullable public byte[] getRequestMessage();
+    method public boolean isAllowUsingExhaustedKeys();
+    method public boolean isAllowUsingExpiredKeys();
+    method public boolean isIncrementUseCount();
+  }
+
+  public static final class CredentialDataRequest.Builder {
+    ctor public CredentialDataRequest.Builder();
+    method @NonNull public android.security.identity.CredentialDataRequest build();
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setAllowUsingExhaustedKeys(boolean);
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setAllowUsingExpiredKeys(boolean);
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setDeviceSignedEntriesToRequest(@NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>);
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setIncrementUseCount(boolean);
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setIssuerSignedEntriesToRequest(@NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>);
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setReaderSignature(@NonNull byte[]);
+    method @NonNull public android.security.identity.CredentialDataRequest.Builder setRequestMessage(@NonNull byte[]);
+  }
+
+  public abstract class CredentialDataResult {
+    method @Nullable public abstract byte[] getDeviceMac();
+    method @NonNull public abstract byte[] getDeviceNameSpaces();
+    method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries();
+    method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries();
+    method @NonNull public abstract byte[] getStaticAuthenticationData();
+  }
+
+  public static interface CredentialDataResult.Entries {
+    method @Nullable public byte[] getEntry(@NonNull String, @NonNull String);
+    method @NonNull public java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
+    method @NonNull public java.util.Collection<java.lang.String> getNamespaces();
+    method @NonNull public java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
+    method public int getStatus(@NonNull String, @NonNull String);
+    field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
+    field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
+    field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
+    field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
+    field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
+  }
+
   public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException {
     ctor public DocTypeNotSupportedException(@NonNull String);
     ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable);
@@ -38040,19 +38125,19 @@
   }
 
   public abstract class IdentityCredential {
-    method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
-    method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
+    method @Deprecated @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
+    method @Deprecated @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
     method @NonNull public byte[] delete(@NonNull byte[]);
-    method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
+    method @Deprecated @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
     method @NonNull public abstract int[] getAuthenticationDataUsageCount();
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
-    method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
+    method @Deprecated @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
     method @NonNull public byte[] proveOwnership(@NonNull byte[]);
-    method public abstract void setAllowUsingExhaustedKeys(boolean);
-    method public void setAllowUsingExpiredKeys(boolean);
+    method @Deprecated public abstract void setAllowUsingExhaustedKeys(boolean);
+    method @Deprecated public void setAllowUsingExpiredKeys(boolean);
     method public abstract void setAvailableAuthenticationKeys(int, int);
-    method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
+    method @Deprecated public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
     method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
     method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
     method @NonNull public byte[] update(@NonNull android.security.identity.PersonalizationData);
@@ -38065,6 +38150,7 @@
 
   public abstract class IdentityCredentialStore {
     method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException;
+    method @NonNull public android.security.identity.PresentationSession createPresentationSession(int) throws android.security.identity.CipherSuiteNotSupportedException;
     method @Deprecated @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
     method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException;
     method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context);
@@ -38103,22 +38189,29 @@
     method @NonNull public android.security.identity.PersonalizationData.Builder putEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
   }
 
-  public abstract class ResultData {
-    method @NonNull public abstract byte[] getAuthenticatedData();
-    method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
-    method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
-    method @Nullable public abstract byte[] getMessageAuthenticationCode();
-    method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces();
-    method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
-    method @NonNull public abstract byte[] getStaticAuthenticationData();
-    method public abstract int getStatus(@NonNull String, @NonNull String);
-    field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
-    field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
-    field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
-    field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
-    field public static final int STATUS_OK = 0; // 0x0
-    field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
-    field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
+  public abstract class PresentationSession {
+    method @Nullable public abstract android.security.identity.CredentialDataResult getCredentialData(@NonNull String, @NonNull android.security.identity.CredentialDataRequest) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException;
+    method @NonNull public abstract java.security.KeyPair getEphemeralKeyPair();
+    method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
+    method public abstract void setSessionTranscript(@NonNull byte[]);
+  }
+
+  @Deprecated public abstract class ResultData {
+    method @Deprecated @NonNull public abstract byte[] getAuthenticatedData();
+    method @Deprecated @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
+    method @Deprecated @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
+    method @Deprecated @Nullable public abstract byte[] getMessageAuthenticationCode();
+    method @Deprecated @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces();
+    method @Deprecated @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
+    method @Deprecated @NonNull public abstract byte[] getStaticAuthenticationData();
+    method @Deprecated public abstract int getStatus(@NonNull String, @NonNull String);
+    field @Deprecated public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
+    field @Deprecated public static final int STATUS_NOT_REQUESTED = 2; // 0x2
+    field @Deprecated public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
+    field @Deprecated public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
+    field @Deprecated public static final int STATUS_OK = 0; // 0x0
+    field @Deprecated public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
+    field @Deprecated public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
   }
 
   public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException {
@@ -47166,6 +47259,15 @@
     field public float ydpi;
   }
 
+  public interface Dumpable {
+    method public void dump(@NonNull java.io.PrintWriter, @Nullable String[]);
+    method @NonNull public default String getDumpableName();
+  }
+
+  public interface DumpableContainer {
+    method public boolean addDumpable(@NonNull android.util.Dumpable);
+  }
+
   public class EventLog {
     method public static int getTagCode(String);
     method public static String getTagName(int);
@@ -49165,6 +49267,7 @@
     field public static final int EDGE_LEFT = 4; // 0x4
     field public static final int EDGE_RIGHT = 8; // 0x8
     field public static final int EDGE_TOP = 1; // 0x1
+    field public static final int FLAG_CANCELED = 32; // 0x20
     field public static final int FLAG_WINDOW_IS_OBSCURED = 1; // 0x1
     field public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 2; // 0x2
     field public static final int INVALID_POINTER_ID = -1; // 0xffffffff
@@ -52267,6 +52370,7 @@
     method public final float getFontScale();
     method @Nullable public final java.util.Locale getLocale();
     method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
+    method public boolean isCallCaptioningEnabled();
     method public final boolean isEnabled();
     method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener);
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4d84537..c4aff2d0 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -9,6 +9,10 @@
 
 package android.app {
 
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+    method public final boolean addDumpable(@NonNull android.util.Dumpable);
+  }
+
   public class ActivityManager {
     method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
     method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
@@ -160,6 +164,7 @@
 
   public final class BtProfileConnectionInfo implements android.os.Parcelable {
     method @NonNull public static android.media.BtProfileConnectionInfo a2dpInfo(boolean, int);
+    method @NonNull public static android.media.BtProfileConnectionInfo a2dpSinkInfo(int);
     method public int describeContents();
     method public boolean getIsLeOutput();
     method public int getProfile();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4b74e98..832478c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -367,7 +367,6 @@
     field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
     field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
     field public static final int config_showDefaultHome = 17891331; // 0x1110003
-    field public static final int config_systemCaptionsServiceCallsEnabled;
   }
 
   public static final class R.color {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d3c99b8..f1b4624 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1319,7 +1319,7 @@
     method public void setAccumulatedDeltaRangeMeters(double);
     method public void setAccumulatedDeltaRangeState(int);
     method public void setAccumulatedDeltaRangeUncertaintyMeters(double);
-    method public void setAutomaticGainControlLevelInDb(double);
+    method @Deprecated public void setAutomaticGainControlLevelInDb(double);
     method public void setBasebandCn0DbHz(double);
     method @Deprecated public void setCarrierCycles(long);
     method public void setCarrierFrequencyHz(float);
@@ -1346,10 +1346,6 @@
     field public static final int ADR_STATE_ALL = 31; // 0x1f
   }
 
-  public final class GnssMeasurementsEvent implements android.os.Parcelable {
-    ctor public GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]);
-  }
-
   public final class GnssNavigationMessage implements android.os.Parcelable {
     ctor public GnssNavigationMessage();
     method public void reset();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cf2b7ac..283345f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -90,6 +90,7 @@
 import android.transition.TransitionManager;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
+import android.util.Dumpable;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
@@ -145,6 +146,7 @@
 import com.android.internal.app.ToolbarActionBar;
 import com.android.internal.app.WindowDecorActionBar;
 import com.android.internal.policy.PhoneWindow;
+import com.android.internal.util.dump.DumpableContainerImpl;
 
 import dalvik.system.VMRuntime;
 
@@ -954,6 +956,9 @@
 
     private SplashScreen mSplashScreen;
 
+    @Nullable
+    private DumpableContainerImpl mDumpableContainer;
+
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
         /**
@@ -7081,8 +7086,23 @@
         dumpInner(prefix, fd, writer, args);
     }
 
+    /**
+     * See {@link android.util.DumpableContainer#addDumpable(Dumpable)}.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public final boolean addDumpable(@NonNull Dumpable dumpable) {
+        if (mDumpableContainer == null) {
+            mDumpableContainer = new DumpableContainerImpl();
+        }
+        return mDumpableContainer.addDumpable(dumpable);
+    }
+
     void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
             @NonNull PrintWriter writer, @Nullable String[] args) {
+        String innerPrefix = prefix + "  ";
+
         if (args != null && args.length > 0) {
             // Handle special cases
             switch (args[0]) {
@@ -7095,12 +7115,33 @@
                 case "--translation":
                     dumpUiTranslation(prefix, writer);
                     return;
+                case "--list-dumpables":
+                    if (mDumpableContainer == null) {
+                        writer.print(prefix); writer.println("No dumpables");
+                        return;
+                    }
+                    mDumpableContainer.listDumpables(prefix, writer);
+                    return;
+                case "--dump-dumpable":
+                    if (args.length == 1) {
+                        writer.println("--dump-dumpable requires the dumpable name");
+                        return;
+                    }
+                    if (mDumpableContainer == null) {
+                        writer.println("no dumpables");
+                        return;
+                    }
+                    // Strips --dump-dumpable NAME
+                    String[] prunedArgs = new String[args.length - 2];
+                    System.arraycopy(args, 2, prunedArgs, 0, prunedArgs.length);
+                    mDumpableContainer.dumpOneDumpable(prefix, writer, args[1], prunedArgs);
+                    return;
             }
         }
+
         writer.print(prefix); writer.print("Local Activity ");
                 writer.print(Integer.toHexString(System.identityHashCode(this)));
                 writer.println(" State:");
-        String innerPrefix = prefix + "  ";
         writer.print(innerPrefix); writer.print("mResumed=");
                 writer.print(mResumed); writer.print(" mStopped=");
                 writer.print(mStopped); writer.print(" mFinished=");
@@ -7138,6 +7179,10 @@
         dumpUiTranslation(prefix, writer);
 
         ResourcesManager.getInstance().dump(prefix, writer);
+
+        if (mDumpableContainer != null) {
+            mDumpableContainer.dumpAllDumpables(prefix, writer, args);
+        }
     }
 
     void dumpContentCaptureManager(String prefix, PrintWriter writer) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fe512ff..fa48730 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -312,6 +312,14 @@
     @ContextType
     private int mContextType;
 
+    /**
+     * {@code true} to indicate that the {@link Context} owns the {@link #getWindowContextToken()}
+     * and is responsible for detaching the token when the Context is released.
+     *
+     * @see #finalize()
+     */
+    private boolean mOwnsToken = false;
+
     @GuardedBy("mSync")
     private File mDatabasesDir;
     @GuardedBy("mSync")
@@ -3015,7 +3023,7 @@
         // WindowContainer. We should detach from WindowContainer when the Context is finalized
         // if this Context is not a WindowContext. WindowContext finalization is handled in
         // WindowContext class.
-        if (mToken instanceof WindowTokenClient && mContextType != CONTEXT_TYPE_WINDOW_CONTEXT) {
+        if (mToken instanceof WindowTokenClient && mOwnsToken) {
             ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
         }
         super.finalize();
@@ -3046,6 +3054,7 @@
         token.attachContext(context);
         token.attachToDisplayContent(displayId);
         context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
+        context.mOwnsToken = true;
 
         return context;
     }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index fdcf99d..a82ecce 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -63,6 +63,7 @@
     boolean isInInvalidMsgState(String pkg, int uid);
     boolean hasUserDemotedInvalidMsgApp(String pkg, int uid);
     void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted);
+    boolean hasSentValidBubble(String pkg, int uid);
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     /**
      * Updates the notification's enabled state. Additionally locks importance for all of the
diff --git a/core/java/android/bluetooth/OWNERS b/core/java/android/bluetooth/OWNERS
index fbee577..8e9d7b7 100644
--- a/core/java/android/bluetooth/OWNERS
+++ b/core/java/android/bluetooth/OWNERS
@@ -1,6 +1,4 @@
 # Bug component: 27441
 
-rahulsabnis@google.com
 sattiraju@google.com
-siyuanh@google.com
-zachoverflow@google.com
+baligh@google.com
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index f0566b8..373a8d9 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -54,13 +54,13 @@
     private final @Nullable String mDeviceProfile;
 
     private final boolean mSelfManaged;
-    private boolean mNotifyOnDeviceNearby;
+    private final boolean mNotifyOnDeviceNearby;
     private final long mTimeApprovedMs;
     /**
      * A long value indicates the last time connected reported by selfManaged devices
      * Default value is Long.MAX_VALUE.
      */
-    private long mLastTimeConnectedMs;
+    private final long mLastTimeConnectedMs;
 
     /**
      * Creates a new Association.
@@ -160,22 +160,6 @@
         return mSelfManaged;
     }
 
-    /**
-     * Should only be used by the CompanionDeviceManagerService.
-     * @hide
-     */
-    public void setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
-        mNotifyOnDeviceNearby = notifyOnDeviceNearby;
-    }
-
-    /**
-     * Should only be used by the CompanionDeviceManagerService.
-     * @hide
-     */
-    public void setLastTimeConnected(long lastTimeConnectedMs) {
-        mLastTimeConnectedMs = lastTimeConnectedMs;
-    }
-
     /** @hide */
     public boolean isNotifyOnDeviceNearby() {
         return mNotifyOnDeviceNearby;
@@ -330,4 +314,112 @@
             return new AssociationInfo(in);
         }
     };
+
+    /**
+     * Use this method to obtain a builder that you can use to create a copy of the
+     * given {@link AssociationInfo} with modified values of {@code mLastTimeConnected}
+     * or {@code mNotifyOnDeviceNearby}.
+     * <p>
+     *     Note that you <b>must</b> call either {@link Builder#setLastTimeConnected(long)
+     *     setLastTimeConnected} or {@link Builder#setNotifyOnDeviceNearby(boolean)
+     *     setNotifyOnDeviceNearby} before you will be able to call {@link Builder#build() build}.
+     *
+     *     This is ensured statically at compile time.
+     *
+     * @hide
+     */
+    @NonNull
+    public static NonActionableBuilder builder(@NonNull AssociationInfo info) {
+        return new Builder(info);
+    }
+
+    /**
+     * @hide
+     */
+    public static final class Builder implements NonActionableBuilder {
+        @NonNull
+        private final AssociationInfo mOriginalInfo;
+        private boolean mNotifyOnDeviceNearby;
+        private long mLastTimeConnectedMs;
+
+        private Builder(@NonNull AssociationInfo info) {
+            mOriginalInfo = info;
+            mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+            mLastTimeConnectedMs = info.mLastTimeConnectedMs;
+        }
+
+        /**
+         * Should only be used by the CompanionDeviceManagerService.
+         * @hide
+         */
+        @Override
+        @NonNull
+        public Builder setLastTimeConnected(long lastTimeConnectedMs) {
+            if (lastTimeConnectedMs < 0) {
+                throw new IllegalArgumentException(
+                        "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs
+                                + " )");
+            }
+            mLastTimeConnectedMs = lastTimeConnectedMs;
+            return this;
+        }
+
+        /**
+         * Should only be used by the CompanionDeviceManagerService.
+         * @hide
+         */
+        @Override
+        @NonNull
+        public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
+            mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        @NonNull
+        public AssociationInfo build() {
+            return new AssociationInfo(
+                    mOriginalInfo.mId,
+                    mOriginalInfo.mUserId,
+                    mOriginalInfo.mPackageName,
+                    mOriginalInfo.mDeviceMacAddress,
+                    mOriginalInfo.mDisplayName,
+                    mOriginalInfo.mDeviceProfile,
+                    mOriginalInfo.mSelfManaged,
+                    mNotifyOnDeviceNearby,
+                    mOriginalInfo.mTimeApprovedMs,
+                    mLastTimeConnectedMs
+            );
+        }
+    }
+
+    /**
+     * This interface is returned from the
+     * {@link AssociationInfo#builder(android.companion.AssociationInfo) builder} entry point
+     * to indicate that this builder is not yet in a state that can produce a meaningful
+     * {@link AssociationInfo} object that is different from the one originally passed in.
+     *
+     * <p>
+     * Only by calling one of the setter methods is this builder turned into one where calling
+     * {@link Builder#build() build()} makes sense.
+     *
+     * @hide
+     */
+    public interface NonActionableBuilder {
+        /**
+         * Should only be used by the CompanionDeviceManagerService.
+         * @hide
+         */
+        @NonNull
+        Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby);
+
+        /**
+         * Should only be used by the CompanionDeviceManagerService.
+         * @hide
+         */
+        @NonNull
+        Builder setLastTimeConnected(long lastTimeConnectedMs);
+    }
 }
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 82ad150..85855be 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -16,12 +16,14 @@
 
 package android.companion.virtual;
 
+import android.app.PendingIntent;
 import android.graphics.Point;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.os.ResultReceiver;
 
 /**
  * Interface for a virtual device.
@@ -41,6 +43,7 @@
      * Closes the virtual device and frees all associated resources.
      */
     void close();
+
     void createVirtualKeyboard(
             int displayId,
             String inputDeviceName,
@@ -66,4 +69,10 @@
     boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
     boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
+
+    /**
+     * Launches a pending intent on the given display that is owned by this virtual device.
+     */
+    void launchPendingIntent(
+            int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 858e4daa1..8ab6688 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -23,6 +23,8 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.Activity;
+import android.app.PendingIntent;
 import android.companion.AssociationInfo;
 import android.content.Context;
 import android.graphics.Point;
@@ -33,15 +35,19 @@
 import android.hardware.input.VirtualMouse;
 import android.hardware.input.VirtualTouchscreen;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.view.Surface;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.concurrent.Executor;
 
 /**
  * System level service for managing virtual devices.
@@ -129,6 +135,49 @@
         }
 
         /**
+         * Launches a given pending intent on the give display ID.
+         *
+         * @param displayId The display to launch the pending intent on. This display must be
+         *   created from this virtual device.
+         * @param pendingIntent The pending intent to be launched. If the intent is an activity
+         *   intent, the activity will be started on the virtual display using
+         *   {@link android.app.ActivityOptions#setLaunchDisplayId}. If the intent is a service or
+         *   broadcast intent, an attempt will be made to catch activities started as a result of
+         *   sending the pending intent and move them to the given display.
+         * @param executor The executor to run {@code launchCallback} on.
+         * @param launchCallback Callback that is called when the pending intent launching is
+         *   complete.
+         *
+         * @hide
+         */
+        public void launchPendingIntent(
+                int displayId,
+                @NonNull PendingIntent pendingIntent,
+                @NonNull Executor executor,
+                @NonNull LaunchCallback launchCallback) {
+            try {
+                mVirtualDevice.launchPendingIntent(
+                        displayId,
+                        pendingIntent,
+                        new ResultReceiver(new Handler(Looper.myLooper())) {
+                            @Override
+                            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                                super.onReceiveResult(resultCode, resultData);
+                                executor.execute(() -> {
+                                    if (resultCode == Activity.RESULT_OK) {
+                                        launchCallback.onLaunchSuccess();
+                                    } else {
+                                        launchCallback.onLaunchFailed();
+                                    }
+                                });
+                            }
+                        });
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Creates a virtual display for this virtual device. All displays created on the same
          * device belongs to the same display group.
          *
@@ -299,4 +348,22 @@
             }
         }
     }
+
+    /**
+     * Callback for launching pending intents on the virtual device.
+     *
+     * @hide
+     */
+    // TODO(b/194949534): Unhide this API
+    public interface LaunchCallback {
+        /**
+         * Called when the pending intent launched successfully.
+         */
+        void onLaunchSuccess();
+
+        /**
+         * Called when the pending intent failed to launch.
+         */
+        void onLaunchFailed();
+    }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a6d846b..d817f1e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2734,6 +2734,8 @@
      * API shipped in Android 11.
      * <li><code>202101</code>: corresponds to the features included in the Identity Credential
      * API shipped in Android 12.
+     * <li><code>202201</code>: corresponds to the features included in the Identity Credential
+     * API shipped in Android 13.
      * </ul>
      */
     @SdkConstant(SdkConstantType.FEATURE)
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 08f5a8a..0e42b02 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -22,6 +22,8 @@
 import android.hardware.input.InputSensorInfo;
 import android.os.Build;
 
+import java.util.UUID;
+
 /**
  * Class representing a sensor. Use {@link SensorManager#getSensorList} to get
  * the list of available sensors. For more information about Android sensors,
@@ -925,6 +927,7 @@
     @UnsupportedAppUsage
     private int     mFlags;
     private int     mId;
+    private UUID    mUuid;
 
     Sensor() {
     }
@@ -951,6 +954,8 @@
         this.mMaxDelay = sensorInfo.getMaxDelay();
         this.mFlags = sensorInfo.getFlags();
         this.mId = sensorInfo.getId();
+        // The UUID is never specified when creating a sensor from Input manager
+        this.mUuid = new UUID((long) this.mId, 0);
     }
 
     /**
@@ -1040,11 +1045,9 @@
     }
 
     /**
-     * Do not use.
-     *
-     * This method throws an UnsupportedOperationException.
-     *
-     * Use getId() if you want a unique ID.
+     * Reserved for system and audio servers.
+     * When called from an unauthorized context, the UUID will contain the
+     * sensor ID in the MSB and 0 in the LSB.
      *
      * @see getId
      *
@@ -1052,7 +1055,7 @@
      */
     @SystemApi
     public java.util.UUID getUuid() {
-        throw new UnsupportedOperationException();
+        return mUuid;
     }
 
     /**
@@ -1286,17 +1289,24 @@
     }
 
     /**
-     * Sets the ID associated with the sensor.
+     * Sets the UUID associated with the sensor.
      *
-     * The method name is misleading; while this ID is based on the UUID,
-     * we do not pass in the actual UUID.
+     * NOTE: to be used only by native bindings in SensorManager.
+     *
+     * @see #getUuid
+     */
+    private void setUuid(long msb, long lsb) {
+        mUuid = new UUID(msb, lsb);
+    }
+
+    /**
+     * Sets the ID associated with the sensor.
      *
      * NOTE: to be used only by native bindings in SensorManager.
      *
      * @see #getId
      */
-    private void setUuid(long msb, long lsb) {
-        // TODO(b/29547335): Rename this method to setId.
-        mId = (int) msb;
+    private void setId(int id) {
+        mId = id;
     }
 }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 6b5bec9..dc65bef 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -38,6 +38,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
 import android.security.keystore.KeyProperties;
 import android.text.TextUtils;
 import android.util.Log;
@@ -666,8 +667,8 @@
     /**
      * A wrapper class for the cryptographic operations supported by BiometricPrompt.
      *
-     * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and
-     * {@link IdentityCredential}.
+     * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
+     * {@link IdentityCredential}, and {@link PresentationSession}.
      *
      * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and
      * time-based. This is specified during key creation via the timeout parameter of the
@@ -697,10 +698,21 @@
             super(mac);
         }
 
+        /**
+         * Create from a {@link IdentityCredential} object.
+         *
+         * @param credential a {@link IdentityCredential} object.
+         * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
+         */
+        @Deprecated
         public CryptoObject(@NonNull IdentityCredential credential) {
             super(credential);
         }
 
+        public CryptoObject(@NonNull PresentationSession session) {
+            super(session);
+        }
+
         /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
@@ -728,10 +740,20 @@
         /**
          * Get {@link IdentityCredential} object.
          * @return {@link IdentityCredential} object or null if this doesn't contain one.
+         * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
          */
+        @Deprecated
         public @Nullable IdentityCredential getIdentityCredential() {
             return super.getIdentityCredential();
         }
+
+        /**
+         * Get {@link PresentationSession} object.
+         * @return {@link PresentationSession} object or null if this doesn't contain one.
+         */
+        public @Nullable PresentationSession getPresentationSession() {
+            return super.getPresentationSession();
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 7648cf2..d415706 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
 import android.security.keystore2.AndroidKeyStoreProvider;
 
 import java.security.Signature;
@@ -27,8 +28,8 @@
 
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
- * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and
- * {@link IdentityCredential} objects.
+ * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
+ * {@link IdentityCredential}, and {@link PresentationSession} objects.
  * @hide
  */
 public class CryptoObject {
@@ -46,10 +47,21 @@
         mCrypto = mac;
     }
 
+    /**
+     * Create from a {@link IdentityCredential} object.
+     *
+     * @param credential a {@link IdentityCredential} object.
+     * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
+     */
+    @Deprecated
     public CryptoObject(@NonNull IdentityCredential credential) {
         mCrypto = credential;
     }
 
+    public CryptoObject(@NonNull PresentationSession session) {
+        mCrypto = session;
+    }
+
     /**
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
@@ -77,12 +89,22 @@
     /**
      * Get {@link IdentityCredential} object.
      * @return {@link IdentityCredential} object or null if this doesn't contain one.
+     * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
      */
+    @Deprecated
     public IdentityCredential getIdentityCredential() {
         return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
     }
 
     /**
+     * Get {@link PresentationSession} object.
+     * @return {@link PresentationSession} object or null if this doesn't contain one.
+     */
+    public PresentationSession getPresentationSession() {
+        return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
+    }
+
+    /**
      * @hide
      * @return the opId associated with this object or 0 if none
      */
@@ -91,6 +113,8 @@
             return 0;
         } else if (mCrypto instanceof IdentityCredential) {
             return ((IdentityCredential) mCrypto).getCredstoreOperationHandle();
+        } else if (mCrypto instanceof PresentationSession) {
+            return ((PresentationSession) mCrypto).getCredstoreOperationHandle();
         }
         return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
     }
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 56f8142..b970559 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -306,22 +306,21 @@
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
 
-        if (cancel != null) {
-            if (cancel.isCanceled()) {
-                Slog.w(TAG, "enrollment already canceled");
-                return;
-            } else {
-                cancel.setOnCancelListener(new OnEnrollCancelListener());
-            }
+        if (cancel != null && cancel.isCanceled()) {
+            Slog.w(TAG, "enrollment already canceled");
+            return;
         }
 
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enroll");
-                mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver,
-                        mContext.getOpPackageName(), disabledFeatures, previewSurface,
-                        debugConsent);
+                final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
+                        mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
+                        previewSurface, debugConsent);
+                if (cancel != null) {
+                    cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or
@@ -359,21 +358,20 @@
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
 
-        if (cancel != null) {
-            if (cancel.isCanceled()) {
-                Slog.w(TAG, "enrollRemotely is already canceled.");
-                return;
-            } else {
-                cancel.setOnCancelListener(new OnEnrollCancelListener());
-            }
+        if (cancel != null && cancel.isCanceled()) {
+            Slog.w(TAG, "enrollRemotely is already canceled.");
+            return;
         }
 
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enrollRemotely");
-                mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver,
-                        mContext.getOpPackageName(), disabledFeatures);
+                final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
+                        mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
+                if (cancel != null) {
+                    cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enrollRemotely: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or
@@ -713,10 +711,10 @@
         }
     }
 
-    private void cancelEnrollment() {
+    private void cancelEnrollment(long requestId) {
         if (mService != null) {
             try {
-                mService.cancelEnrollment(mToken);
+                mService.cancelEnrollment(mToken, requestId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1100,9 +1098,16 @@
     }
 
     private class OnEnrollCancelListener implements OnCancelListener {
+        private final long mAuthRequestId;
+
+        private OnEnrollCancelListener(long id) {
+            mAuthRequestId = id;
+        }
+
         @Override
         public void onCancel() {
-            cancelEnrollment();
+            Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId);
+            cancelEnrollment(mAuthRequestId);
         }
     }
 
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e919824..989b001 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -76,15 +76,16 @@
     void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
 
     // Start face enrollment
-    void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
-            String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent);
+    long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
+            String opPackageName, in int [] disabledFeatures,
+            in Surface previewSurface, boolean debugConsent);
 
     // Start remote face enrollment
-    void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
+    long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
             String opPackageName, in int [] disabledFeatures);
 
     // Cancel enrollment in progress
-    void cancelEnrollment(IBinder token);
+    void cancelEnrollment(IBinder token, long requestId);
 
     // Removes the specified face enrollment for the specified userId.
     void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index fe04e5d..7e070bc 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -58,6 +58,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
 import android.util.Slog;
 import android.view.Surface;
 
@@ -183,9 +184,16 @@
     }
 
     private class OnEnrollCancelListener implements OnCancelListener {
+        private final long mAuthRequestId;
+
+        private OnEnrollCancelListener(long id) {
+            mAuthRequestId = id;
+        }
+
         @Override
         public void onCancel() {
-            cancelEnrollment();
+            Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId);
+            cancelEnrollment(mAuthRequestId);
         }
     }
 
@@ -264,10 +272,21 @@
          * Get {@link IdentityCredential} object.
          * @return {@link IdentityCredential} object or null if this doesn't contain one.
          * @hide
+         * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
          */
+        @Deprecated
         public IdentityCredential getIdentityCredential() {
             return super.getIdentityCredential();
         }
+
+        /**
+         * Get {@link PresentationSession} object.
+         * @return {@link PresentationSession} object or null if this doesn't contain one.
+         * @hide
+         */
+        public PresentationSession getPresentationSession() {
+            return super.getPresentationSession();
+        }
     }
 
     /**
@@ -646,20 +665,19 @@
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
 
-        if (cancel != null) {
-            if (cancel.isCanceled()) {
-                Slog.w(TAG, "enrollment already canceled");
-                return;
-            } else {
-                cancel.setOnCancelListener(new OnEnrollCancelListener());
-            }
+        if (cancel != null && cancel.isCanceled()) {
+            Slog.w(TAG, "enrollment already canceled");
+            return;
         }
 
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
-                mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
-                        mContext.getOpPackageName(), enrollReason);
+                final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
+                        mServiceReceiver, mContext.getOpPackageName(), enrollReason);
+                if (cancel != null) {
+                    cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
@@ -1302,9 +1320,9 @@
         return allSensors.isEmpty() ? null : allSensors.get(0);
     }
 
-    private void cancelEnrollment() {
+    private void cancelEnrollment(long requestId) {
         if (mService != null) try {
-            mService.cancelEnrollment(mToken);
+            mService.cancelEnrollment(mToken, requestId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ba1dc6d..cbff8b1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -84,11 +84,11 @@
     void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
 
     // Start fingerprint enrollment
-    void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
+    long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
             String opPackageName, int enrollReason);
 
     // Cancel enrollment in progress
-    void cancelEnrollment(IBinder token);
+    void cancelEnrollment(IBinder token, long requestId);
 
     // Any errors resulting from this call will be returned to the listener
     void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index f50aa99..147138e 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -69,6 +69,8 @@
 
     int getMultipathPreference(in Network network);
 
+    SubscriptionPlan getSubscriptionPlan(in NetworkTemplate template);
+    void onStatsProviderWarningOrLimitReached();
     SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
     void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
     String getSubscriptionPlansOwner(int subId);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 7ebb646..426fc61 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -535,6 +535,46 @@
     }
 
     /**
+     * Get subscription plan for the given networkTemplate.
+     *
+     * @param template the networkTemplate to get the subscription plan for.
+     * @return the active {@link SubscriptionPlan} for the given template, or
+     *         {@code null} if not found.
+     * @hide
+     */
+    @Nullable
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) {
+        try {
+            return mService.getSubscriptionPlan(template);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notifies that the specified {@link NetworkStatsProvider} has reached its quota
+     * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+     * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public void onStatsProviderWarningOrLimitReached() {
+        try {
+            mService.onStatsProviderWarningOrLimitReached();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets network policy settings back to factory defaults.
      *
      * @hide
diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java
index febd9b4..3f7521a 100644
--- a/core/java/android/net/annotations/PolicyDirection.java
+++ b/core/java/android/net/annotations/PolicyDirection.java
@@ -24,10 +24,6 @@
 
 /**
  * IPsec traffic direction.
- *
- * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class
- * to allow others to statically include it.
- *
  * @hide
  */
 @IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT})
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 125b573..69e6313 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -63,13 +63,22 @@
     private final int mOpportunisticMatchCriteria;
 
     private VcnCellUnderlyingNetworkTemplate(
-            int networkQuality,
             int meteredMatchCriteria,
+            int minEntryUpstreamBandwidthKbps,
+            int minExitUpstreamBandwidthKbps,
+            int minEntryDownstreamBandwidthKbps,
+            int minExitDownstreamBandwidthKbps,
             Set<String> allowedNetworkPlmnIds,
             Set<Integer> allowedSpecificCarrierIds,
             int roamingMatchCriteria,
             int opportunisticMatchCriteria) {
-        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, meteredMatchCriteria);
+        super(
+                NETWORK_PRIORITY_TYPE_CELL,
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps);
         mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
         mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
         mRoamingMatchCriteria = roamingMatchCriteria;
@@ -109,9 +118,17 @@
             @NonNull PersistableBundle in) {
         Objects.requireNonNull(in, "PersistableBundle is null");
 
-        final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
         final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
 
+        final int minEntryUpstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitUpstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minEntryDownstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitDownstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+
         final PersistableBundle plmnIdsBundle =
                 in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
         Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null");
@@ -131,8 +148,11 @@
         final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
 
         return new VcnCellUnderlyingNetworkTemplate(
-                networkQuality,
                 meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps,
                 allowedNetworkPlmnIds,
                 allowedSpecificCarrierIds,
                 roamingMatchCriteria,
@@ -243,7 +263,6 @@
 
     /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
     public static final class Builder {
-        private int mNetworkQuality = NETWORK_QUALITY_ANY;
         private int mMeteredMatchCriteria = MATCH_ANY;
 
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
@@ -252,29 +271,15 @@
         private int mRoamingMatchCriteria = MATCH_ANY;
         private int mOpportunisticMatchCriteria = MATCH_ANY;
 
+        private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set the required network quality to match this template.
-         *
-         * <p>Network quality is a aggregation of multiple signals that reflect the network link
-         * metrics. For example, the network validation bit (see {@link
-         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
-         * and signal strength.
-         *
-         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
-         * @hide
-         */
-        @NonNull
-        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
-            validateNetworkQuality(networkQuality);
-
-            mNetworkQuality = networkQuality;
-            return this;
-        }
-
-        /**
          * Set the matching criteria for metered networks.
          *
          * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
@@ -369,12 +374,92 @@
             return this;
         }
 
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
+         *     that ARE NOT the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
+         *     that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinUpstreamBandwidthKbps(
+                int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
+            validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
+
+            mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+            mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+
+            return this;
+        }
+
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
+         *     networks that ARE NOT the already-selected underlying network, or {@code 0} to
+         *     disable this requirement. Disabled by default.
+         * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
+         *     network that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinDownstreamBandwidthKbps(
+                int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
+            validateMinBandwidthKbps(
+                    minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
+
+            mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+            mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
+
+            return this;
+        }
+
         /** Build the VcnCellUnderlyingNetworkTemplate. */
         @NonNull
         public VcnCellUnderlyingNetworkTemplate build() {
             return new VcnCellUnderlyingNetworkTemplate(
-                    mNetworkQuality,
                     mMeteredMatchCriteria,
+                    mMinEntryUpstreamBandwidthKbps,
+                    mMinExitUpstreamBandwidthKbps,
+                    mMinEntryDownstreamBandwidthKbps,
+                    mMinExitDownstreamBandwidthKbps,
                     mAllowedNetworkPlmnIds,
                     mAllowedSpecificCarrierIds,
                     mRoamingMatchCriteria,
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 92956e8..a6830b7 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -17,7 +17,6 @@
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
@@ -169,18 +168,15 @@
     static {
         DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
-                        .setNetworkQuality(NETWORK_QUALITY_OK)
                         .setOpportunistic(MATCH_REQUIRED)
                         .build());
 
         DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
-                        .setNetworkQuality(NETWORK_QUALITY_OK)
                         .build());
 
         DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
-                        .setNetworkQuality(NETWORK_QUALITY_OK)
                         .build());
     }
 
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index 60fc936..3a9ca3e 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -48,23 +48,6 @@
     /** @hide */
     static final int NETWORK_PRIORITY_TYPE_CELL = 2;
 
-    /** Denotes that any network quality is acceptable. @hide */
-    public static final int NETWORK_QUALITY_ANY = 0;
-    /** Denotes that network quality needs to be OK. @hide */
-    public static final int NETWORK_QUALITY_OK = 100000;
-
-    private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
-
-    static {
-        NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY");
-        NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK");
-    }
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
-    public @interface NetworkQuality {}
-
     /**
      * Used to configure the matching criteria of a network characteristic. This may include network
      * capabilities, or cellular subscription information. Denotes that networks with or without the
@@ -103,44 +86,73 @@
     private final int mNetworkPriorityType;
 
     /** @hide */
-    static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
-
-    private final int mNetworkQuality;
-
-    /** @hide */
     static final String METERED_MATCH_KEY = "mMeteredMatchCriteria";
 
     private final int mMeteredMatchCriteria;
 
     /** @hide */
+    public static final int DEFAULT_MIN_BANDWIDTH_KBPS = 0;
+
+    /** @hide */
+    static final String MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY = "mMinEntryUpstreamBandwidthKbps";
+
+    private final int mMinEntryUpstreamBandwidthKbps;
+
+    /** @hide */
+    static final String MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY = "mMinExitUpstreamBandwidthKbps";
+
+    private final int mMinExitUpstreamBandwidthKbps;
+
+    /** @hide */
+    static final String MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY =
+            "mMinEntryDownstreamBandwidthKbps";
+
+    private final int mMinEntryDownstreamBandwidthKbps;
+
+    /** @hide */
+    static final String MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY = "mMinExitDownstreamBandwidthKbps";
+
+    private final int mMinExitDownstreamBandwidthKbps;
+
+    /** @hide */
     VcnUnderlyingNetworkTemplate(
-            int networkPriorityType, int networkQuality, int meteredMatchCriteria) {
+            int networkPriorityType,
+            int meteredMatchCriteria,
+            int minEntryUpstreamBandwidthKbps,
+            int minExitUpstreamBandwidthKbps,
+            int minEntryDownstreamBandwidthKbps,
+            int minExitDownstreamBandwidthKbps) {
         mNetworkPriorityType = networkPriorityType;
-        mNetworkQuality = networkQuality;
         mMeteredMatchCriteria = meteredMatchCriteria;
+        mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+        mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+        mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+        mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
     }
 
     /** @hide */
-    static void validateNetworkQuality(int networkQuality) {
+    static void validateMatchCriteria(int matchCriteria, String matchingCapability) {
         Preconditions.checkArgument(
-                networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
-                "Invalid networkQuality:" + networkQuality);
+                MATCH_CRITERIA_TO_STRING_MAP.contains(matchCriteria),
+                "Invalid matching criteria: " + matchCriteria + " for " + matchingCapability);
     }
 
     /** @hide */
-    static void validateMatchCriteria(int meteredMatchCriteria, String matchingCapability) {
+    static void validateMinBandwidthKbps(int minEntryBandwidth, int minExitBandwidth) {
         Preconditions.checkArgument(
-                MATCH_CRITERIA_TO_STRING_MAP.contains(meteredMatchCriteria),
-                "Invalid matching criteria: "
-                        + meteredMatchCriteria
-                        + " for "
-                        + matchingCapability);
+                minEntryBandwidth >= 0, "Invalid minEntryBandwidth, must be >= 0");
+        Preconditions.checkArgument(
+                minExitBandwidth >= 0, "Invalid minExitBandwidth, must be >= 0");
+        Preconditions.checkArgument(
+                minEntryBandwidth >= minExitBandwidth,
+                "Minimum entry bandwidth must be >= exit bandwidth");
     }
 
     /** @hide */
     protected void validate() {
-        validateNetworkQuality(mNetworkQuality);
         validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria");
+        validateMinBandwidthKbps(mMinEntryUpstreamBandwidthKbps, mMinExitUpstreamBandwidthKbps);
+        validateMinBandwidthKbps(mMinEntryDownstreamBandwidthKbps, mMinExitDownstreamBandwidthKbps);
     }
 
     /** @hide */
@@ -168,15 +180,24 @@
         final PersistableBundle result = new PersistableBundle();
 
         result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
-        result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
         result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria);
+        result.putInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, mMinEntryUpstreamBandwidthKbps);
+        result.putInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, mMinExitUpstreamBandwidthKbps);
+        result.putInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, mMinEntryDownstreamBandwidthKbps);
+        result.putInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, mMinExitDownstreamBandwidthKbps);
 
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mMeteredMatchCriteria);
+        return Objects.hash(
+                mNetworkPriorityType,
+                mMeteredMatchCriteria,
+                mMinEntryUpstreamBandwidthKbps,
+                mMinExitUpstreamBandwidthKbps,
+                mMinEntryDownstreamBandwidthKbps,
+                mMinExitDownstreamBandwidthKbps);
     }
 
     @Override
@@ -187,8 +208,11 @@
 
         final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other;
         return mNetworkPriorityType == rhs.mNetworkPriorityType
-                && mNetworkQuality == rhs.mNetworkQuality
-                && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria;
+                && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria
+                && mMinEntryUpstreamBandwidthKbps == rhs.mMinEntryUpstreamBandwidthKbps
+                && mMinExitUpstreamBandwidthKbps == rhs.mMinExitUpstreamBandwidthKbps
+                && mMinEntryDownstreamBandwidthKbps == rhs.mMinEntryDownstreamBandwidthKbps
+                && mMinExitDownstreamBandwidthKbps == rhs.mMinExitDownstreamBandwidthKbps;
     }
 
     /** @hide */
@@ -197,8 +221,8 @@
     }
 
     /** @hide */
-    static String getMatchCriteriaString(int meteredMatchCriteria) {
-        return getNameString(MATCH_CRITERIA_TO_STRING_MAP, meteredMatchCriteria);
+    static String getMatchCriteriaString(int matchCriteria) {
+        return getNameString(MATCH_CRITERIA_TO_STRING_MAP, matchCriteria);
     }
 
     /** @hide */
@@ -213,34 +237,63 @@
         pw.println(this.getClass().getSimpleName() + ":");
         pw.increaseIndent();
 
-        pw.println(
-                "mNetworkQuality: "
-                        + getNameString(NETWORK_QUALITY_TO_STRING_MAP, mNetworkQuality));
         pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
+        pw.println("mMinEntryUpstreamBandwidthKbps: " + mMinEntryUpstreamBandwidthKbps);
+        pw.println("mMinExitUpstreamBandwidthKbps: " + mMinExitUpstreamBandwidthKbps);
+        pw.println("mMinEntryDownstreamBandwidthKbps: " + mMinEntryDownstreamBandwidthKbps);
+        pw.println("mMinExitDownstreamBandwidthKbps: " + mMinExitDownstreamBandwidthKbps);
         dumpTransportSpecificFields(pw);
 
         pw.decreaseIndent();
     }
 
     /**
-     * Retrieve the required network quality to match this template.
-     *
-     * @see Builder#setNetworkQuality(int)
-     * @hide
-     */
-    @NetworkQuality
-    public int getNetworkQuality() {
-        return mNetworkQuality;
-    }
-
-    /**
      * Return the matching criteria for metered networks.
      *
      * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int)
      * @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int)
      */
-    @MatchCriteria
     public int getMetered() {
         return mMeteredMatchCriteria;
     }
+
+    /**
+     * Returns the minimum entry upstream bandwidth allowed by this template.
+     *
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+     */
+    public int getMinEntryUpstreamBandwidthKbps() {
+        return mMinEntryUpstreamBandwidthKbps;
+    }
+
+    /**
+     * Returns the minimum exit upstream bandwidth allowed by this template.
+     *
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int)
+     */
+    public int getMinExitUpstreamBandwidthKbps() {
+        return mMinExitUpstreamBandwidthKbps;
+    }
+
+    /**
+     * Returns the minimum entry downstream bandwidth allowed by this template.
+     *
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+     */
+    public int getMinEntryDownstreamBandwidthKbps() {
+        return mMinEntryDownstreamBandwidthKbps;
+    }
+
+    /**
+     * Returns the minimum exit downstream bandwidth allowed by this template.
+     *
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int)
+     */
+    public int getMinExitDownstreamBandwidthKbps() {
+        return mMinExitDownstreamBandwidthKbps;
+    }
 }
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 272ca9d..23a07ab 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -46,8 +46,19 @@
     @Nullable private final Set<String> mSsids;
 
     private VcnWifiUnderlyingNetworkTemplate(
-            int networkQuality, int meteredMatchCriteria, Set<String> ssids) {
-        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, meteredMatchCriteria);
+            int meteredMatchCriteria,
+            int minEntryUpstreamBandwidthKbps,
+            int minExitUpstreamBandwidthKbps,
+            int minEntryDownstreamBandwidthKbps,
+            int minExitDownstreamBandwidthKbps,
+            Set<String> ssids) {
+        super(
+                NETWORK_PRIORITY_TYPE_WIFI,
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps);
         mSsids = new ArraySet<>(ssids);
 
         validate();
@@ -75,15 +86,29 @@
             @NonNull PersistableBundle in) {
         Objects.requireNonNull(in, "PersistableBundle is null");
 
-        final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
         final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
 
+        final int minEntryUpstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitUpstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minEntryDownstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitDownstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+
         final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
         Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
         final Set<String> ssids =
                 new ArraySet<String>(
                         PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
-        return new VcnWifiUnderlyingNetworkTemplate(networkQuality, meteredMatchCriteria, ssids);
+        return new VcnWifiUnderlyingNetworkTemplate(
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps,
+                ssids);
     }
 
     /** @hide */
@@ -137,33 +162,18 @@
 
     /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
     public static final class Builder {
-        private int mNetworkQuality = NETWORK_QUALITY_ANY;
         private int mMeteredMatchCriteria = MATCH_ANY;
         @NonNull private final Set<String> mSsids = new ArraySet<>();
 
+        private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set the required network quality to match this template.
-         *
-         * <p>Network quality is a aggregation of multiple signals that reflect the network link
-         * metrics. For example, the network validation bit (see {@link
-         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
-         * and signal strength.
-         *
-         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
-         * @hide
-         */
-        @NonNull
-        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
-            validateNetworkQuality(networkQuality);
-
-            mNetworkQuality = networkQuality;
-            return this;
-        }
-
-        /**
          * Set the matching criteria for metered networks.
          *
          * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
@@ -200,11 +210,93 @@
             return this;
         }
 
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
+         *     that ARE NOT the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
+         *     that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinUpstreamBandwidthKbps(
+                int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
+            validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
+
+            mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+            mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+
+            return this;
+        }
+
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
+         *     networks that ARE NOT the already-selected underlying network, or {@code 0} to
+         *     disable this requirement. Disabled by default.
+         * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
+         *     network that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinDownstreamBandwidthKbps(
+                int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
+            validateMinBandwidthKbps(
+                    minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
+
+            mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+            mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
+
+            return this;
+        }
+
         /** Build the VcnWifiUnderlyingNetworkTemplate. */
         @NonNull
         public VcnWifiUnderlyingNetworkTemplate build() {
             return new VcnWifiUnderlyingNetworkTemplate(
-                    mNetworkQuality, mMeteredMatchCriteria, mSsids);
+                    mMeteredMatchCriteria,
+                    mMinEntryUpstreamBandwidthKbps,
+                    mMinExitUpstreamBandwidthKbps,
+                    mMinEntryDownstreamBandwidthKbps,
+                    mMinExitDownstreamBandwidthKbps,
+                    mSsids);
         }
     }
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3f42164..fe86874 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -90,7 +90,7 @@
     Bundle getApplicationRestrictionsForUser(in String packageName, int userId);
     void setDefaultGuestRestrictions(in Bundle restrictions);
     Bundle getDefaultGuestRestrictions();
-    int removeUserOrSetEphemeral(int userId, boolean evenWhenDisallowed);
+    int removeUserWhenPossible(int userId, boolean overrideDevicePolicy);
     boolean markGuestForDeletion(int userId);
     UserInfo findCurrentGuestUser();
     boolean isQuietModeEnabled(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index edf6280..190f5f1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4750,7 +4750,7 @@
     public int removeUserWhenPossible(@NonNull UserHandle user,
             boolean overrideDevicePolicy) {
         try {
-            return mService.removeUserOrSetEphemeral(user.getIdentifier(), overrideDevicePolicy);
+            return mService.removeUserWhenPossible(user.getIdentifier(), overrideDevicePolicy);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -4777,7 +4777,7 @@
     public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
             boolean evenWhenDisallowed) {
         try {
-            return mService.removeUserOrSetEphemeral(userId, evenWhenDisallowed);
+            return mService.removeUserWhenPossible(userId, evenWhenDisallowed);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 72e2863..5cdb1fd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10525,7 +10525,7 @@
                 DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
         })
         @Retention(RetentionPolicy.SOURCE)
-        @interface DeviceStateRotationLockSetting {
+        public @interface DeviceStateRotationLockSetting {
         }
 
         /**
diff --git a/core/java/android/util/Dumpable.java b/core/java/android/util/Dumpable.java
new file mode 100644
index 0000000..79c576d
--- /dev/null
+++ b/core/java/android/util/Dumpable.java
@@ -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 android.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.PrintWriter;
+
+/**
+ * Represents an object whose state can be dumped into a {@link PrintWriter}.
+ */
+public interface Dumpable {
+
+    /**
+     * Gets the name of the {@link Dumpable}.
+     *
+     * @return class name, by default.
+     */
+    @NonNull
+    default String getDumpableName() {
+        return getClass().getName();
+    }
+
+    //TODO(b/149254050): decide whether it should take a ParcelFileDescription as well.
+
+    /**
+     * Dumps the internal state into the given {@code writer}.
+     *
+     * @param writer writer to be written to
+     * @param args optional list of arguments
+     */
+    void dump(@NonNull PrintWriter writer, @Nullable String[] args);
+}
diff --git a/core/java/android/util/DumpableContainer.java b/core/java/android/util/DumpableContainer.java
new file mode 100644
index 0000000..04d19dc
--- /dev/null
+++ b/core/java/android/util/DumpableContainer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.annotation.NonNull;
+
+/**
+ * Objects that contains a list of {@link Dumpable}, which will be dumped when the object itself
+ * is dumped.
+ */
+public interface DumpableContainer {
+
+    /**
+     * Adds the given {@link Dumpable dumpable} to the container.
+     *
+     * <p>If a dumpable with the same {@link Dumpable#getDumpableName() name} was added before, this
+     * call is ignored.
+     *
+     * @param dumpable dumpable to be added.
+     *
+     * @throws IllegalArgumentException if the {@link Dumpable#getDumpableName() dumpable name} is
+     * {@code null}.
+     *
+     * @return {@code true} if the dumpable was added, {@code false} if the call was ignored.
+     */
+    boolean addDumpable(@NonNull Dumpable dumpable);
+}
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
new file mode 100644
index 0000000..fc9661a
--- /dev/null
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -0,0 +1,28 @@
+/*
+** 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.
+*/
+
+package android.view;
+
+import android.content.res.Configuration;
+
+/**
+ * API from content embedder back to embedded content in SurfaceControlViewHost
+ * {@hide}
+ */
+oneway interface ISurfaceControlViewHost {
+    void onConfigurationChanged(in Configuration newConfig);
+    void onDispatchDetachedFromWindow();
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index adb8b86..0ef5854 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -478,10 +478,15 @@
     public static final int FLAG_IS_GENERATED_GESTURE = 0x8;
 
     /**
-     * This flag associated with {@link #ACTION_POINTER_UP}, this indicates that the pointer
-     * has been canceled. Typically this is used for palm event when the user has accidental
-     * touches.
-     * @hide
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
      */
     public static final int FLAG_CANCELED = 0x20;
 
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index d160be5..43df294 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -80,6 +80,7 @@
 per-file IPinnedStackListener.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IRecents*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IRemote*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ISurfaceControlViewHost*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IWindow*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file RemoteAnimation*.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file RemoteAnimation*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index a6c5042d..85a9dbd 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -45,6 +46,35 @@
     private SurfaceControl mSurfaceControl;
     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
 
+    private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub {
+        @Override
+        public void onConfigurationChanged(Configuration configuration) {
+            if (mViewRoot == null) {
+                return;
+            }
+            mViewRoot.mHandler.post(() -> {
+                if (mWm != null) {
+                    mWm.setConfiguration(configuration);
+                }
+                if (mViewRoot != null) {
+                    mViewRoot.forceWmRelayout();
+                }
+            });
+        }
+
+        @Override
+        public void onDispatchDetachedFromWindow() {
+            if (mViewRoot == null) {
+                return;
+            }
+            mViewRoot.mHandler.post(() -> {
+                release();
+            });
+        }
+    }
+
+    private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
+
     /**
      * Package encapsulating a Surface hierarchy which contains interactive view
      * elements. It's expected to get this object from
@@ -71,12 +101,14 @@
         private SurfaceControl mSurfaceControl;
         private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
         private final IBinder mInputToken;
+        private final ISurfaceControlViewHost mRemoteInterface;
 
         SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
-                       IBinder inputToken) {
+               IBinder inputToken, ISurfaceControlViewHost ri) {
             mSurfaceControl = sc;
             mAccessibilityEmbeddedConnection = connection;
             mInputToken = inputToken;
+            mRemoteInterface = ri;
         }
 
         /**
@@ -97,6 +129,7 @@
             }
             mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
             mInputToken = other.mInputToken;
+            mRemoteInterface = other.mRemoteInterface;
         }
 
         private SurfacePackage(Parcel in) {
@@ -105,6 +138,8 @@
             mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
                     in.readStrongBinder());
             mInputToken = in.readStrongBinder();
+            mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface(
+                in.readStrongBinder());
         }
 
         /**
@@ -126,6 +161,13 @@
             return mAccessibilityEmbeddedConnection;
         }
 
+        /**
+         * @hide
+         */
+        public ISurfaceControlViewHost getRemoteInterface() {
+            return mRemoteInterface;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -136,6 +178,7 @@
             mSurfaceControl.writeToParcel(out, flags);
             out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
             out.writeStrongBinder(mInputToken);
+            out.writeStrongBinder(mRemoteInterface.asBinder());
         }
 
         /**
@@ -231,7 +274,7 @@
     public @Nullable SurfacePackage getSurfacePackage() {
         if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
             return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection,
-                    mViewRoot.getInputToken());
+                mViewRoot.getInputToken(), mRemoteInterface);
         } else {
             return null;
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7718511..7412931 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10639,4 +10639,9 @@
     boolean wasRelayoutRequested() {
         return mRelayoutRequested;
     }
+
+    void forceWmRelayout() {
+       mForceNextWindowRelayout = true;
+       scheduleTraversals();
+    }
 }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d699194d..5176f9b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -87,7 +87,7 @@
         mHostInputToken = hostInputToken;
     }
 
-    protected void setConfiguration(Configuration configuration) {
+    public void setConfiguration(Configuration configuration) {
         mConfiguration.setTo(configuration);
     }
 
@@ -330,6 +330,7 @@
     public void setInsets(android.view.IWindow window, int touchableInsets,
             android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets,
             android.graphics.Region touchableRegion) {
+        setTouchRegion(window.asBinder(), touchableRegion);
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index 3d68692..3f6a871 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -22,6 +22,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.Typeface;
@@ -30,6 +31,8 @@
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
 
+import com.android.internal.R;
+
 import java.util.ArrayList;
 import java.util.Locale;
 
@@ -51,6 +54,7 @@
     private final ArrayList<CaptioningChangeListener> mListeners = new ArrayList<>();
     private final ContentResolver mContentResolver;
     private final ContentObserver mContentObserver;
+    private final Resources mResources;
 
     /**
      * Creates a new captioning manager for the specified context.
@@ -62,6 +66,7 @@
 
         final Handler handler = new Handler(context.getMainLooper());
         mContentObserver = new MyContentObserver(handler);
+        mResources = context.getResources();
     }
 
     /**
@@ -181,6 +186,13 @@
         }
     }
 
+    /**
+     * Returns true if system wide call captioning is enabled for this device.
+     */
+    public boolean isCallCaptioningEnabled() {
+        return mResources.getBoolean(R.bool.config_systemCaptionsServiceCallsEnabled);
+    }
+
     private void notifyEnabledChanged() {
         final boolean enabled = isEnabled();
         synchronized (mListeners) {
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index c3ef881..3359a41 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
+import android.util.ArraySet;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -81,8 +82,10 @@
 
     /**
      * This is called when the apps that contains running activities on the display has changed.
+     * The running activities refer to the non-finishing activities regardless of they are running
+     * in a process.
      */
-    public void onRunningAppsChanged(int[] runningUids) {}
+    public void onRunningAppsChanged(ArraySet<Integer> runningUids) {}
 
     /** Dump debug data */
     public void dump(String prefix, final PrintWriter pw) {
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 4ba7ef2..547535d 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -19,9 +19,10 @@
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 import static android.window.ConfigurationHelper.shouldUpdateResources;
 
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
 import android.app.IWindowToken;
 import android.app.ResourcesManager;
 import android.content.Context;
@@ -30,7 +31,9 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IWindowManager;
@@ -71,6 +74,8 @@
 
     private boolean mAttachToWindowContainer;
 
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
     /**
      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
      * can only attach one {@link Context}.
@@ -132,7 +137,8 @@
             if (configuration == null) {
                 return false;
             }
-            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
+            mHandler.post(() -> onConfigurationChanged(configuration, displayId,
+                    false /* shouldReportConfigChange */));
             mAttachToWindowContainer = true;
             return true;
         } catch (RemoteException e) {
@@ -179,9 +185,11 @@
      * @param newConfig the updated {@link Configuration}
      * @param newDisplayId the updated {@link android.view.Display} ID
      */
+    @BinderThread
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
-        onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
+        mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId,
+                true /* shouldReportConfigChange */));
     }
 
     // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
@@ -192,6 +200,7 @@
      * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
      * whether to dispatch configuration update or not.
      */
+    @MainThread
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
             boolean shouldReportConfigChange) {
@@ -217,16 +226,14 @@
 
             if (shouldReportConfigChange && context instanceof WindowContext) {
                 final WindowContext windowContext = (WindowContext) context;
-                ActivityThread.currentActivityThread().getHandler().post(
-                        () -> windowContext.dispatchConfigurationChanged(newConfig));
+                windowContext.dispatchConfigurationChanged(newConfig);
             }
 
             final int diff = mConfiguration.diffPublicOnly(newConfig);
             if (shouldReportConfigChange && diff != 0
                     && context instanceof WindowProviderService) {
                 final WindowProviderService windowProviderService = (WindowProviderService) context;
-                ActivityThread.currentActivityThread().getHandler().post(
-                        () -> windowProviderService.onConfigurationChanged(newConfig));
+                windowProviderService.onConfigurationChanged(newConfig);
             }
             freeTextLayoutCachesIfNeeded(diff);
             if (mShouldDumpConfigForIme) {
@@ -248,12 +255,15 @@
         }
     }
 
+    @BinderThread
     @Override
     public void onWindowTokenRemoved() {
-        final Context context = mContextRef.get();
-        if (context != null) {
-            context.destroy();
-            mContextRef.clear();
-        }
+        mHandler.post(() -> {
+            final Context context = mContextRef.get();
+            if (context != null) {
+                context.destroy();
+                mContextRef.clear();
+            }
+        });
     }
 }
diff --git a/core/java/com/android/internal/util/dump/DumpableContainerImpl.java b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
new file mode 100644
index 0000000..d48b4b1
--- /dev/null
+++ b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal.util.dump;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Dumpable;
+import android.util.DumpableContainer;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+// TODO(b/149254050): add unit tests
+/**
+ * Helper class for {@link DumpableContainer} implementations - they can "implement it by
+ * association", i.e., by delegating the interface methods to a {@code DumpableContainerImpl}.
+ *
+ * @hide
+ */
+public final class DumpableContainerImpl implements DumpableContainer {
+
+    private static final String TAG = DumpableContainerImpl.class.getSimpleName();
+
+    private static final boolean DEBUG = false;
+
+    @Nullable
+    private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
+
+    @Override
+    public boolean addDumpable(Dumpable dumpable) {
+        Objects.requireNonNull(dumpable, "dumpable");
+        String name = dumpable.getDumpableName();
+        Objects.requireNonNull(name, () -> "name of" + dumpable);
+
+        if (mDumpables.containsKey(name)) {
+            Log.e(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
+                    + " with that name (" + name + "): " + mDumpables.get(name));
+            return false;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Adding " + name + " -> " + dumpable);
+        }
+        mDumpables.put(name,  dumpable);
+        return true;
+    }
+
+    /**
+     * Dumps the number of dumpable, without a newline.
+     */
+    private int dumpNumberDumpables(IndentingPrintWriter writer) {
+        int size = mDumpables == null ? 0 : mDumpables.size();
+        if (size == 0) {
+            writer.print("No dumpables");
+        } else {
+            writer.print(size); writer.print(" dumpables");
+        }
+        return size;
+    }
+
+    /**
+     * Lists the name of all dumpables to the given {@code writer}.
+     */
+    public void listDumpables(String prefix, PrintWriter writer) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
+
+        int size = dumpNumberDumpables(ipw);
+        if (size == 0) {
+            ipw.println();
+            return;
+        }
+        ipw.print(": ");
+        for (int i = 0; i < size; i++) {
+            ipw.print(mDumpables.keyAt(i));
+            if (i < size - 1) ipw.print(' ');
+        }
+        ipw.println();
+    }
+
+    /**
+     * Dumps the content of all dumpables to the given {@code writer}.
+     */
+    public void dumpAllDumpables(String prefix, PrintWriter writer, String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
+        int size = dumpNumberDumpables(ipw);
+        if (size == 0) {
+            ipw.println();
+            return;
+        }
+        ipw.println(": ");
+
+        for (int i = 0; i < size; i++) {
+            String dumpableName = mDumpables.keyAt(i);
+            ipw.print('#'); ipw.print(i); ipw.print(": "); ipw.println(dumpableName);
+            Dumpable dumpable = mDumpables.valueAt(i);
+            indentAndDump(ipw, dumpable, args);
+        }
+    }
+
+    private void indentAndDump(IndentingPrintWriter writer, Dumpable dumpable, String[] args) {
+        writer.increaseIndent();
+        try {
+            dumpable.dump(writer, args);
+        } finally {
+            writer.decreaseIndent();
+        }
+    }
+
+    /**
+     * Dumps the content of a specific dumpable to the given {@code writer}.
+     */
+    @SuppressWarnings("resource") // cannot close ipw as it would close writer
+    public void dumpOneDumpable(String prefix, PrintWriter writer, String dumpableName,
+            String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
+        Dumpable dumpable = mDumpables.get(dumpableName);
+        if (dumpable == null) {
+            ipw.print("No "); ipw.println(dumpableName);
+            return;
+        }
+        ipw.print(dumpableName); ipw.println(':');
+        indentAndDump(ipw, dumpable, args);
+    }
+}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d0504fb..d039bcf 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -66,6 +66,7 @@
     jfieldID    flags;
     //methods
     jmethodID   setType;
+    jmethodID   setId;
     jmethodID   setUuid;
     jmethodID   init;
 } gSensorOffsets;
@@ -112,6 +113,7 @@
     sensorOffsets.flags = GetFieldIDOrDie(_env,sensorClass, "mFlags", "I");
 
     sensorOffsets.setType = GetMethodIDOrDie(_env,sensorClass, "setType", "(I)Z");
+    sensorOffsets.setId = GetMethodIDOrDie(_env,sensorClass, "setId", "(I)V");
     sensorOffsets.setUuid = GetMethodIDOrDie(_env,sensorClass, "setUuid", "(JJ)V");
     sensorOffsets.init = GetMethodIDOrDie(_env,sensorClass, "<init>", "()V");
 
@@ -188,9 +190,10 @@
             env->SetObjectField(sensor, sensorOffsets.stringType, stringType);
         }
 
-        // TODO(b/29547335): Rename "setUuid" method to "setId".
-        int64_t id = nativeSensor.getId();
-        env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, 0);
+        int32_t id = nativeSensor.getId();
+        env->CallVoidMethod(sensor, sensorOffsets.setId, id);
+        Sensor::uuid_t uuid = nativeSensor.getUuid();
+        env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
     }
     return sensor;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 777ddaa..d6bbe71 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3595,6 +3595,18 @@
     <permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"
         android:protectionLevel="signature|privileged" />
 
+    <!-- ========================================= -->
+    <!-- Permissions for SupplementalApi -->
+    <!-- ========================================= -->
+    <eat-comment />
+
+    <!-- TODO(b/213488783): Update with correct names. -->
+    <!-- Allows an application to access SupplementalApis. -->
+    <permission android:name="android.permission.ACCESS_SUPPLEMENTAL_APIS"
+        android:label="@string/permlab_accessSupplementalApi"
+        android:description="@string/permdesc_accessSupplementalApi"
+        android:protectionLevel="normal" />
+
     <!-- ==================================== -->
     <!-- Private permissions                  -->
     <!-- ==================================== -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5fa7409..574c2f7 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3317,8 +3317,6 @@
   </staging-public-group>
 
   <staging-public-group type="bool" first-id="0x01cf0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_systemCaptionsServiceCallsEnabled" />
     <!-- @hide @TestApi -->
     <public name="config_preventImeStartupUnlessTextEditor" />
   </staging-public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2879759..6577ebc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4046,11 +4046,16 @@
     <!-- Description of an application permission that lets it ask user to ignore battery optimizations for that app-->
     <string name="permdesc_requestIgnoreBatteryOptimizations">Allows an app to ask for permission to ignore battery optimizations for that app.</string>
 
-    <!-- Title of an application permission that lets query all other packages. [CHAR LIMIT=NONE] -->
+    <!-- Title of an application permission that lets it query all other packages. [CHAR LIMIT=NONE] -->
     <string name="permlab_queryAllPackages">query all packages</string>
     <!-- Description of an application permission that lets it query all other packages. [CHAR LIMIT=NONE] -->
     <string name="permdesc_queryAllPackages">Allows an app to see all installed packages.</string>
 
+    <!-- Title of an application permission that lets it access SupplementalApis. [CHAR LIMIT=NONE] -->
+    <string name="permlab_accessSupplementalApi">access SupplementalApis</string>
+    <!-- Description of an application permission that lets it access SupplementalApis. [CHAR LIMIT=NONE]-->
+    <string name="permdesc_accessSupplementalApi">Allows an application to access SupplementalApis.</string>
+
     <!-- Shown in the tutorial for tap twice for zoom control. -->
     <string name="tutorial_double_tap_to_zoom_message_short">Tap twice for zoom control</string>
 
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index dcfca84..3b2f244 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -42,7 +42,6 @@
         <item name="outlineSpotShadowColor">@color/btn_colored_background_material</item>
         <item name="textAppearance">?attr/textAppearanceButton</item>
         <item name="textColor">@color/btn_colored_text_material</item>
-        <item name="drawableTint">@color/btn_colored_text_material</item>
     </style>
     <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView" />
     <style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0acd4bf..4e77563 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3654,6 +3654,8 @@
   <java-symbol type="string" name="config_retailDemoPackage" />
   <java-symbol type="string" name="config_retailDemoPackageSignature" />
 
+  <java-symbol type="bool" name="config_systemCaptionsServiceCallsEnabled" />
+
   <java-symbol type="string" name="notification_channel_foreground_service" />
   <java-symbol type="string" name="foreground_service_app_in_background" />
   <java-symbol type="string" name="foreground_service_apps_in_background" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ee0fb44..6f5951b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -392,6 +392,7 @@
         <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
         <permission name="android.permission.SET_WALLPAPER" />
         <permission name="android.permission.SET_WALLPAPER_COMPONENT" />
+        <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
         <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
         <!-- Permissions required for Incremental CTS tests -->
         <permission name="com.android.permission.USE_INSTALLER_V2"/>
diff --git a/identity/java/android/security/identity/CredentialDataRequest.java b/identity/java/android/security/identity/CredentialDataRequest.java
new file mode 100644
index 0000000..2a47a02
--- /dev/null
+++ b/identity/java/android/security/identity/CredentialDataRequest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * An object representing a request for credential data.
+ */
+public class CredentialDataRequest {
+    CredentialDataRequest() {}
+
+    /**
+     * Gets the device-signed entries to request.
+     *
+     * @return the device-signed entries to request.
+     */
+    public @NonNull Map<String, Collection<String>> getDeviceSignedEntriesToRequest() {
+        return mDeviceSignedEntriesToRequest;
+    }
+
+    /**
+     * Gets the issuer-signed entries to request.
+     *
+     * @return the issuer-signed entries to request.
+     */
+    public @NonNull Map<String, Collection<String>> getIssuerSignedEntriesToRequest() {
+        return mIssuerSignedEntriesToRequest;
+    }
+
+    /**
+     * Gets whether to allow using an authentication key which use count has been exceeded.
+     *
+     * <p>By default this is set to true.
+     *
+     * @return whether to allow using an authentication key which use
+     *         count has been exceeded if no other key is available.
+     */
+    public boolean isAllowUsingExhaustedKeys() {
+        return mAllowUsingExhaustedKeys;
+    }
+
+    /**
+     * Gets whether to allow using an authentication key which is expired.
+     *
+     * <p>By default this is set to false.
+     *
+     * @return whether to allow using an authentication key which is
+     *         expired if no other key is available.
+     */
+    public boolean isAllowUsingExpiredKeys() {
+        return mAllowUsingExpiredKeys;
+    }
+
+    /**
+     * Gets whether to increment the use-count for the authentication key used.
+     *
+     * <p>By default this is set to true.
+     *
+     * @return whether to increment the use count of the authentication key used.
+     */
+    public boolean isIncrementUseCount() {
+        return mIncrementUseCount;
+    }
+
+    /**
+     * Gets the request message CBOR.
+     *
+     * <p>This data structure is described in the documentation for the
+     * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+     *
+     * @return the request message CBOR as described above.
+     */
+    public @Nullable byte[] getRequestMessage() {
+        return mRequestMessage;
+    }
+
+    /**
+     * Gets the reader signature.
+     *
+     * <p>This data structure is described in the documentation for the
+     * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+     *
+     * @return a {@code COSE_Sign1} structure as described above.
+     */
+    public @Nullable byte[] getReaderSignature() {
+        return mReaderSignature;
+    }
+
+    Map<String, Collection<String>> mDeviceSignedEntriesToRequest = new LinkedHashMap<>();
+    Map<String, Collection<String>> mIssuerSignedEntriesToRequest = new LinkedHashMap<>();
+    boolean mAllowUsingExhaustedKeys = true;
+    boolean mAllowUsingExpiredKeys = false;
+    boolean mIncrementUseCount = true;
+    byte[] mRequestMessage = null;
+    byte[] mReaderSignature = null;
+
+    /**
+     * A builder for {@link CredentialDataRequest}.
+     */
+    public static final class Builder {
+        private CredentialDataRequest mData;
+
+        /**
+         * Creates a new builder.
+         */
+        public Builder() {
+            mData = new CredentialDataRequest();
+        }
+
+        /**
+         * Sets the device-signed entries to request.
+         *
+         * @param entriesToRequest the device-signed entries to request.
+         */
+        public @NonNull Builder setDeviceSignedEntriesToRequest(
+                @NonNull Map<String, Collection<String>> entriesToRequest) {
+            mData.mDeviceSignedEntriesToRequest = entriesToRequest;
+            return this;
+        }
+
+        /**
+         * Sets the issuer-signed entries to request.
+         *
+         * @param entriesToRequest the issuer-signed entries to request.
+         * @return the builder.
+         */
+        public @NonNull Builder setIssuerSignedEntriesToRequest(
+                @NonNull Map<String, Collection<String>> entriesToRequest) {
+            mData.mIssuerSignedEntriesToRequest = entriesToRequest;
+            return this;
+        }
+
+        /**
+         * Sets whether to allow using an authentication key which use count has been exceeded.
+         *
+         * By default this is set to true.
+         *
+         * @param allowUsingExhaustedKeys whether to allow using an authentication key which use
+         *                                count has been exceeded if no other key is available.
+         * @return the builder.
+         */
+        public @NonNull Builder setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
+            mData.mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
+            return this;
+        }
+
+        /**
+         * Sets whether to allow using an authentication key which is expired.
+         *
+         * By default this is set to false.
+         *
+         * @param allowUsingExpiredKeys whether to allow using an authentication key which is
+         *                              expired if no other key is available.
+         * @return the builder.
+         */
+        public @NonNull Builder setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
+            mData.mAllowUsingExpiredKeys = allowUsingExpiredKeys;
+            return this;
+        }
+
+        /**
+         * Sets whether to increment the use-count for the authentication key used.
+         *
+         * By default this is set to true.
+         *
+         * @param incrementUseCount whether to increment the use count of the authentication
+         *                          key used.
+         * @return the builder.
+         */
+        public @NonNull Builder setIncrementUseCount(boolean incrementUseCount) {
+            mData.mIncrementUseCount = incrementUseCount;
+            return this;
+        }
+
+        /**
+         * Sets the request message CBOR.
+         *
+         * <p>This data structure is described in the documentation for the
+         * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+         *
+         * @param requestMessage the request message CBOR as described above.
+         * @return the builder.
+         */
+        public @NonNull Builder setRequestMessage(@NonNull byte[] requestMessage) {
+            mData.mRequestMessage = requestMessage;
+            return this;
+        }
+
+        /**
+         * Sets the reader signature.
+         *
+         * <p>This data structure is described in the documentation for the
+         * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method.
+         *
+         * @param readerSignature a {@code COSE_Sign1} structure as described above.
+         * @return the builder.
+         */
+        public @NonNull Builder setReaderSignature(@NonNull byte[] readerSignature) {
+            mData.mReaderSignature = readerSignature;
+            return this;
+        }
+
+        /**
+         * Finishes building a {@link CredentialDataRequest}.
+         *
+         * @return the {@link CredentialDataRequest} object.
+         */
+        public @NonNull CredentialDataRequest build() {
+            return mData;
+        }
+    }
+}
diff --git a/identity/java/android/security/identity/CredentialDataResult.java b/identity/java/android/security/identity/CredentialDataResult.java
new file mode 100644
index 0000000..beb03af
--- /dev/null
+++ b/identity/java/android/security/identity/CredentialDataResult.java
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.util.Collection;
+
+
+/**
+ * An object that contains the result of retrieving data from a credential. This is used to return
+ * data requested in a {@link PresentationSession}.
+ */
+public abstract class CredentialDataResult {
+    /**
+     * @hide
+     */
+    protected CredentialDataResult() {}
+
+    /**
+     * Returns a CBOR structure containing the retrieved device-signed data.
+     *
+     * <p>This structure - along with the session transcript - may be cryptographically
+     * authenticated to prove to the reader that the data is from a trusted credential and
+     * {@link #getDeviceMac()} can be used to get a MAC.
+     *
+     * <p>The CBOR structure which is cryptographically authenticated is the
+     * {@code DeviceAuthenticationBytes} structure according to the following
+     * <a href="https://tools.ietf.org/html/rfc8610">CDDL</a> schema:
+     *
+     * <pre>
+     *   DeviceAuthentication = [
+     *     "DeviceAuthentication",
+     *     SessionTranscript,
+     *     DocType,
+     *     DeviceNameSpacesBytes
+     *   ]
+     *
+     *   DocType = tstr
+     *   SessionTranscript = any
+     *   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+     *   DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+     * </pre>
+     *
+     * <p>where
+     *
+     * <pre>
+     *   DeviceNameSpaces = {
+     *     * NameSpace => DeviceSignedItems
+     *   }
+     *
+     *   DeviceSignedItems = {
+     *     + DataItemName => DataItemValue
+     *   }
+     *
+     *   NameSpace = tstr
+     *   DataItemName = tstr
+     *   DataItemValue = any
+     * </pre>
+     *
+     * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure
+     * as defined above.
+     *
+     * @return The bytes of the {@code DeviceNameSpaces} CBOR structure.
+     */
+    public abstract @NonNull byte[] getDeviceNameSpaces();
+
+    /**
+     * Returns a message authentication code over the {@code DeviceAuthenticationBytes} CBOR
+     * specified in {@link #getDeviceNameSpaces()}, to prove to the reader that the data
+     * is from a trusted credential.
+     *
+     * <p>The MAC proves to the reader that the data is from a trusted credential. This code is
+     * produced by using the key agreement and key derivation function from the ciphersuite
+     * with the authentication private key and the reader ephemeral public key to compute a
+     * shared message authentication code (MAC) key, then using the MAC function from the
+     * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of
+     * ISO/IEC 18013-5 for details of this operation.
+     *
+     * <p>If the session transcript or reader ephemeral key wasn't set on the {@link
+     * PresentationSession} used to obtain this data no message authencation code will be produced
+     * and this method will return {@code null}.
+     *
+     * @return A COSE_Mac0 structure with the message authentication code as described above
+     *         or {@code null} if the conditions specified above are not met.
+     */
+    public abstract @Nullable byte[] getDeviceMac();
+
+    /**
+     * Returns the static authentication data associated with the dynamic authentication
+     * key used to MAC the data returned by {@link #getDeviceNameSpaces()}.
+     *
+     * @return The static authentication data associated with dynamic authentication key used to
+     * MAC the data.
+     */
+    public abstract @NonNull byte[] getStaticAuthenticationData();
+
+    /**
+     * Gets the device-signed entries that was returned.
+     *
+     * @return an object to examine the entries returned.
+     */
+    public abstract @NonNull Entries getDeviceSignedEntries();
+
+    /**
+     * Gets the issuer-signed entries that was returned.
+     *
+     * @return an object to examine the entries returned.
+     */
+    public abstract @NonNull Entries getIssuerSignedEntries();
+
+    /**
+     * A class for representing data elements returned.
+     */
+    public interface Entries {
+        /** Value was successfully retrieved. */
+        int STATUS_OK = 0;
+
+        /** The entry does not exist. */
+        int STATUS_NO_SUCH_ENTRY = 1;
+
+        /** The entry was not requested. */
+        int STATUS_NOT_REQUESTED = 2;
+
+        /** The entry wasn't in the request message. */
+        int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
+
+        /** The entry was not retrieved because user authentication failed. */
+        int STATUS_USER_AUTHENTICATION_FAILED = 4;
+
+        /** The entry was not retrieved because reader authentication failed. */
+        int STATUS_READER_AUTHENTICATION_FAILED = 5;
+
+        /**
+         * The entry was not retrieved because it was configured without any access
+         * control profile.
+         */
+        int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
+
+        /**
+         * Gets the names of namespaces with retrieved entries.
+         *
+         * @return collection of name of namespaces containing retrieved entries. May be empty if no
+         *     data was retrieved.
+         */
+        @NonNull Collection<String> getNamespaces();
+
+        /**
+         * Get the names of all requested entries in a name space.
+         *
+         * <p>This includes the name of entries that wasn't successfully retrieved.
+         *
+         * @param namespaceName the namespace name to get entries for.
+         * @return A collection of names for the given namespace or the empty collection if no
+         *   entries was returned for the given name space.
+         */
+        @NonNull Collection<String> getEntryNames(@NonNull String namespaceName);
+
+        /**
+         * Get the names of all entries that was successfully retrieved from a name space.
+         *
+         * <p>This only return entries for which {@link #getStatus(String, String)} will return
+         * {@link #STATUS_OK}.
+         *
+         * @param namespaceName the namespace name to get entries for.
+         * @return The entries in the given namespace that were successfully rerieved or the
+         *   empty collection if no entries was returned for the given name space.
+         */
+        @NonNull Collection<String> getRetrievedEntryNames(@NonNull String namespaceName);
+
+        /**
+         * Gets the status of an entry.
+         *
+         * <p>This returns {@link #STATUS_OK} if the value was retrieved, {@link
+         * #STATUS_NO_SUCH_ENTRY} if the given entry wasn't retrieved, {@link
+         * #STATUS_NOT_REQUESTED} if it wasn't requested, {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if
+         * the request message was set but the entry wasn't present in the request message, {@link
+         * #STATUS_USER_AUTHENTICATION_FAILED} if the value wasn't retrieved because the necessary
+         * user authentication wasn't performed, {@link #STATUS_READER_AUTHENTICATION_FAILED} if
+         * the supplied reader certificate chain didn't match the set of certificates the entry was
+         * provisioned with, or {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was
+         * configured without any access control profiles.
+         *
+         * @param namespaceName the namespace name of the entry.
+         * @param name the name of the entry to get the value for.
+         * @return the status indicating whether the value was retrieved and if not, why.
+         */
+        @Status int getStatus(@NonNull String namespaceName, @NonNull String name);
+
+        /**
+         * Gets the raw CBOR data for the value of an entry.
+         *
+         * <p>This should only be called on an entry for which the {@link #getStatus(String,
+         * String)} method returns {@link #STATUS_OK}.
+         *
+         * @param namespaceName the namespace name of the entry.
+         * @param name the name of the entry to get the value for.
+         * @return the raw CBOR data or {@code null} if no entry with the given name exists.
+         */
+        @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name);
+
+        /**
+         * The type of the entry status.
+         * @hide
+         */
+        @Retention(SOURCE)
+        @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED,
+                    STATUS_NOT_IN_REQUEST_MESSAGE, STATUS_USER_AUTHENTICATION_FAILED,
+                    STATUS_READER_AUTHENTICATION_FAILED, STATUS_NO_ACCESS_CONTROL_PROFILES})
+                    @interface Status {}
+    }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreCredentialDataResult.java b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
new file mode 100644
index 0000000..7afe3d4
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+class CredstoreCredentialDataResult extends CredentialDataResult {
+
+    ResultData mDeviceSignedResult;
+    ResultData mIssuerSignedResult;
+    CredstoreEntries mDeviceSignedEntries;
+    CredstoreEntries mIssuerSignedEntries;
+
+    CredstoreCredentialDataResult(ResultData deviceSignedResult, ResultData issuerSignedResult) {
+        mDeviceSignedResult = deviceSignedResult;
+        mIssuerSignedResult = issuerSignedResult;
+        mDeviceSignedEntries = new CredstoreEntries(deviceSignedResult);
+        mIssuerSignedEntries = new CredstoreEntries(issuerSignedResult);
+    }
+
+    @Override
+    public @NonNull byte[] getDeviceNameSpaces() {
+        return mDeviceSignedResult.getAuthenticatedData();
+    }
+
+    @Override
+    public @Nullable byte[] getDeviceMac() {
+        return mDeviceSignedResult.getMessageAuthenticationCode();
+    }
+
+    @Override
+    public @NonNull byte[] getStaticAuthenticationData() {
+        return mDeviceSignedResult.getStaticAuthenticationData();
+    }
+
+    @Override
+    public @NonNull CredentialDataResult.Entries getDeviceSignedEntries() {
+        return mDeviceSignedEntries;
+    }
+
+    @Override
+    public @NonNull CredentialDataResult.Entries getIssuerSignedEntries() {
+        return mIssuerSignedEntries;
+    }
+
+    static class CredstoreEntries implements CredentialDataResult.Entries {
+        ResultData mResultData;
+
+        CredstoreEntries(ResultData resultData) {
+            mResultData = resultData;
+        }
+
+        @Override
+        public @NonNull Collection<String> getNamespaces() {
+            return mResultData.getNamespaces();
+        }
+
+        @Override
+        public @NonNull Collection<String> getEntryNames(@NonNull String namespaceName) {
+            Collection<String> ret = mResultData.getEntryNames(namespaceName);
+            if (ret == null) {
+                ret = new LinkedList<String>();
+            }
+            return ret;
+        }
+
+        @Override
+        public @NonNull Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) {
+            Collection<String> ret = mResultData.getRetrievedEntryNames(namespaceName);
+            if (ret == null) {
+                ret = new LinkedList<String>();
+            }
+            return ret;
+        }
+
+        @Override
+        @Status
+        public int getStatus(@NonNull String namespaceName, @NonNull String name) {
+            return mResultData.getStatus(namespaceName, name);
+        }
+
+        @Override
+        public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) {
+            return mResultData.getEntry(namespaceName, name);
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index 6398cee..8e01105 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -58,14 +58,17 @@
     private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
     private Context mContext;
     private ICredential mBinder;
+    private CredstorePresentationSession mSession;
 
     CredstoreIdentityCredential(Context context, String credentialName,
             @IdentityCredentialStore.Ciphersuite int cipherSuite,
-            ICredential binder) {
+            ICredential binder,
+            @Nullable CredstorePresentationSession session) {
         mContext = context;
         mCredentialName = credentialName;
         mCipherSuite = cipherSuite;
         mBinder = binder;
+        mSession = session;
     }
 
     private KeyPair mEphemeralKeyPair = null;
@@ -239,6 +242,7 @@
 
     private boolean mAllowUsingExhaustedKeys = true;
     private boolean mAllowUsingExpiredKeys = false;
+    private boolean mIncrementKeyUsageCount = true;
 
     @Override
     public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
@@ -250,6 +254,11 @@
         mAllowUsingExpiredKeys = allowUsingExpiredKeys;
     }
 
+    @Override
+    public void setIncrementKeyUsageCount(boolean incrementKeyUsageCount) {
+        mIncrementKeyUsageCount = incrementKeyUsageCount;
+    }
+
     private boolean mOperationHandleSet = false;
     private long mOperationHandle = 0;
 
@@ -264,7 +273,8 @@
         if (!mOperationHandleSet) {
             try {
                 mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys,
-                        mAllowUsingExpiredKeys);
+                                                         mAllowUsingExpiredKeys,
+                                                         mIncrementKeyUsageCount);
                 mOperationHandleSet = true;
             } catch (android.os.RemoteException e) {
                 throw new RuntimeException("Unexpected RemoteException ", e);
@@ -315,7 +325,8 @@
                 sessionTranscript != null ? sessionTranscript : new byte[0],
                 readerSignature != null ? readerSignature : new byte[0],
                 mAllowUsingExhaustedKeys,
-                mAllowUsingExpiredKeys);
+                mAllowUsingExpiredKeys,
+                mIncrementKeyUsageCount);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index d8d4742..fb0880c 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -126,7 +126,8 @@
             ICredential credstoreCredential;
             credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
             return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
-                    credstoreCredential);
+                    credstoreCredential,
+                    null);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
@@ -162,4 +163,23 @@
                     + e.errorCode, e);
         }
     }
+
+    @Override
+    public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite)
+            throws CipherSuiteNotSupportedException {
+        try {
+            ISession credstoreSession = mStore.createPresentationSession(cipherSuite);
+            return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
+                throw new CipherSuiteNotSupportedException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
 }
diff --git a/identity/java/android/security/identity/CredstorePresentationSession.java b/identity/java/android/security/identity/CredstorePresentationSession.java
new file mode 100644
index 0000000..e3c6689
--- /dev/null
+++ b/identity/java/android/security/identity/CredstorePresentationSession.java
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+class CredstorePresentationSession extends PresentationSession {
+    private static final String TAG = "CredstorePresentationSession";
+
+    private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
+    private Context mContext;
+    private CredstoreIdentityCredentialStore mStore;
+    private ISession mBinder;
+    private Map<String, CredstoreIdentityCredential> mCredentialCache = new LinkedHashMap<>();
+    private KeyPair mEphemeralKeyPair = null;
+    private byte[] mSessionTranscript = null;
+    private boolean mOperationHandleSet = false;
+    private long mOperationHandle = 0;
+
+    CredstorePresentationSession(Context context,
+            @IdentityCredentialStore.Ciphersuite int cipherSuite,
+            CredstoreIdentityCredentialStore store,
+            ISession binder) {
+        mContext = context;
+        mCipherSuite = cipherSuite;
+        mStore = store;
+        mBinder = binder;
+    }
+
+    private void ensureEphemeralKeyPair() {
+        if (mEphemeralKeyPair != null) {
+            return;
+        }
+        try {
+            // This PKCS#12 blob is generated in credstore, using BoringSSL.
+            //
+            // The main reason for this convoluted approach and not just sending the decomposed
+            // key-pair is that this would require directly using (device-side) BouncyCastle which
+            // is tricky due to various API hiding efforts. So instead we have credstore generate
+            // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL
+            // doesn't support not using encryption when building a PKCS#12 blob).
+            //
+            byte[] pkcs12 = mBinder.getEphemeralKeyPair();
+            String alias = "ephemeralKey";
+            char[] password = {};
+
+            KeyStore ks = KeyStore.getInstance("PKCS12");
+            ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12);
+            ks.load(bais, password);
+            PrivateKey privKey = (PrivateKey) ks.getKey(alias, password);
+
+            Certificate cert = ks.getCertificate(alias);
+            PublicKey pubKey = cert.getPublicKey();
+
+            mEphemeralKeyPair = new KeyPair(pubKey, privKey);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        } catch (android.os.RemoteException
+                | KeyStoreException
+                | CertificateException
+                | UnrecoverableKeyException
+                | NoSuchAlgorithmException
+                | IOException e) {
+            throw new RuntimeException("Unexpected exception ", e);
+        }
+    }
+
+    @Override
+    public @NonNull KeyPair getEphemeralKeyPair() {
+        ensureEphemeralKeyPair();
+        return mEphemeralKeyPair;
+    }
+
+    @Override
+    public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+            throws InvalidKeyException {
+        try {
+            byte[] uncompressedForm =
+                    Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey);
+            mBinder.setReaderEphemeralPublicKey(uncompressedForm);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public void setSessionTranscript(@NonNull byte[] sessionTranscript) {
+        try {
+            mBinder.setSessionTranscript(sessionTranscript);
+            mSessionTranscript = sessionTranscript;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public @Nullable CredentialDataResult getCredentialData(@NonNull String credentialName,
+                                                            @NonNull CredentialDataRequest request)
+            throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException,
+            InvalidRequestMessageException, EphemeralPublicKeyNotFoundException {
+        try {
+            // Cache the IdentityCredential to satisfy the property that AuthKey usage counts are
+            // incremented on only the _first_ getCredentialData() call.
+            //
+            CredstoreIdentityCredential credential = mCredentialCache.get(credentialName);
+            if (credential == null) {
+                ICredential credstoreCredential =
+                    mBinder.getCredentialForPresentation(credentialName);
+                credential = new CredstoreIdentityCredential(mContext, credentialName,
+                                                             mCipherSuite, credstoreCredential,
+                                                             this);
+                mCredentialCache.put(credentialName, credential);
+
+                credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys());
+                credential.setAllowUsingExpiredKeys(request.isAllowUsingExpiredKeys());
+                credential.setIncrementKeyUsageCount(request.isIncrementUseCount());
+            }
+
+            ResultData deviceSignedResult = credential.getEntries(
+                    request.getRequestMessage(),
+                    request.getDeviceSignedEntriesToRequest(),
+                    mSessionTranscript,
+                    request.getReaderSignature());
+
+            // By design this second getEntries() call consumes the same auth-key.
+
+            ResultData issuerSignedResult = credential.getEntries(
+                    request.getRequestMessage(),
+                    request.getIssuerSignedEntriesToRequest(),
+                    mSessionTranscript,
+                    request.getReaderSignature());
+
+            return new CredstoreCredentialDataResult(deviceSignedResult, issuerSignedResult);
+
+        } catch (SessionTranscriptMismatchException e) {
+            throw new RuntimeException("Unexpected ", e);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
+                return null;
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    /**
+     * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+     * operation handle.
+     *
+     * @hide
+     */
+    @Override
+    public long getCredstoreOperationHandle() {
+        if (!mOperationHandleSet) {
+            try {
+                mOperationHandle = mBinder.getAuthChallenge();
+                mOperationHandleSet = true;
+            } catch (android.os.RemoteException e) {
+                throw new RuntimeException("Unexpected RemoteException ", e);
+            } catch (android.os.ServiceSpecificException e) {
+                if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
+                    // The NoAuthenticationKeyAvailableException will be thrown when
+                    // the caller proceeds to call getEntries().
+                }
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+        return mOperationHandle;
+    }
+
+}
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 1e68585..cdf746f 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -48,7 +48,9 @@
      * encryption".
      *
      * @return ephemeral key pair to use to establish a secure channel with a reader.
+     * @deprecated Use {@link PresentationSession} instead.
      */
+    @Deprecated
     public @NonNull abstract KeyPair createEphemeralKeyPair();
 
     /**
@@ -58,7 +60,9 @@
      * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
      *                                 establish a secure session.
      * @throws InvalidKeyException if the given key is invalid.
+     * @deprecated Use {@link PresentationSession} instead.
      */
+    @Deprecated
     public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
             throws InvalidKeyException;
 
@@ -72,7 +76,10 @@
      *
      * @param messagePlaintext unencrypted message to encrypt.
      * @return encrypted message.
+     * @deprecated Applications should use {@link PresentationSession} and
+     *             implement encryption/decryption themselves.
      */
+    @Deprecated
     public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext);
 
     /**
@@ -86,7 +93,10 @@
      * @param messageCiphertext encrypted message to decrypt.
      * @return decrypted message.
      * @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
+     * @deprecated Applications should use {@link PresentationSession} and
+     *             implement encryption/decryption themselves.
      */
+    @Deprecated
     public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
             throws MessageDecryptionException;
 
@@ -111,7 +121,9 @@
      *
      * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count
      *                                has been exceeded if no other key is available.
+     * @deprecated Use {@link PresentationSession} instead.
      */
+    @Deprecated
     public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
 
     /**
@@ -128,12 +140,36 @@
      *
      * @param allowUsingExpiredKeys whether to allow using an authentication key which use count
      *                              has been exceeded if no other key is available.
+     * @deprecated Use {@link PresentationSession} instead.
      */
+    @Deprecated
     public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
         throw new UnsupportedOperationException();
     }
 
     /**
+     * @hide
+     *
+     * Sets whether the usage count of an authentication key should be increased. This must be
+     * called prior to calling
+     * {@link #getEntries(byte[], Map, byte[], byte[])} or using a
+     * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this object.
+     *
+     * <p>By default this is set to true.
+     *
+     * <p>This is only implemented in feature version 202201 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param incrementKeyUsageCount whether the usage count of the key should be increased.
+     * @deprecated Use {@link PresentationSession} instead.
+     */
+    public void setIncrementKeyUsageCount(boolean incrementKeyUsageCount) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
      * operation handle.
      *
@@ -149,15 +185,19 @@
      * by using the {@link ResultData#getStatus(String, String)} method on each of the requested
      * entries.
      *
-     * <p>It is the responsibility of the calling application to know if authentication is needed
-     * and use e.g. {@link android.hardware.biometrics.BiometricPrompt} to make the user
-     * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
-     * references this object. If needed, this must be done before calling
-     * {@link #getEntries(byte[], Map, byte[], byte[])}.
-     *
      * <p>It is permissible to call this method multiple times using the same instance but if this
      * is done, the {@code sessionTranscript} parameter must be identical for each call. If this is
      * not the case, the {@link SessionTranscriptMismatchException} exception is thrown.
+     * Additionally, if this is done the same auth-key will be used.
+     *
+     * <p>The application should not make any assumptions on whether user authentication is needed.
+     * Instead, the application should request the data elements values first and then examine
+     * the returned {@link ResultData}. If {@link ResultData#STATUS_USER_AUTHENTICATION_FAILED}
+     * is returned the application should get a
+     * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
+     * object and use it with a {@link android.hardware.biometrics.BiometricPrompt}. Upon successful
+     * authentication the application may call {@link #getEntries(byte[], Map, byte[], byte[])}
+     * again.
      *
      * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
      * from the verifier. The content can be defined in the way appropriate for the credential, but
@@ -269,7 +309,9 @@
      * @throws InvalidRequestMessageException         if the requestMessage is malformed.
      * @throws EphemeralPublicKeyNotFoundException    if the ephemeral public key was not found in
      *                                                the session transcript.
+     * @deprecated Use {@link PresentationSession} instead.
      */
+    @Deprecated
     public abstract @NonNull ResultData getEntries(
             @Nullable byte[] requestMessage,
             @NonNull Map<String, Collection<String>> entriesToRequest,
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
index 6ccd0e8..dbb8aaa 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -209,6 +209,25 @@
     @Deprecated
     public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
 
+    /**
+     * Creates a new presentation session.
+     *
+     * <p>This method gets an object to be used for interaction with a remote verifier for
+     * presentation of one or more credentials.
+     *
+     * <p>This is only implemented in feature version 202201 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param cipherSuite    the cipher suite to use for communicating with the verifier.
+     * @return The presentation session.
+     */
+    public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite)
+            throws CipherSuiteNotSupportedException {
+        throw new UnsupportedOperationException();
+    }
+
     /** @hide */
     @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256})
     @Retention(RetentionPolicy.SOURCE)
diff --git a/identity/java/android/security/identity/PresentationSession.java b/identity/java/android/security/identity/PresentationSession.java
new file mode 100644
index 0000000..afaafce
--- /dev/null
+++ b/identity/java/android/security/identity/PresentationSession.java
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+
+/**
+ * Class for presenting multiple documents to a remote verifier.
+ *
+ * Use {@link IdentityCredentialStore#createPresentationSession(int)} to create a {@link
+ * PresentationSession} instance.
+ */
+public abstract class PresentationSession {
+    /**
+     * @hide
+     */
+    protected PresentationSession() {}
+
+    /**
+     * Gets the ephemeral key pair to use to establish a secure channel with the verifier.
+     *
+     * <p>Applications should use this key-pair for the communications channel with the verifier
+     * using a protocol / cipher-suite appropriate for the application. One example of such a
+     * protocol is the one used for Mobile Driving Licenses, see ISO 18013-5.
+     *
+     * <p>The ephemeral key pair is tied to the {@link PresentationSession} instance so subsequent
+     * calls to this method will return the same key-pair.
+     *
+     * @return ephemeral key pair to use to establish a secure channel with a reader.
+     */
+    public @NonNull abstract KeyPair getEphemeralKeyPair();
+
+    /**
+     * Set the ephemeral public key provided by the verifier.
+     *
+     * <p>If called, this must be called before any calls to
+     * {@link #getCredentialData(String, CredentialDataRequest)}.
+     *
+     * <p>This method can only be called once per {@link PresentationSession} instance.
+     *
+     * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
+     *                                 establish a secure session.
+     * @throws InvalidKeyException if the given key is invalid.
+     */
+    public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+            throws InvalidKeyException;
+
+    /**
+     * Set the session transcript.
+     *
+     * <p>If called, this must be called before any calls to
+     * {@link #getCredentialData(String, CredentialDataRequest)}.
+     *
+     * <p>The X and Y coordinates of the public part of the key-pair returned by {@link
+     * #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR.  Each of
+     * these coordinates must appear encoded with the most significant bits first and use the exact
+     * amount of bits indicated by the key size of the ephemeral keys. For example, if the
+     * ephemeral key is using the P-256 curve then the 32 bytes for the X coordinate encoded with
+     * the most significant bits first must appear somewhere and ditto for the 32 bytes for the Y
+     * coordinate.
+     *
+     * <p>This method can only be called once per {@link PresentationSession} instance.
+     *
+     * @param sessionTranscript the session transcript.
+     */
+    public abstract void setSessionTranscript(@NonNull byte[] sessionTranscript);
+
+    /**
+     * Retrieves data from a named credential in the current presentation session.
+     *
+     * <p>If an access control check fails for one of the requested entries or if the entry
+     * doesn't exist, the entry is simply not returned. The application can detect this
+     * by using the {@link CredentialDataResult.Entries#getStatus(String, String)} method on
+     * each of the requested entries.
+     *
+     * <p>The application should not make any assumptions on whether user authentication is needed.
+     * Instead, the application should request the data elements values first and then examine
+     * the returned {@link CredentialDataResult.Entries}. If
+     * {@link CredentialDataResult.Entries#STATUS_USER_AUTHENTICATION_FAILED} is returned the
+     * application should get a
+     * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
+     * object and use it with a {@link android.hardware.biometrics.BiometricPrompt}. Upon successful
+     * authentication the application may call
+     * {@link #getCredentialData(String, CredentialDataRequest)} again.
+     *
+     * <p>It is permissible to call this method multiple times using the same credential name.
+     * If this is done the same auth-key will be used.
+     *
+     * <p>If the reader signature is set in the request parameter (via the
+     * {@link CredentialDataRequest.Builder#setReaderSignature(byte[])} method) it must contain
+     * the bytes of a {@code COSE_Sign1} structure as defined in RFC 8152. For the payload
+     * {@code nil} shall be used and the detached payload is the {@code ReaderAuthenticationBytes}
+     * CBOR described below.
+     * <pre>
+     *     ReaderAuthentication = [
+     *       "ReaderAuthentication",
+     *       SessionTranscript,
+     *       ItemsRequestBytes
+     *     ]
+     *
+     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+     *
+     *     ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+     * </pre>
+     *
+     * <p>where {@code ItemsRequestBytes} are the bytes of the request message set in
+     * the request parameter (via the
+     * {@link CredentialDataRequest.Builder#setRequestMessage(byte[])} method).
+     *
+     * <p>The public key corresponding to the key used to make the signature, can be found in the
+     * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as
+     * described in
+     * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-08">draft-ietf-cose-x509-08</a>).
+     * There will be at least one certificate in said element and there may be more (and if so,
+     * each certificate must be signed by its successor).
+     *
+     * <p>Data elements protected by reader authentication are returned if, and only if,
+     * {@code requestMessage} is signed by the top-most certificate in the reader's certificate
+     * chain, and the data element is configured with an {@link AccessControlProfile} configured
+     * with an X.509 certificate for a key which appear in the certificate chain.
+     *
+     * <p>Note that the request message CBOR is used only for enforcing reader authentication, it's
+     * not used for determining which entries this API will return. The application is expected to
+     * have parsed the request message and filtered it according to user preference and/or consent.
+     *
+     * @param credentialName the name of the credential to retrieve.
+     * @param request the data to retrieve from the credential
+     * @return If the credential wasn't found, returns null. Otherwise a
+     *         {@link CredentialDataResult} object containing entry data organized by namespace and
+     *         a cryptographically authenticated representation of the same data, bound to the
+     *         current session.
+     * @throws NoAuthenticationKeyAvailableException  if authentication keys were never
+     *                                                provisioned for the credential or if they
+     *                                                are expired or exhausted their use-count.
+     * @throws InvalidRequestMessageException         if the requestMessage is malformed.
+     * @throws InvalidReaderSignatureException        if the reader signature is invalid, or it
+     *                                                doesn't contain a certificate chain, or if
+     *                                                the signature failed to validate.
+     * @throws EphemeralPublicKeyNotFoundException    if the ephemeral public key was not found in
+     *                                                the session transcript.
+     */
+    public abstract @Nullable CredentialDataResult getCredentialData(
+            @NonNull String credentialName, @NonNull CredentialDataRequest request)
+            throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException,
+            InvalidRequestMessageException, EphemeralPublicKeyNotFoundException;
+
+    /**
+     * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+     * operation handle.
+     *
+     * @hide
+     */
+    public abstract long getCredstoreOperationHandle();
+}
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 71860d2..d46f985 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -28,7 +28,10 @@
 /**
  * An object that contains the result of retrieving data from a credential. This is used to return
  * data requested from a {@link IdentityCredential}.
+ *
+ * @deprecated Use {@link PresentationSession} instead.
  */
+@Deprecated
 public abstract class ResultData {
 
     /** Value was successfully retrieved. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 8e6c05d..eda09e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -62,6 +62,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -132,6 +133,8 @@
         final Rect fullscreenHitRegion = new Rect(displayRegion);
         final boolean inLandscape = mSession.displayLayout.isLandscape();
         final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
+        final float dividerWidth = mContext.getResources().getDimensionPixelSize(
+                R.dimen.split_divider_bar_width);
         // We allow splitting if we are already in split-screen or the running task is a standard
         // task in fullscreen mode.
         final boolean allowSplit = inSplitScreen
@@ -153,8 +156,11 @@
 
                 // If we have existing split regions use those bounds, otherwise split it 50/50
                 if (inSplitScreen) {
+                    // Add the divider bounds to each side since that counts for the hit region.
                     leftHitRegion.set(topOrLeftBounds);
+                    leftHitRegion.right += dividerWidth / 2;
                     rightHitRegion.set(bottomOrRightBounds);
+                    rightHitRegion.left -= dividerWidth / 2;
                 } else {
                     displayRegion.splitVertically(leftHitRegion, rightHitRegion);
                 }
@@ -170,8 +176,11 @@
 
                 // If we have existing split regions use those bounds, otherwise split it 50/50
                 if (inSplitScreen) {
+                    // Add the divider bounds to each side since that counts for the hit region.
                     topHitRegion.set(topOrLeftBounds);
+                    topHitRegion.bottom += dividerWidth / 2;
                     bottomHitRegion.set(bottomOrRightBounds);
+                    bottomHitRegion.top -= dividerWidth / 2;
                 } else {
                     displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 73f65b0..f8902c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -269,6 +269,8 @@
         // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
         // flag because we do know that the next window will take input
         // focus, so we want to get the IME window up on top of us right away.
+        // Touches will only pass through to the host activity window and will be blocked from
+        // passing to any other windows.
         windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -276,9 +278,6 @@
         params.token = appToken;
         params.packageName = activityInfo.packageName;
         params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        // Setting as trusted overlay to let touches pass through. This is safe because this
-        // window is controlled by the system.
-        params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
         if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
             params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 2c02d2c..fb1004b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
@@ -41,7 +40,6 @@
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
 import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -112,11 +110,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 7d7add4..264d482 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.resizeSplitScreen
 import com.android.server.wm.flicker.helpers.setRotation
@@ -45,7 +44,6 @@
 import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.helpers.SimpleAppHelper
 import com.android.wm.shell.flicker.testapp.Components
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -137,11 +135,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Test
     fun topAppLayerIsAlwaysVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
index 5678899..d703ea0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -35,7 +34,6 @@
 import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -79,11 +77,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
index c2edf9d..6b18839 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -35,7 +34,6 @@
 import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -78,11 +76,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
index 777998c..acd658b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
@@ -37,7 +36,6 @@
 import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -88,11 +86,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
index 914b11d..b40be8b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
@@ -37,7 +36,6 @@
 import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -93,11 +91,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @FlakyTest
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index d3bb008..2deff7b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -26,9 +26,7 @@
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Rule
 import org.junit.Test
@@ -93,11 +91,7 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /**
      * Checks [pipApp] window remains visible throughout the animation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index fa9fbcd..0e73463 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.traces.common.FlickerComponentName
@@ -36,7 +35,6 @@
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
 import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
 import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -121,11 +119,7 @@
      */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     /**
      * Checks that all parts of the screen are covered at the start and end of the transition
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index f8a3aff..990872f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,9 +18,7 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import org.junit.Assume.assumeFalse
 import org.junit.Test
 
 /**
@@ -29,15 +27,6 @@
 abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     protected val testApp = FixedAppHelper(instrumentation)
 
-    /** {@inheritDoc}  */
-    @Presubmit
-    @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
-
     /**
      * Checks that the pip app window remains inside the display bounds throughout the whole
      * animation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 2231d88..2c08b7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -24,8 +24,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -82,11 +80,7 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index fcac2c7..e340f4c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -24,9 +24,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Rule
 import org.junit.Test
@@ -101,11 +99,7 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 8e6603b..c036515 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -24,9 +24,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -81,11 +79,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index f9e180e..e415024 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -24,8 +24,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -85,11 +83,7 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index b7bfa1b..4a4c46c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -25,8 +25,6 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.traces.region.RegionSubject
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -85,11 +83,7 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     companion object {
         /**
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/location/java/android/location/GnssAutomaticGainControl.aidl
similarity index 82%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to location/java/android/location/GnssAutomaticGainControl.aidl
index 6041460..8298cb71 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/location/java/android/location/GnssAutomaticGainControl.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv.interactive;
+package android.location;
 
-parcelable TvIAppInfo;
\ No newline at end of file
+parcelable GnssAutomaticGainControl;
diff --git a/location/java/android/location/GnssAutomaticGainControl.java b/location/java/android/location/GnssAutomaticGainControl.java
new file mode 100644
index 0000000..e4f7304
--- /dev/null
+++ b/location/java/android/location/GnssAutomaticGainControl.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * A class that contains GNSS Automatic Gain Control (AGC) information.
+ *
+ * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
+ * level may be used to indicate potential interference. Higher gain (and/or lower input power)
+ * shall be output as a positive number. Hence in cases of strong jamming, in the band of this
+ * signal, this value will go more negative. This value must be consistent given the same level
+ * of the incoming signal power.
+ *
+ * <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
+ * components) may also affect the typical output of this value on any given hardware design
+ * in an open sky test - the important aspect of this output is that changes in this value are
+ * indicative of changes on input signal power in the frequency band for this measurement.
+ */
+public final class GnssAutomaticGainControl implements Parcelable {
+    private final double mLevelDb;
+    private final int mConstellationType;
+    private final long mCarrierFrequencyHz;
+
+    /**
+     * Creates a {@link GnssAutomaticGainControl} with a full list of parameters.
+     */
+    private GnssAutomaticGainControl(double levelDb, int constellationType,
+            long carrierFrequencyHz) {
+        mLevelDb = levelDb;
+        mConstellationType = constellationType;
+        mCarrierFrequencyHz = carrierFrequencyHz;
+    }
+
+    /**
+     * Gets the Automatic Gain Control level in dB.
+     */
+    @FloatRange(from = -10000, to = 10000)
+    public double getLevelDb() {
+        return mLevelDb;
+    }
+
+    /**
+     * Gets the constellation type.
+     *
+     * <p>The return value is one of those constants with {@code CONSTELLATION_} prefix in
+     * {@link GnssStatus}.
+     */
+    @GnssStatus.ConstellationType
+    public int getConstellationType() {
+        return mConstellationType;
+    }
+
+    /**
+     * Gets the carrier frequency of the tracked signal.
+     *
+     * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
+     * L5 = 1176.45 MHz, varying GLO channels, etc.
+     *
+     * @return the carrier frequency of the signal tracked in Hz.
+     */
+    @IntRange(from = 0)
+    public long getCarrierFrequencyHz() {
+        return mCarrierFrequencyHz;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flag) {
+        parcel.writeDouble(mLevelDb);
+        parcel.writeInt(mConstellationType);
+        parcel.writeLong(mCarrierFrequencyHz);
+    }
+
+    @NonNull
+    public static final Creator<GnssAutomaticGainControl> CREATOR =
+            new Creator<GnssAutomaticGainControl>() {
+                @Override
+                @NonNull
+                public GnssAutomaticGainControl createFromParcel(@NonNull Parcel parcel) {
+                    return new GnssAutomaticGainControl(parcel.readDouble(), parcel.readInt(),
+                            parcel.readLong());
+                }
+
+                @Override
+                public GnssAutomaticGainControl[] newArray(int i) {
+                    return new GnssAutomaticGainControl[i];
+                }
+            };
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        s.append("GnssAutomaticGainControl[");
+        s.append("Level=").append(mLevelDb).append(" dB");
+        s.append(" Constellation=").append(
+                GnssStatus.constellationTypeToString(mConstellationType));
+        s.append(" CarrierFrequency=").append(mCarrierFrequencyHz).append(" Hz");
+        s.append(']');
+        return s.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof GnssAutomaticGainControl)) {
+            return false;
+        }
+
+        GnssAutomaticGainControl other = (GnssAutomaticGainControl) obj;
+        if (Double.compare(mLevelDb, other.mLevelDb)
+                != 0) {
+            return false;
+        }
+        if (mConstellationType != other.mConstellationType) {
+            return false;
+        }
+        if (mCarrierFrequencyHz != other.mCarrierFrequencyHz) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mLevelDb, mConstellationType, mCarrierFrequencyHz);
+    }
+
+    /** Builder for {@link GnssAutomaticGainControl} */
+    public static final class Builder {
+        private double mLevelDb;
+        private int mConstellationType;
+        private long mCarrierFrequencyHz;
+
+        /**
+         * Constructs a {@link GnssAutomaticGainControl.Builder} instance.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructs a {@link GnssAutomaticGainControl.Builder} instance by copying a
+         * {@link GnssAutomaticGainControl}.
+         */
+        public Builder(@NonNull GnssAutomaticGainControl agc) {
+            mLevelDb = agc.getLevelDb();
+            mConstellationType = agc.getConstellationType();
+            mCarrierFrequencyHz = agc.getCarrierFrequencyHz();
+        }
+
+        /**
+         * Sets the Automatic Gain Control level in dB.
+         */
+        @NonNull
+        public Builder setLevelDb(@FloatRange(from = -10000, to = 10000) double levelDb) {
+            Preconditions.checkArgument(levelDb >= -10000 && levelDb <= 10000);
+            mLevelDb = levelDb;
+            return this;
+        }
+
+        /**
+         * Sets the constellation type.
+         */
+        @NonNull
+        public Builder setConstellationType(@GnssStatus.ConstellationType int constellationType) {
+            mConstellationType = constellationType;
+            return this;
+        }
+
+        /**
+         * Sets the Carrier frequency in Hz.
+         */
+        @NonNull public Builder setCarrierFrequencyHz(@IntRange(from = 0) long carrierFrequencyHz) {
+            Preconditions.checkArgumentNonnegative(carrierFrequencyHz);
+            mCarrierFrequencyHz = carrierFrequencyHz;
+            return this;
+        }
+
+        /** Builds a {@link GnssAutomaticGainControl} instance as specified by this builder. */
+        @NonNull
+        public GnssAutomaticGainControl build() {
+            return new GnssAutomaticGainControl(mLevelDb, mConstellationType, mCarrierFrequencyHz);
+        }
+    }
+}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index ecdd4b6..cdfa02c 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1381,7 +1381,10 @@
     /**
      * Returns {@code true} if {@link #getAutomaticGainControlLevelDb()} is available,
      * {@code false} otherwise.
+     *
+     * @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead.
      */
+    @Deprecated
     public boolean hasAutomaticGainControlLevelDb() {
         return isFlagSet(HAS_AUTOMATIC_GAIN_CONTROL);
     }
@@ -1401,7 +1404,10 @@
      * indicative of changes on input signal power in the frequency band for this measurement.
      *
      * <p> The value is only available if {@link #hasAutomaticGainControlLevelDb()} is {@code true}
+     *
+     * @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead.
      */
+    @Deprecated
     public double getAutomaticGainControlLevelDb() {
         return mAutomaticGainControlLevelInDb;
     }
@@ -1409,7 +1415,9 @@
     /**
      * Sets the Automatic Gain Control level in dB.
      * @hide
+     * @deprecated Use {@link GnssMeasurementsEvent.Builder#setGnssAutomaticGainControls()} instead.
      */
+    @Deprecated
     @TestApi
     public void setAutomaticGainControlLevelInDb(double agcLevelDb) {
         setFlag(HAS_AUTOMATIC_GAIN_CONTROL);
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index a07a64a..075ddeb 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -18,16 +18,19 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.TestApi;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.security.InvalidParameterException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 
 /**
  * A class implementing a container for data associated with a measurement event.
@@ -35,7 +38,8 @@
  */
 public final class GnssMeasurementsEvent implements Parcelable {
     private final GnssClock mClock;
-    private final Collection<GnssMeasurement> mReadOnlyMeasurements;
+    private final List<GnssMeasurement> mMeasurements;
+    private final List<GnssAutomaticGainControl> mGnssAgcs;
 
     /**
      * Used for receiving GNSS satellite measurements from the GNSS engine.
@@ -116,20 +120,13 @@
     }
 
     /**
-     * @hide
+     * Create a {@link GnssMeasurementsEvent} instance with a full list of parameters.
      */
-    @TestApi
-    public GnssMeasurementsEvent(GnssClock clock, GnssMeasurement[] measurements) {
-        if (clock == null) {
-            throw new InvalidParameterException("Parameter 'clock' must not be null.");
-        }
-        if (measurements == null || measurements.length == 0) {
-            mReadOnlyMeasurements = Collections.emptyList();
-        } else {
-            Collection<GnssMeasurement> measurementCollection = Arrays.asList(measurements);
-            mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection);
-        }
-
+    private GnssMeasurementsEvent(@NonNull GnssClock clock,
+            @NonNull List<GnssMeasurement> measurements,
+            @NonNull List<GnssAutomaticGainControl> agcs) {
+        mMeasurements = measurements;
+        mGnssAgcs = agcs;
         mClock = clock;
     }
 
@@ -143,26 +140,31 @@
     }
 
     /**
-     * Gets a read-only collection of measurements associated with the current event.
+     * Gets the collection of measurements associated with the current event.
      */
     @NonNull
     public Collection<GnssMeasurement> getMeasurements() {
-        return mReadOnlyMeasurements;
+        return mMeasurements;
+    }
+
+    /**
+     * Gets the collection of {@link GnssAutomaticGainControl} associated with the
+     * current event.
+     */
+    @NonNull
+    public Collection<GnssAutomaticGainControl> getGnssAutomaticGainControls() {
+        return mGnssAgcs;
     }
 
     public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
             new Creator<GnssMeasurementsEvent>() {
         @Override
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
-            ClassLoader classLoader = getClass().getClassLoader();
-
-            GnssClock clock = in.readParcelable(classLoader);
-
-            int measurementsLength = in.readInt();
-            GnssMeasurement[] measurementsArray = new GnssMeasurement[measurementsLength];
-            in.readTypedArray(measurementsArray, GnssMeasurement.CREATOR);
-
-            return new GnssMeasurementsEvent(clock, measurementsArray);
+            GnssClock clock = in.readParcelable(getClass().getClassLoader());
+            List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
+            List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
+                    GnssAutomaticGainControl.CREATOR);
+            return new GnssMeasurementsEvent(clock, measurements, agcs);
         }
 
         @Override
@@ -179,28 +181,105 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mClock, flags);
-
-        int measurementsCount = mReadOnlyMeasurements.size();
-        GnssMeasurement[] measurementsArray =
-                mReadOnlyMeasurements.toArray(new GnssMeasurement[measurementsCount]);
-        parcel.writeInt(measurementsArray.length);
-        parcel.writeTypedArray(measurementsArray, flags);
+        parcel.writeTypedList(mMeasurements);
+        parcel.writeTypedList(mGnssAgcs);
     }
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("[ GnssMeasurementsEvent:\n\n");
+        StringBuilder builder = new StringBuilder("GnssMeasurementsEvent[");
+        builder.append(mClock);
+        builder.append(' ').append(mMeasurements.toString());
+        builder.append(' ').append(mGnssAgcs.toString());
+        builder.append("]");
+        return builder.toString();
+    }
 
-        builder.append(mClock.toString());
-        builder.append("\n");
+    /** Builder for {@link GnssMeasurementsEvent} */
+    public static final class Builder {
+        private GnssClock mClock;
+        private List<GnssMeasurement> mMeasurements;
+        private List<GnssAutomaticGainControl> mGnssAgcs;
 
-        for (GnssMeasurement measurement : mReadOnlyMeasurements) {
-            builder.append(measurement.toString());
-            builder.append("\n");
+        /**
+         * Constructs a {@link GnssMeasurementsEvent.Builder} instance.
+         */
+        public Builder() {
+            mClock = new GnssClock();
+            mMeasurements = new ArrayList<>();
+            mGnssAgcs = new ArrayList<>();
         }
 
-        builder.append("]");
+        /**
+         * Constructs a {@link GnssMeasurementsEvent.Builder} instance by copying a
+         * {@link GnssMeasurementsEvent}.
+         */
+        public Builder(@NonNull GnssMeasurementsEvent event) {
+            mClock = event.getClock();
+            mMeasurements = (List<GnssMeasurement>) event.getMeasurements();
+            mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls();
+        }
 
-        return builder.toString();
+        /**
+         * Sets the {@link GnssClock}.
+         */
+        @NonNull
+        public Builder setClock(@NonNull GnssClock clock) {
+            Preconditions.checkNotNull(clock);
+            mClock = clock;
+            return this;
+        }
+
+        /**
+         * Sets the collection of {@link GnssMeasurement}.
+         *
+         * This API exists for JNI since it is easier for JNI to work with an array than a
+         * collection.
+         * @hide
+         */
+        @NonNull
+        public Builder setMeasurements(@Nullable GnssMeasurement... measurements) {
+            mMeasurements = measurements == null ? Collections.emptyList() : Arrays.asList(
+                    measurements);
+            return this;
+        }
+
+        /**
+         * Sets the collection of {@link GnssMeasurement}.
+         */
+        @NonNull
+        public Builder setMeasurements(@NonNull Collection<GnssMeasurement> measurements) {
+            mMeasurements = new ArrayList<>(measurements);
+            return this;
+        }
+
+        /**
+         * Sets the collection of {@link GnssAutomaticGainControl}.
+         *
+         * This API exists for JNI since it is easier for JNI to work with an array than a
+         * collection.
+         * @hide
+         */
+        @NonNull
+        public Builder setGnssAutomaticGainControls(@Nullable GnssAutomaticGainControl... agcs) {
+            mGnssAgcs = agcs == null ? Collections.emptyList() : Arrays.asList(agcs);
+            return this;
+        }
+
+        /**
+         * Sets the collection of {@link GnssAutomaticGainControl}.
+         */
+        @NonNull
+        public Builder setGnssAutomaticGainControls(
+                @NonNull Collection<GnssAutomaticGainControl> agcs) {
+            mGnssAgcs = new ArrayList<>(agcs);
+            return this;
+        }
+
+        /** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */
+        @NonNull
+        public GnssMeasurementsEvent build() {
+            return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs);
+        }
     }
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ffd5eaa..68e5d94 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -79,6 +79,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -5648,6 +5649,43 @@
     }
 
     /**
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * These are the devices anticipated to play sound from an {@link AudioTrack} created with
+     * the specified {@link AudioAttributes}.
+     * The audio routing can change if audio devices are physically connected or disconnected or
+     * concurrently through {@link AudioRouting} or {@link MediaRouter}.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of
+     * {@link AudioDeviceInfo} otherwise (typically one device, except for duplicated paths).
+     */
+    public @NonNull List<AudioDeviceInfo> getAudioDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        final List<AudioDeviceAttributes> devicesForAttributes;
+        try {
+            Objects.requireNonNull(attributes);
+            final IAudioService service = getService();
+            devicesForAttributes = service.getDevicesForAttributesUnprotected(attributes);
+        } catch (Exception e) {
+            Log.i(TAG, "No audio devices available for specified attributes.");
+            return Collections.emptyList();
+        }
+
+        // Map from AudioDeviceAttributes to AudioDeviceInfo
+        AudioDeviceInfo[] outputDeviceInfos = getDevicesStatic(GET_DEVICES_OUTPUTS);
+        List<AudioDeviceInfo> deviceInfosForAttributes = new ArrayList<>();
+        for (AudioDeviceAttributes deviceForAttributes : devicesForAttributes) {
+            for (AudioDeviceInfo deviceInfo : outputDeviceInfos) {
+                if (deviceForAttributes.getType() == deviceInfo.getType()
+                        && TextUtils.equals(deviceForAttributes.getAddress(),
+                                deviceInfo.getAddress())) {
+                    deviceInfosForAttributes.add(deviceInfo);
+                }
+            }
+        }
+        return Collections.unmodifiableList(deviceInfosForAttributes);
+    }
+
+    /**
      * @hide
      * Volume behavior for an audio device that has no particular volume behavior set. Invalid as
      * an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java
index 19ea2de..d1bb41e 100644
--- a/media/java/android/media/BtProfileConnectionInfo.java
+++ b/media/java/android/media/BtProfileConnectionInfo.java
@@ -34,7 +34,7 @@
     /** @hide */
     @IntDef({
             BluetoothProfile.A2DP,
-            BluetoothProfile.A2DP_SINK, // Can only be set by BtHelper
+            BluetoothProfile.A2DP_SINK,
             BluetoothProfile.HEADSET, // Can only be set by BtHelper
             BluetoothProfile.HEARING_AID,
             BluetoothProfile.LE_AUDIO,
@@ -105,6 +105,16 @@
     }
 
     /**
+     * Constructor for A2dp sink info
+     * The {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent.
+     *
+     * @param volume of device -1 to ignore value
+     */
+    public static @NonNull BtProfileConnectionInfo a2dpSinkInfo(int volume) {
+        return new BtProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false);
+    }
+
+    /**
      * Constructor for hearing aid info
      *
      * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7f6fb90..96199a9 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -308,6 +308,8 @@
 
     List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes);
 
+    List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes);
+
     int setAllowedCapturePolicy(in int capturePolicy);
 
     int getAllowedCapturePolicy();
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 69ce8d2..09d7fbd 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -1124,6 +1124,8 @@
     private class SurfaceImage extends android.media.Image {
         public SurfaceImage(int format) {
             mFormat = format;
+            mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat;
+            mDataSpace = ImageReader.this.mDataSpace;
         }
 
         SurfaceImage(int hardwareBufferFormat, long dataSpace) {
@@ -1229,6 +1231,12 @@
         }
 
         @Override
+        public long getDataSpace() {
+            throwISEIfImageIsInvalid();
+            return mDataSpace;
+        }
+
+        @Override
         public void setTimestamp(long timestampNs) {
             throwISEIfImageIsInvalid();
             mTimestamp = timestampNs;
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 85ad3cd..6ba9133 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -49,8 +49,10 @@
                             return StreamEventRequest.createFromParcelBody(source);
                         case TvInputManager.BROADCAST_INFO_TYPE_DSMCC:
                             return DsmccRequest.createFromParcelBody(source);
-                        case TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION:
+                        case TvInputManager.BROADCAST_INFO_TYPE_COMMAND:
                             return CommandRequest.createFromParcelBody(source);
+                        case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE:
+                            return TimelineRequest.createFromParcelBody(source);
                         default:
                             throw new IllegalStateException(
                                     "Unexpected broadcast info request type (value "
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index e423aba..67bdedc 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -50,8 +50,10 @@
                             return StreamEventResponse.createFromParcelBody(source);
                         case TvInputManager.BROADCAST_INFO_TYPE_DSMCC:
                             return DsmccResponse.createFromParcelBody(source);
-                        case TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION:
+                        case TvInputManager.BROADCAST_INFO_TYPE_COMMAND:
                             return CommandResponse.createFromParcelBody(source);
+                        case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE:
+                            return TimelineResponse.createFromParcelBody(source);
                         default:
                             throw new IllegalStateException(
                                     "Unexpected broadcast info response type (value "
diff --git a/media/java/android/media/tv/CommandRequest.java b/media/java/android/media/tv/CommandRequest.java
index 2391fa3..d61c858 100644
--- a/media/java/android/media/tv/CommandRequest.java
+++ b/media/java/android/media/tv/CommandRequest.java
@@ -23,7 +23,7 @@
 /** @hide */
 public final class CommandRequest extends BroadcastInfoRequest implements Parcelable {
     public static final @TvInputManager.BroadcastInfoType int requestType =
-            TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION;
+            TvInputManager.BROADCAST_INFO_TYPE_COMMAND;
 
     public static final @NonNull Parcelable.Creator<CommandRequest> CREATOR =
             new Parcelable.Creator<CommandRequest>() {
diff --git a/media/java/android/media/tv/CommandResponse.java b/media/java/android/media/tv/CommandResponse.java
index d34681f..af3d00c 100644
--- a/media/java/android/media/tv/CommandResponse.java
+++ b/media/java/android/media/tv/CommandResponse.java
@@ -23,7 +23,7 @@
 /** @hide */
 public final class CommandResponse extends BroadcastInfoResponse implements Parcelable {
     public static final @TvInputManager.BroadcastInfoType int responseType =
-            TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION;
+            TvInputManager.BROADCAST_INFO_TYPE_COMMAND;
 
     public static final @NonNull Parcelable.Creator<CommandResponse> CREATOR =
             new Parcelable.Creator<CommandResponse>() {
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
index e43d31a..4d49620 100644
--- a/media/java/android/media/tv/DsmccResponse.java
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -21,6 +21,9 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** @hide */
 public final class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
     public static final @TvInputManager.BroadcastInfoType int responseType =
@@ -41,20 +44,27 @@
             };
 
     private final ParcelFileDescriptor mFileDescriptor;
+    private final boolean mIsDirectory;
+    private final List<String> mChildren;
 
     public static DsmccResponse createFromParcelBody(Parcel in) {
         return new DsmccResponse(in);
     }
 
     public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
-            ParcelFileDescriptor file) {
+            ParcelFileDescriptor file, boolean isDirectory, List<String> children) {
         super(responseType, requestId, sequence, responseResult);
         mFileDescriptor = file;
+        mIsDirectory = isDirectory;
+        mChildren = children;
     }
 
     protected DsmccResponse(Parcel source) {
         super(responseType, source);
         mFileDescriptor = source.readFileDescriptor();
+        mIsDirectory = (source.readInt() == 1);
+        mChildren = new ArrayList<>();
+        source.readStringList(mChildren);
     }
 
     public ParcelFileDescriptor getFile() {
@@ -65,5 +75,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         mFileDescriptor.writeToParcel(dest, flags);
+        dest.writeInt(mIsDirectory ? 1 : 0);
+        dest.writeStringList(mChildren);
     }
 }
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index f4f55e4..49148ce 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -47,6 +47,7 @@
     void onTimeShiftStartPositionChanged(long timeMs, int seq);
     void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
     void onAitInfoUpdated(in AitInfo aitInfo, int seq);
+    void onSignalStrength(int stength, int seq);
 
     void onTuned(in Uri channelUri, int seq);
     // For the recording session
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 770b8aa..2a33ee6 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -77,7 +77,7 @@
     void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
     void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
 
-    void setIAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
+    void setInteractiveAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
 
     void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data,
             int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index f427501..9820034 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -42,7 +42,7 @@
     void setCaptionEnabled(boolean enabled);
     void selectTrack(int type, in String trackId);
 
-    void setIAppNotificationEnabled(boolean enable);
+    void setInteractiveAppNotificationEnabled(boolean enable);
 
     void appPrivateCommand(in String action, in Bundle data);
 
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 9830e78..9dfdb78 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -44,6 +44,7 @@
     void onTimeShiftStartPositionChanged(long timeMs);
     void onTimeShiftCurrentPositionChanged(long timeMs);
     void onAitInfoUpdated(in AitInfo aitInfo);
+    void onSignalStrength(int strength);
 
     // For the recording session
     void onTuned(in Uri channelUri);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 418ab2c..8911f6c 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -247,7 +247,7 @@
                 break;
             }
             case DO_SET_IAPP_NOTIFICATION_ENABLED: {
-                mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj);
+                mTvInputSessionImpl.setInteractiveAppNotificationEnabled((Boolean) msg.obj);
                 break;
             }
             case DO_REQUEST_AD: {
@@ -322,7 +322,7 @@
     }
 
     @Override
-    public void setIAppNotificationEnabled(boolean enabled) {
+    public void setInteractiveAppNotificationEnabled(boolean enabled) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageO(DO_SET_IAPP_NOTIFICATION_ENABLED, enabled));
     }
diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS
index 33acd0d..fa04293 100644
--- a/media/java/android/media/tv/OWNERS
+++ b/media/java/android/media/tv/OWNERS
@@ -1,6 +1,6 @@
-nchalko@google.com
 quxiangfang@google.com
 shubang@google.com
+hgchen@google.com
 
 # For android remote service
 per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index 912cbce..68d5f8a 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -17,9 +17,9 @@
 package android.media.tv;
 
 import android.annotation.NonNull;
+import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.net.Uri;
 
 /** @hide */
 public final class TableResponse extends BroadcastInfoResponse implements Parcelable {
diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java
new file mode 100644
index 0000000..0714972
--- /dev/null
+++ b/media/java/android/media/tv/TimelineRequest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class TimelineRequest extends BroadcastInfoRequest implements Parcelable {
+    private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
+            TvInputManager.BROADCAST_INFO_TYPE_TIMELINE;
+
+    public static final @NonNull Parcelable.Creator<TimelineRequest> CREATOR =
+            new Parcelable.Creator<TimelineRequest>() {
+                @Override
+                public TimelineRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TimelineRequest[] newArray(int size) {
+                    return new TimelineRequest[size];
+                }
+            };
+
+    private final int mIntervalMs;
+
+    static TimelineRequest createFromParcelBody(Parcel in) {
+        return new TimelineRequest(in);
+    }
+
+    public TimelineRequest(int requestId, @RequestOption int option, int intervalMs) {
+        super(REQUEST_TYPE, requestId, option);
+        mIntervalMs = intervalMs;
+    }
+
+    protected TimelineRequest(Parcel source) {
+        super(REQUEST_TYPE, source);
+        mIntervalMs = source.readInt();
+    }
+
+    public int getIntervalMs() {
+        return mIntervalMs;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mIntervalMs);
+    }
+}
diff --git a/media/java/android/media/tv/TimelineResponse.java b/media/java/android/media/tv/TimelineResponse.java
new file mode 100644
index 0000000..fee10b4
--- /dev/null
+++ b/media/java/android/media/tv/TimelineResponse.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class TimelineResponse extends BroadcastInfoResponse implements Parcelable {
+    private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
+            TvInputManager.BROADCAST_INFO_TYPE_TIMELINE;
+
+    public static final @NonNull Parcelable.Creator<TimelineResponse> CREATOR =
+            new Parcelable.Creator<TimelineResponse>() {
+                @Override
+                public TimelineResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TimelineResponse[] newArray(int size) {
+                    return new TimelineResponse[size];
+                }
+            };
+
+    private final String mSelector;
+    private final int mUnitsPerTick;
+    private final int mUnitsPerSecond;
+    private final long mWallClock;
+    private final long mTicks;
+
+    static TimelineResponse createFromParcelBody(Parcel in) {
+        return new TimelineResponse(in);
+    }
+
+    public TimelineResponse(int requestId, int sequence,
+            @ResponseResult int responseResult, String selector, int unitsPerTick,
+            int unitsPerSecond, long wallClock, long ticks) {
+        super(RESPONSE_TYPE, requestId, sequence, responseResult);
+        mSelector = selector;
+        mUnitsPerTick = unitsPerTick;
+        mUnitsPerSecond = unitsPerSecond;
+        mWallClock = wallClock;
+        mTicks = ticks;
+    }
+
+    protected TimelineResponse(Parcel source) {
+        super(RESPONSE_TYPE, source);
+        mSelector = source.readString();
+        mUnitsPerTick = source.readInt();
+        mUnitsPerSecond = source.readInt();
+        mWallClock = source.readLong();
+        mTicks = source.readLong();
+    }
+
+    public String getSelector() {
+        return mSelector;
+    }
+
+    public int getUnitsPerTick() {
+        return mUnitsPerTick;
+    }
+
+    public int getUnitsPerSecond() {
+        return mUnitsPerSecond;
+    }
+
+    public long getWallClock() {
+        return mWallClock;
+    }
+
+    public long getTicks() {
+        return mTicks;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mSelector);
+        dest.writeInt(mUnitsPerTick);
+        dest.writeInt(mUnitsPerSecond);
+        dest.writeLong(mWallClock);
+        dest.writeLong(mTicks);
+    }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 6420b4a..98d1599 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -363,9 +363,10 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION,
+    @IntDef(prefix = "BROADCAST_INFO_TYPE_", value =
+            {BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION,
             BROADCAST_INFO_TYPE_PES, BROADCAST_INFO_STREAM_EVENT, BROADCAST_INFO_TYPE_DSMCC,
-            BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION})
+            BROADCAST_INFO_TYPE_COMMAND, BROADCAST_INFO_TYPE_TIMELINE})
     public @interface BroadcastInfoType {}
 
     /** @hide */
@@ -381,7 +382,31 @@
     /** @hide */
     public static final int BROADCAST_INFO_TYPE_DSMCC = 6;
     /** @hide */
-    public static final int BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION = 7;
+    public static final int BROADCAST_INFO_TYPE_COMMAND = 7;
+    /** @hide */
+    public static final int BROADCAST_INFO_TYPE_TIMELINE = 8;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SIGNAL_STRENGTH_",
+            value = {SIGNAL_STRENGTH_LOST, SIGNAL_STRENGTH_WEAK, SIGNAL_STRENGTH_STRONG})
+    public @interface SignalStrength {}
+
+    /**
+     * Signal lost.
+     * @hide
+     */
+    public static final int SIGNAL_STRENGTH_LOST = 1;
+    /**
+     * Weak signal.
+     * @hide
+     */
+    public static final int SIGNAL_STRENGTH_WEAK = 2;
+    /**
+     * Strong signal.
+     * @hide
+     */
+    public static final int SIGNAL_STRENGTH_STRONG = 3;
 
     /**
      * An unknown state of the client pid gets from the TvInputManager. Client gets this value when
@@ -662,6 +687,14 @@
         }
 
         /**
+         * This is called when signal strength is updated.
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param strength The current signal strength.
+         */
+        public void onSignalStrength(Session session, @SignalStrength int strength) {
+        }
+
+        /**
          * This is called when the session has been tuned to the given channel.
          *
          * @param channelUri The URI of a channel.
@@ -735,8 +768,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onTracksChanged(mSession, tracks);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyTracksChanged(tracks);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyTracksChanged(tracks);
                     }
                 }
             });
@@ -747,8 +781,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onTrackSelected(mSession, type, trackId);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyTrackSelected(type, trackId);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyTrackSelected(type, trackId);
                     }
                 }
             });
@@ -768,8 +803,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onVideoAvailable(mSession);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyVideoAvailable();
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyVideoAvailable();
                     }
                 }
             });
@@ -780,8 +816,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onVideoUnavailable(mSession, reason);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyVideoUnavailable(reason);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyVideoUnavailable(reason);
                     }
                 }
             });
@@ -792,8 +829,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onContentAllowed(mSession);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyContentAllowed();
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyContentAllowed();
                     }
                 }
             });
@@ -804,8 +842,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onContentBlocked(mSession, rating);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyContentBlocked(rating);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyContentBlocked(rating);
                     }
                 }
             });
@@ -866,13 +905,27 @@
             });
         }
 
+        void postSignalStrength(final int strength) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSignalStrength(mSession, strength);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifySignalStrength(strength);
+                    }
+                }
+            });
+        }
+
         void postTuned(final Uri channelUri) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     mSessionCallback.onTuned(mSession, channelUri);
-                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
-                        mSession.getIAppSession().notifyTuned(channelUri);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyTuned(channelUri);
                     }
                 }
             });
@@ -903,8 +956,9 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        if (mSession.getIAppSession() != null) {
-                            mSession.getIAppSession().notifyBroadcastInfoResponse(response);
+                        if (mSession.getInteractiveAppSession() != null) {
+                            mSession.getInteractiveAppSession()
+                                    .notifyBroadcastInfoResponse(response);
                         }
                     }
                 });
@@ -916,8 +970,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        if (mSession.getIAppSession() != null) {
-                            mSession.getIAppSession().notifyAdResponse(response);
+                        if (mSession.getInteractiveAppSession() != null) {
+                            mSession.getInteractiveAppSession().notifyAdResponse(response);
                         }
                     }
                 });
@@ -1299,6 +1353,18 @@
             }
 
             @Override
+            public void onSignalStrength(int strength, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postSignalStrength(strength);
+                }
+            }
+
+            @Override
             public void onTuned(Uri channelUri, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -2265,12 +2331,12 @@
             mSessionCallbackRecordMap = sessionCallbackRecordMap;
         }
 
-        public TvIAppManager.Session getIAppSession() {
+        public TvIAppManager.Session getInteractiveAppSession() {
             return mIAppSession;
         }
 
-        public void setIAppSession(TvIAppManager.Session IAppSession) {
-            this.mIAppSession = IAppSession;
+        public void setInteractiveAppSession(TvIAppManager.Session iAppSession) {
+            this.mIAppSession = iAppSession;
         }
 
         /**
@@ -2531,13 +2597,13 @@
          *                {@code false} otherwise.
          * @hide
          */
-        public void setIAppNotificationEnabled(boolean enabled) {
+        public void setInteractiveAppNotificationEnabled(boolean enabled) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
             }
             try {
-                mService.setIAppNotificationEnabled(mToken, enabled, mUserId);
+                mService.setInteractiveAppNotificationEnabled(mToken, enabled, mUserId);
                 mIAppNotificationEnabled = enabled;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3a40d6f..524ba34 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -966,6 +966,27 @@
         }
 
         /**
+         * Notifies signal strength.
+         * @hide
+         */
+        public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifySignalStrength");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onSignalStrength(strength);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifySignalStrength", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
          * is relative to the overlay view that sits on top of this surface.
          *
@@ -1180,7 +1201,7 @@
          * @param enabled {@code true} to enable, {@code false} to disable.
          * @hide
          */
-        public void onSetIAppNotificationEnabled(boolean enabled) {
+        public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
         }
 
         /**
@@ -1532,10 +1553,10 @@
         }
 
         /**
-         * Calls {@link #onSetIAppNotificationEnabled}.
+         * Calls {@link #onSetInteractiveAppNotificationEnabled}.
          */
-        void setIAppNotificationEnabled(boolean enabled) {
-            onSetIAppNotificationEnabled(enabled);
+        void setInteractiveAppNotificationEnabled(boolean enabled) {
+            onSetInteractiveAppNotificationEnabled(enabled);
         }
 
         /**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 4a12cd7..71f6ad6 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -486,9 +486,9 @@
      *                {@code false} otherwise.
      * @hide
      */
-    public void setIAppNotificationEnabled(boolean enabled) {
+    public void setInteractiveAppNotificationEnabled(boolean enabled) {
         if (mSession != null) {
-            mSession.setIAppNotificationEnabled(enabled);
+            mSession.setInteractiveAppNotificationEnabled(enabled);
         }
     }
 
@@ -1071,6 +1071,16 @@
         }
 
         /**
+         * This is called when signal strength is updated.
+         * @param inputId The ID of the TV input bound to this view.
+         * @param strength The current signal strength.
+         *
+         * @hide
+         */
+        public void onSignalStrength(String inputId, @TvInputManager.SignalStrength int strength) {
+        }
+
+        /**
          * This is called when the session has been tuned to the given channel.
          *
          * @param channelUri The URI of a channel.
@@ -1390,6 +1400,20 @@
         }
 
         @Override
+        public void onSignalStrength(Session session, int strength) {
+            if (DEBUG) {
+                Log.d(TAG, "onSignalStrength(strength=" + strength + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSignalStrength - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onSignalStrength(mInputId, strength);
+            }
+        }
+
+        @Override
         public void onTuned(Session session, Uri channelUri) {
             if (DEBUG) {
                 Log.d(TAG, "onTuned(channelUri=" + channelUri + ")");
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 2e04359..a19a2d2 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -20,9 +20,9 @@
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.ITvIAppClient;
-import android.media.tv.interactive.ITvIAppManagerCallback;
-import android.media.tv.interactive.TvIAppInfo;
+import android.media.tv.interactive.ITvInteractiveAppClient;
+import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
+import android.media.tv.interactive.TvInteractiveAppInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.view.Surface;
@@ -32,21 +32,25 @@
  * @hide
  */
 interface ITvIAppManager {
-    List<TvIAppInfo> getTvIAppServiceList(int userId);
+    List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId);
     void prepare(String tiasId, int type, int userId);
-    void notifyAppLinkInfo(String tiasId, in Bundle info, int userId);
+    void registerAppLinkInfo(String tiasId, in Bundle info, int userId);
+    void unregisterAppLinkInfo(String tiasId, in Bundle info, int userId);
     void sendAppLinkCommand(String tiasId, in Bundle command, int userId);
-    void startIApp(in IBinder sessionToken, int userId);
-    void stopIApp(in IBinder sessionToken, int userId);
+    void startInteractiveApp(in IBinder sessionToken, int userId);
+    void stopInteractiveApp(in IBinder sessionToken, int userId);
+    void resetInteractiveApp(in IBinder sessionToken, int userId);
     void createBiInteractiveApp(
             in IBinder sessionToken, in Uri biIAppUri, in Bundle params, int userId);
     void destroyBiInteractiveApp(in IBinder sessionToken, in String biIAppId, int userId);
+    void setTeletextAppEnabled(in IBinder sessionToken, boolean enable, int userId);
     void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
     void sendCurrentChannelLcn(in IBinder sessionToken, int lcn, int userId);
     void sendStreamVolume(in IBinder sessionToken, float volume, int userId);
     void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
-    void createSession(
-            in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
+    void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId);
+    void createSession(in ITvInteractiveAppClient client, in String iAppServiceId, int type,
+            int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
     void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
     void notifyTrackSelected(in IBinder sessionToken, int type, in String trackId, int userId);
@@ -55,6 +59,7 @@
     void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
     void notifyContentAllowed(in IBinder sessionToken, int userId);
     void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
+    void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
@@ -67,6 +72,6 @@
     void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
     void removeMediaView(in IBinder sessionToken, int userId);
 
-    void registerCallback(in ITvIAppManagerCallback callback, int userId);
-    void unregisterCallback(in ITvIAppManagerCallback callback, int userId);
+    void registerCallback(in ITvInteractiveAppManagerCallback callback, int userId);
+    void unregisterCallback(in ITvInteractiveAppManagerCallback callback, int userId);
 }
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
deleted file mode 100644
index 8acb75f..0000000
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.interactive;
-
-import android.media.tv.interactive.ITvIAppServiceCallback;
-import android.media.tv.interactive.ITvIAppSessionCallback;
-import android.os.Bundle;
-import android.view.InputChannel;
-
-/**
- * Top-level interface to a TV IApp component (implemented in a Service). It's used for
- * TvIAppManagerService to communicate with TvIAppService.
- * @hide
- */
-oneway interface ITvIAppService {
-    void registerCallback(in ITvIAppServiceCallback callback);
-    void unregisterCallback(in ITvIAppServiceCallback callback);
-    void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
-            in String iAppServiceId, int type);
-    void prepare(int type);
-    void notifyAppLinkInfo(in Bundle info);
-    void sendAppLinkCommand(in Bundle command);
-}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
similarity index 83%
rename from media/java/android/media/tv/interactive/ITvIAppClient.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 892a800..1a8fc46 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -24,11 +24,11 @@
 import android.view.InputChannel;
 
 /**
- * Interface a client of the ITvIAppManager implements, to identify itself and receive information
- * about changes to the state of each TV interactive application service.
+ * Interface a client of the ITvInteractiveAppManager implements, to identify itself and receive
+ * information about changes to the state of each TV interactive application service.
  * @hide
  */
-oneway interface ITvIAppClient {
+oneway interface ITvInteractiveAppClient {
     void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq);
     void onSessionReleased(int seq);
     void onLayoutSurface(int left, int top, int right, int bottom, int seq);
@@ -36,11 +36,13 @@
     void onRemoveBroadcastInfo(int id, int seq);
     void onSessionStateChanged(int state, int seq);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq);
+    void onTeletextAppStateChanged(int state, int seq);
     void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
     void onSetVideoBounds(in Rect rect, int seq);
     void onRequestCurrentChannelUri(int seq);
     void onRequestCurrentChannelLcn(int seq);
     void onRequestStreamVolume(int seq);
     void onRequestTrackInfoList(int seq);
+    void onRequestCurrentTvInputId(int seq);
     void onAdRequest(in AdRequest request, int Seq);
 }
diff --git a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
similarity index 62%
rename from media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
index d5e0c63..f4510f6 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
@@ -16,16 +16,16 @@
 
 package android.media.tv.interactive;
 
-import android.media.tv.interactive.TvIAppInfo;
+import android.media.tv.interactive.TvInteractiveAppInfo;
 
 /**
- * Interface to receive callbacks from ITvIAppManager regardless of sessions.
+ * Interface to receive callbacks from ITvInteractiveAppManager regardless of sessions.
  * @hide
  */
-interface ITvIAppManagerCallback {
-    void onIAppServiceAdded(in String iAppServiceId);
-    void onIAppServiceRemoved(in String iAppServiceId);
-    void onIAppServiceUpdated(in String iAppServiceId);
-    void onTvIAppInfoUpdated(in TvIAppInfo tvIAppInfo);
+interface ITvInteractiveAppManagerCallback {
+    void onInteractiveAppServiceAdded(in String iAppServiceId);
+    void onInteractiveAppServiceRemoved(in String iAppServiceId);
+    void onInteractiveAppServiceUpdated(in String iAppServiceId);
+    void onTvInteractiveAppInfoUpdated(in TvInteractiveAppInfo tvIAppInfo);
     void onStateChanged(in String iAppServiceId, int type, int state);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
new file mode 100644
index 0000000..c1e6622
--- /dev/null
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.media.tv.interactive.ITvInteractiveAppServiceCallback;
+import android.media.tv.interactive.ITvInteractiveAppSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV Interactive App component (implemented in a Service). It's used for
+ * TvIAppManagerService to communicate with TvIAppService.
+ * @hide
+ */
+oneway interface ITvInteractiveAppService {
+    void registerCallback(in ITvInteractiveAppServiceCallback callback);
+    void unregisterCallback(in ITvInteractiveAppServiceCallback callback);
+    void createSession(in InputChannel channel, in ITvInteractiveAppSessionCallback callback,
+            in String iAppServiceId, int type);
+    void prepare(int type);
+    void registerAppLinkInfo(in Bundle info);
+    void unregisterAppLinkInfo(in Bundle info);
+    void sendAppLinkCommand(in Bundle command);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
similarity index 84%
rename from media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
index fec7d78..f56d3bd 100644
--- a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
@@ -17,10 +17,10 @@
 package android.media.tv.interactive;
 
 /**
- * Helper interface for ITvIAppService to allow the TvIAppService to notify the
+ * Helper interface for ITvInteractiveAppService to allow the TvIAppService to notify the
  * TvIAppManagerService.
  * @hide
  */
-oneway interface ITvIAppServiceCallback {
+oneway interface ITvInteractiveAppServiceCallback {
     void onStateChanged(int type, int state);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
similarity index 83%
rename from media/java/android/media/tv/interactive/ITvIAppSession.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 2788ff6..c449d2475 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -26,18 +26,22 @@
 import android.view.Surface;
 
 /**
- * Sub-interface of ITvIAppService.aidl which is created per session and has its own context.
+ * Sub-interface of ITvInteractiveAppService.aidl which is created per session and has its own
+ * context.
  * @hide
  */
-oneway interface ITvIAppSession {
-    void startIApp();
-    void stopIApp();
+oneway interface ITvInteractiveAppSession {
+    void startInteractiveApp();
+    void stopInteractiveApp();
+    void resetInteractiveApp();
     void createBiInteractiveApp(in Uri biIAppUri, in Bundle params);
     void destroyBiInteractiveApp(in String biIAppId);
+    void setTeletextAppEnabled(boolean enable);
     void sendCurrentChannelUri(in Uri channelUri);
     void sendCurrentChannelLcn(int lcn);
     void sendStreamVolume(float volume);
     void sendTrackInfoList(in List<TvTrackInfo> tracks);
+    void sendCurrentTvInputId(in String inputId);
     void release();
     void notifyTuned(in Uri channelUri);
     void notifyTrackSelected(int type, in String trackId);
@@ -46,6 +50,7 @@
     void notifyVideoUnavailable(int reason);
     void notifyContentAllowed();
     void notifyContentBlocked(in String rating);
+    void notifySignalStrength(int strength);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
similarity index 78%
rename from media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 9b9e6af..c270424 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -19,27 +19,29 @@
 import android.graphics.Rect;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
-import android.media.tv.interactive.ITvIAppSession;
+import android.media.tv.interactive.ITvInteractiveAppSession;
 import android.net.Uri;
 import android.os.Bundle;
 
 /**
- * Helper interface for ITvIAppSession to allow TvIAppService to notify the system service when
- * there is a related event.
+ * Helper interface for ITvInteractiveAppSession to allow TvIAppService to notify the
+ * system service when there is a related event.
  * @hide
  */
-oneway interface ITvIAppSessionCallback {
-    void onSessionCreated(in ITvIAppSession session);
+oneway interface ITvInteractiveAppSessionCallback {
+    void onSessionCreated(in ITvInteractiveAppSession session);
     void onLayoutSurface(int left, int top, int right, int bottom);
     void onBroadcastInfoRequest(in BroadcastInfoRequest request);
     void onRemoveBroadcastInfo(int id);
     void onSessionStateChanged(int state);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId);
+    void onTeletextAppStateChanged(int state);
     void onCommandRequest(in String cmdType, in Bundle parameters);
     void onSetVideoBounds(in Rect rect);
     void onRequestCurrentChannelUri();
     void onRequestCurrentChannelLcn();
     void onRequestStreamVolume();
     void onRequestTrackInfoList();
+    void onRequestCurrentTvInputId();
     void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.java b/media/java/android/media/tv/interactive/TvIAppInfo.java
deleted file mode 100644
index b5245fc..0000000
--- a/media/java/android/media/tv/interactive/TvIAppInfo.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.interactive;
-
-import android.annotation.NonNull;
-import android.annotation.StringDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class is used to specify meta information of a TV interactive app.
- * @hide
- */
-public final class TvIAppInfo implements Parcelable {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "TvIAppInfo";
-
-    @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = {
-            INTERACTIVE_APP_TYPE_HBBTV,
-            INTERACTIVE_APP_TYPE_ATSC,
-            INTERACTIVE_APP_TYPE_GINGA,
-    })
-    @interface InteractiveAppType {}
-
-    /** HbbTV interactive app type */
-    public static final String INTERACTIVE_APP_TYPE_HBBTV = "hbbtv";
-    /** ATSC interactive app type */
-    public static final String INTERACTIVE_APP_TYPE_ATSC = "atsc";
-    /** Ginga interactive app type */
-    public static final String INTERACTIVE_APP_TYPE_GINGA = "ginga";
-
-    private final ResolveInfo mService;
-    private final String mId;
-    private List<String> mTypes = new ArrayList<>();
-
-    private TvIAppInfo(ResolveInfo service, String id, List<String> types) {
-        mService = service;
-        mId = id;
-        mTypes = types;
-    }
-
-    private TvIAppInfo(@NonNull Parcel in) {
-        mService = ResolveInfo.CREATOR.createFromParcel(in);
-        mId = in.readString();
-        in.readStringList(mTypes);
-    }
-
-    public static final @NonNull Creator<TvIAppInfo> CREATOR = new Creator<TvIAppInfo>() {
-        @Override
-        public TvIAppInfo createFromParcel(Parcel in) {
-            return new TvIAppInfo(in);
-        }
-
-        @Override
-        public TvIAppInfo[] newArray(int size) {
-            return new TvIAppInfo[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        mService.writeToParcel(dest, flags);
-        dest.writeString(mId);
-        dest.writeStringList(mTypes);
-    }
-
-    @NonNull
-    public String getId() {
-        return mId;
-    }
-
-    /**
-     * Returns the component of the TV IApp service.
-     * @hide
-     */
-    public ComponentName getComponent() {
-        return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
-    }
-
-    /**
-     * Returns the information of the service that implements this TV IApp service.
-     */
-    public ServiceInfo getServiceInfo() {
-        return mService.serviceInfo;
-    }
-
-    /**
-     * Gets supported interactive app types
-     */
-    @NonNull
-    public List<String> getSupportedTypes() {
-        return new ArrayList<>(mTypes);
-    }
-
-    /**
-     * A convenience builder for creating {@link TvIAppInfo} objects.
-     */
-    public static final class Builder {
-        private static final String XML_START_TAG_NAME = "tv-iapp";
-        private final Context mContext;
-        private final ResolveInfo mResolveInfo;
-        private final List<String> mTypes = new ArrayList<>();
-
-        /**
-         * Constructs a new builder for {@link TvIAppInfo}.
-         *
-         * @param context A Context of the application package implementing this class.
-         * @param component The name of the application component to be used for the
-         *                  {@link TvIAppService}.
-         */
-        public Builder(@NonNull Context context, @NonNull ComponentName component) {
-            if (context == null) {
-                throw new IllegalArgumentException("context cannot be null.");
-            }
-            Intent intent = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
-            mResolveInfo = context.getPackageManager().resolveService(intent,
-                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
-            if (mResolveInfo == null) {
-                throw new IllegalArgumentException("Invalid component. Can't find the service.");
-            }
-            mContext = context;
-        }
-
-        /**
-         * Creates a {@link TvIAppInfo} instance with the specified fields. Most of the information
-         * is obtained by parsing the AndroidManifest and {@link TvIAppService#SERVICE_META_DATA}
-         * for the {@link TvIAppService} this TV IApp implements.
-         *
-         * @return TvIAppInfo containing information about this TV IApp service.
-         */
-        @NonNull
-        public TvIAppInfo build() {
-            ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName,
-                    mResolveInfo.serviceInfo.name);
-            String id;
-            id = generateIAppServiceId(componentName);
-            parseServiceMetadata();
-            return new TvIAppInfo(mResolveInfo, id, mTypes);
-        }
-
-        private static String generateIAppServiceId(ComponentName name) {
-            return name.flattenToShortString();
-        }
-
-        private void parseServiceMetadata() {
-            ServiceInfo si = mResolveInfo.serviceInfo;
-            PackageManager pm = mContext.getPackageManager();
-            try (XmlResourceParser parser =
-                         si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) {
-                if (parser == null) {
-                    throw new IllegalStateException("No " + TvIAppService.SERVICE_META_DATA
-                            + " meta-data found for " + si.name);
-                }
-
-                Resources res = pm.getResourcesForApplication(si.applicationInfo);
-                AttributeSet attrs = Xml.asAttributeSet(parser);
-
-                int type;
-                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                        && type != XmlPullParser.START_TAG) {
-                    // move to the START_TAG
-                }
-
-                String nodeName = parser.getName();
-                if (!XML_START_TAG_NAME.equals(nodeName)) {
-                    throw new IllegalStateException("Meta-data does not start with "
-                            + XML_START_TAG_NAME + " tag for " + si.name);
-                }
-
-                TypedArray sa = res.obtainAttributes(attrs,
-                        com.android.internal.R.styleable.TvIAppService);
-                CharSequence[] types = sa.getTextArray(
-                        com.android.internal.R.styleable.TvIAppService_supportedTypes);
-                for (CharSequence cs : types) {
-                    mTypes.add(cs.toString().toLowerCase());
-                }
-
-                sa.recycle();
-            } catch (IOException | XmlPullParserException e) {
-                throw new IllegalStateException(
-                        "Failed reading meta-data for " + si.packageName, e);
-            } catch (PackageManager.NameNotFoundException e) {
-                throw new IllegalStateException("No resources found for " + si.packageName, e);
-            }
-        }
-    }
-}
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
old mode 100644
new mode 100755
index 9685e3a..f819438
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -64,39 +64,63 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = false, prefix = "TV_IAPP_RTE_STATE_", value = {
-            TV_IAPP_RTE_STATE_UNREALIZED,
-            TV_IAPP_RTE_STATE_PREPARING,
-            TV_IAPP_RTE_STATE_READY,
-            TV_IAPP_RTE_STATE_ERROR})
-    public @interface TvIAppRteState {}
+    @IntDef(flag = false, prefix = "TV_INTERACTIVE_APP_RTE_STATE_", value = {
+            TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED,
+            TV_INTERACTIVE_APP_RTE_STATE_PREPARING,
+            TV_INTERACTIVE_APP_RTE_STATE_READY,
+            TV_INTERACTIVE_APP_RTE_STATE_ERROR})
+    public @interface TvInteractiveAppRteState {}
 
     /**
      * Unrealized state of interactive app RTE.
      * @hide
      */
-    public static final int TV_IAPP_RTE_STATE_UNREALIZED = 1;
+    public static final int TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED = 1;
     /**
      * Preparing state of interactive app RTE.
      * @hide
      */
-    public static final int TV_IAPP_RTE_STATE_PREPARING = 2;
+    public static final int TV_INTERACTIVE_APP_RTE_STATE_PREPARING = 2;
     /**
      * Ready state of interactive app RTE.
      * @hide
      */
-    public static final int TV_IAPP_RTE_STATE_READY = 3;
+    public static final int TV_INTERACTIVE_APP_RTE_STATE_READY = 3;
     /**
      * Error state of interactive app RTE.
      * @hide
      */
-    public static final int TV_IAPP_RTE_STATE_ERROR = 4;
+    public static final int TV_INTERACTIVE_APP_RTE_STATE_ERROR = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
+            TELETEXT_APP_STATE_SHOW,
+            TELETEXT_APP_STATE_HIDE,
+            TELETEXT_APP_STATE_ERROR})
+    public @interface TeletextAppState {}
+
+    /**
+     * Show state of Teletext app.
+     * @hide
+     */
+    public static final int TELETEXT_APP_STATE_SHOW = 1;
+    /**
+     * Hide state of Teletext app.
+     * @hide
+     */
+    public static final int TELETEXT_APP_STATE_HIDE = 2;
+    /**
+     * Error state of Teletext app.
+     * @hide
+     */
+    public static final int TELETEXT_APP_STATE_ERROR = 3;
 
     /**
      * Key for package name in app link.
      * <p>Type: String
      *
-     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #registerAppLinkInfo(String, Bundle)
      * @see #sendAppLinkCommand(String, Bundle)
      * @hide
      */
@@ -106,7 +130,7 @@
      * Key for class name in app link.
      * <p>Type: String
      *
-     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #registerAppLinkInfo(String, Bundle)
      * @see #sendAppLinkCommand(String, Bundle)
      * @hide
      */
@@ -116,7 +140,7 @@
      * Key for URI scheme in app link.
      * <p>Type: String
      *
-     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #registerAppLinkInfo(String, Bundle)
      * @hide
      */
     public static final String KEY_URI_SCHEME = "uri_scheme";
@@ -125,7 +149,7 @@
      * Key for URI host in app link.
      * <p>Type: String
      *
-     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #registerAppLinkInfo(String, Bundle)
      * @hide
      */
     public static final String KEY_URI_HOST = "uri_host";
@@ -134,7 +158,7 @@
      * Key for URI prefix in app link.
      * <p>Type: String
      *
-     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #registerAppLinkInfo(String, Bundle)
      * @hide
      */
     public static final String KEY_URI_PREFIX = "uri_prefix";
@@ -174,7 +198,7 @@
             new SparseArray<>();
 
     // @GuardedBy("mLock")
-    private final List<TvIAppCallbackRecord> mCallbackRecords = new LinkedList<>();
+    private final List<TvInteractiveAppCallbackRecord> mCallbackRecords = new LinkedList<>();
 
     // A sequence number for the next session to be created. Should be protected by a lock
     // {@code mSessionCallbackRecordMap}.
@@ -182,13 +206,13 @@
 
     private final Object mLock = new Object();
 
-    private final ITvIAppClient mClient;
+    private final ITvInteractiveAppClient mClient;
 
     /** @hide */
     public TvIAppManager(ITvIAppManager service, int userId) {
         mService = service;
         mUserId = userId;
-        mClient = new ITvIAppClient.Stub() {
+        mClient = new ITvInteractiveAppClient.Stub() {
             @Override
             public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel,
                     int seq) {
@@ -260,8 +284,10 @@
             }
 
             @Override
-            public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType,
-                    Bundle parameters, int seq) {
+            public void onCommandRequest(
+                    @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                    Bundle parameters,
+                    int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
                     if (record == null) {
@@ -345,6 +371,18 @@
             }
 
             @Override
+            public void onRequestCurrentTvInputId(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestCurrentTvInputId();
+                }
+            }
+
+            @Override
             public void onSessionStateChanged(int state, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -367,41 +405,54 @@
                     record.postBiInteractiveAppCreated(biIAppUri, biIAppId);
                 }
             }
+
+            @Override
+            public void onTeletextAppStateChanged(int state, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postTeletextAppStateChanged(state);
+                }
+            }
         };
-        ITvIAppManagerCallback managerCallback = new ITvIAppManagerCallback.Stub() {
+        ITvInteractiveAppManagerCallback managerCallback =
+                new ITvInteractiveAppManagerCallback.Stub() {
             @Override
-            public void onIAppServiceAdded(String iAppServiceId) {
+            public void onInteractiveAppServiceAdded(String iAppServiceId) {
                 synchronized (mLock) {
-                    for (TvIAppCallbackRecord record : mCallbackRecords) {
-                        record.postIAppServiceAdded(iAppServiceId);
+                    for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+                        record.postInteractiveAppServiceAdded(iAppServiceId);
                     }
                 }
             }
 
             @Override
-            public void onIAppServiceRemoved(String iAppServiceId) {
+            public void onInteractiveAppServiceRemoved(String iAppServiceId) {
                 synchronized (mLock) {
-                    for (TvIAppCallbackRecord record : mCallbackRecords) {
-                        record.postIAppServiceRemoved(iAppServiceId);
+                    for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+                        record.postInteractiveAppServiceRemoved(iAppServiceId);
                     }
                 }
             }
 
             @Override
-            public void onIAppServiceUpdated(String iAppServiceId) {
+            public void onInteractiveAppServiceUpdated(String iAppServiceId) {
                 synchronized (mLock) {
-                    for (TvIAppCallbackRecord record : mCallbackRecords) {
-                        record.postIAppServiceUpdated(iAppServiceId);
+                    for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+                        record.postInteractiveAppServiceUpdated(iAppServiceId);
                     }
                 }
             }
 
             @Override
-            public void onTvIAppInfoUpdated(TvIAppInfo iAppInfo) {
-                // TODO: add public API updateIAppInfo()
+            public void onTvInteractiveAppInfoUpdated(TvInteractiveAppInfo iAppInfo) {
+                // TODO: add public API updateInteractiveAppInfo()
                 synchronized (mLock) {
-                    for (TvIAppCallbackRecord record : mCallbackRecords) {
-                        record.postTvIAppInfoUpdated(iAppInfo);
+                    for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
+                        record.postTvInteractiveAppInfoUpdated(iAppInfo);
                     }
                 }
             }
@@ -409,7 +460,7 @@
             @Override
             public void onStateChanged(String iAppServiceId, int type, int state) {
                 synchronized (mLock) {
-                    for (TvIAppCallbackRecord record : mCallbackRecords) {
+                    for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
                         record.postStateChanged(iAppServiceId, type, state);
                     }
                 }
@@ -425,110 +476,112 @@
     }
 
     /**
-     * Callback used to monitor status of the TV IApp.
+     * Callback used to monitor status of the TV Interactive App.
      * @hide
      */
-    public abstract static class TvIAppCallback {
+    public abstract static class TvInteractiveAppCallback {
         /**
-         * This is called when a TV IApp service is added to the system.
+         * This is called when a TV Interactive App service is added to the system.
          *
-         * <p>Normally it happens when the user installs a new TV IApp service package that
-         * implements {@link TvIAppService} interface.
+         * <p>Normally it happens when the user installs a new TV Interactive App service package
+         * that implements {@link TvIAppService} interface.
          *
-         * @param iAppServiceId The ID of the TV IApp service.
+         * @param iAppServiceId The ID of the TV Interactive App service.
          */
-        public void onIAppServiceAdded(@NonNull String iAppServiceId) {
+        public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when a TV IApp service is removed from the system.
+         * This is called when a TV Interactive App service is removed from the system.
          *
-         * <p>Normally it happens when the user uninstalls the previously installed TV IApp service
-         * package.
+         * <p>Normally it happens when the user uninstalls the previously installed TV Interactive
+         * App service package.
          *
-         * @param iAppServiceId The ID of the TV IApp service.
+         * @param iAppServiceId The ID of the TV Interactive App service.
          */
-        public void onIAppServiceRemoved(@NonNull String iAppServiceId) {
+        public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when a TV IApp service is updated on the system.
+         * This is called when a TV Interactive App service is updated on the system.
          *
-         * <p>Normally it happens when a previously installed TV IApp service package is
+         * <p>Normally it happens when a previously installed TV Interactive App service package is
          * re-installed or a newer version of the package exists becomes available/unavailable.
          *
-         * @param iAppServiceId The ID of the TV IApp service.
+         * @param iAppServiceId The ID of the TV Interactive App service.
          */
-        public void onIAppServiceUpdated(@NonNull String iAppServiceId) {
+        public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when the information about an existing TV IApp service has been updated.
+         * This is called when the information about an existing TV Interactive App service has been
+         * updated.
          *
-         * <p>Because the system automatically creates a <code>TvIAppInfo</code> object for each TV
-         * IApp service based on the information collected from the
+         * <p>Because the system automatically creates a <code>TvInteractiveAppInfo</code> object
+         * for each TV Interactive App service based on the information collected from the
          * <code>AndroidManifest.xml</code>, this method is only called back when such information
          * has changed dynamically.
          *
-         * @param iAppInfo The <code>TvIAppInfo</code> object that contains new information.
+         * @param iAppInfo The <code>TvInteractiveAppInfo</code> object that contains new
+         *                 information.
          */
-        public void onTvIAppInfoUpdated(@NonNull TvIAppInfo iAppInfo) {
+        public void onTvInteractiveAppInfoUpdated(@NonNull TvInteractiveAppInfo iAppInfo) {
         }
 
         /**
          * This is called when the state of the interactive app service is changed.
          * @hide
          */
-        public void onTvIAppServiceStateChanged(
-                @NonNull String iAppServiceId, int type, @TvIAppRteState int state) {
+        public void onTvInteractiveAppServiceStateChanged(
+                @NonNull String iAppServiceId, int type, @TvInteractiveAppRteState int state) {
         }
     }
 
-    private static final class TvIAppCallbackRecord {
-        private final TvIAppCallback mCallback;
+    private static final class TvInteractiveAppCallbackRecord {
+        private final TvInteractiveAppCallback mCallback;
         private final Handler mHandler;
 
-        TvIAppCallbackRecord(TvIAppCallback callback, Handler handler) {
+        TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Handler handler) {
             mCallback = callback;
             mHandler = handler;
         }
 
-        public TvIAppCallback getCallback() {
+        public TvInteractiveAppCallback getCallback() {
             return mCallback;
         }
 
-        public void postIAppServiceAdded(final String iAppServiceId) {
+        public void postInteractiveAppServiceAdded(final String iAppServiceId) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mCallback.onIAppServiceAdded(iAppServiceId);
+                    mCallback.onInteractiveAppServiceAdded(iAppServiceId);
                 }
             });
         }
 
-        public void postIAppServiceRemoved(final String iAppServiceId) {
+        public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mCallback.onIAppServiceRemoved(iAppServiceId);
+                    mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
                 }
             });
         }
 
-        public void postIAppServiceUpdated(final String iAppServiceId) {
+        public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mCallback.onIAppServiceUpdated(iAppServiceId);
+                    mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
                 }
             });
         }
 
-        public void postTvIAppInfoUpdated(final TvIAppInfo iAppInfo) {
+        public void postTvInteractiveAppInfoUpdated(final TvInteractiveAppInfo iAppInfo) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mCallback.onTvIAppInfoUpdated(iAppInfo);
+                    mCallback.onTvInteractiveAppInfoUpdated(iAppInfo);
                 }
             });
         }
@@ -537,7 +590,7 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mCallback.onTvIAppServiceStateChanged(iAppServiceId, type, state);
+                    mCallback.onTvInteractiveAppServiceStateChanged(iAppServiceId, type, state);
                 }
             });
         }
@@ -578,23 +631,23 @@
     }
 
     /**
-     * Returns the complete list of TV IApp service on the system.
+     * Returns the complete list of TV Interactive App service on the system.
      *
-     * @return List of {@link TvIAppInfo} for each TV IApp service that describes its meta
-     *         information.
+     * @return List of {@link TvInteractiveAppInfo} for each TV Interactive App service that
+     *         describes its meta information.
      * @hide
      */
     @NonNull
-    public List<TvIAppInfo> getTvIAppServiceList() {
+    public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList() {
         try {
-            return mService.getTvIAppServiceList(mUserId);
+            return mService.getTvInteractiveAppServiceList(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Prepares TV IApp service for the given type.
+     * Prepares TV Interactive App service for the given type.
      * @hide
      */
     public void prepare(@NonNull String tvIAppServiceId, int type) {
@@ -606,12 +659,25 @@
     }
 
     /**
-     * Notifies app link info.
+     * Registers app link info.
      * @hide
      */
-    public void notifyAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
+    public void registerAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
         try {
-            mService.notifyAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
+            mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters app link info.
+     * @hide
+     */
+    public void unregisterAppLinkInfo(
+            @NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
+        try {
+            mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -630,32 +696,33 @@
     }
 
     /**
-     * Registers a {@link TvIAppManager.TvIAppCallback}.
+     * Registers a {@link TvInteractiveAppCallback}.
      *
-     * @param callback A callback used to monitor status of the TV IApp services.
+     * @param callback A callback used to monitor status of the TV Interactive App services.
      * @param handler A {@link Handler} that the status change will be delivered to.
      * @hide
      */
-    public void registerCallback(@NonNull TvIAppCallback callback, @NonNull Handler handler) {
+    public void registerCallback(
+            @NonNull TvInteractiveAppCallback callback, @NonNull Handler handler) {
         Preconditions.checkNotNull(callback);
         Preconditions.checkNotNull(handler);
         synchronized (mLock) {
-            mCallbackRecords.add(new TvIAppCallbackRecord(callback, handler));
+            mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, handler));
         }
     }
 
     /**
-     * Unregisters the existing {@link TvIAppManager.TvIAppCallback}.
+     * Unregisters the existing {@link TvInteractiveAppCallback}.
      *
      * @param callback The existing callback to remove.
      * @hide
      */
-    public void unregisterCallback(@NonNull final TvIAppCallback callback) {
+    public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
         Preconditions.checkNotNull(callback);
         synchronized (mLock) {
-            for (Iterator<TvIAppCallbackRecord> it = mCallbackRecords.iterator();
+            for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator();
                     it.hasNext(); ) {
-                TvIAppCallbackRecord record = it.next();
+                TvInteractiveAppCallbackRecord record = it.next();
                 if (record.getCallback() == callback) {
                     it.remove();
                     break;
@@ -692,8 +759,8 @@
         private TvInputEventSender mSender;
         private InputChannel mInputChannel;
 
-        private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId,
-                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
+        private Session(IBinder token, InputChannel channel, ITvIAppManager service,
+                int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
             mToken = token;
             mInputChannel = channel;
             mService = service;
@@ -710,25 +777,37 @@
             mInputSession = inputSession;
         }
 
-        void startIApp() {
+        void startInteractiveApp() {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
             }
             try {
-                mService.startIApp(mToken, mUserId);
+                mService.startInteractiveApp(mToken, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
         }
 
-        void stopIApp() {
+        void stopInteractiveApp() {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
             }
             try {
-                mService.stopIApp(mToken, mUserId);
+                mService.stopInteractiveApp(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void resetInteractiveApp() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.resetInteractiveApp(mToken, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -758,6 +837,18 @@
             }
         }
 
+        void setTeletextAppEnabled(boolean enable) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.setTeletextAppEnabled(mToken, enable, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendCurrentChannelUri(@Nullable Uri channelUri) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -806,6 +897,18 @@
             }
         }
 
+        void sendCurrentTvInputId(@Nullable String inputId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCurrentTvInputId(mToken, inputId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Sets the {@link android.view.Surface} for this session.
          *
@@ -994,7 +1097,7 @@
         }
 
         /**
-         * Notifies IAPP session when a channel is tuned.
+         * Notifies Interactive APP session when a channel is tuned.
          */
         public void notifyTuned(Uri channelUri) {
             if (mToken == null) {
@@ -1009,7 +1112,7 @@
         }
 
         /**
-         * Notifies IAPP session when a track is selected.
+         * Notifies Interactive APP session when a track is selected.
          */
         public void notifyTrackSelected(int type, String trackId) {
             if (mToken == null) {
@@ -1024,7 +1127,7 @@
         }
 
         /**
-         * Notifies IAPP session when tracks are changed.
+         * Notifies Interactive APP session when tracks are changed.
          */
         public void notifyTracksChanged(List<TvTrackInfo> tracks) {
             if (mToken == null) {
@@ -1098,6 +1201,21 @@
             }
         }
 
+        /**
+         * Notifies Interactive APP session when signal strength is changed.
+         */
+        public void notifySignalStrength(int strength) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifySignalStrength(mToken, strength, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private void flushPendingEventsLocked() {
             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
 
@@ -1359,7 +1477,8 @@
             });
         }
 
-        void postCommandRequest(final @TvIAppService.IAppServiceCommandType String cmdType,
+        void postCommandRequest(
+                final @TvIAppService.InteractiveAppServiceCommandType String cmdType,
                 final Bundle parameters) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1414,6 +1533,15 @@
             });
         }
 
+        void postRequestCurrentTvInputId() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestCurrentTvInputId(mSession);
+                }
+            });
+        }
+
         void postAdRequest(final AdRequest request) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1442,6 +1570,15 @@
                 }
             });
         }
+
+        void postTeletextAppStateChanged(int state) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onTeletextAppStateChanged(mSession, state);
+                }
+            });
+        }
     }
 
     /**
@@ -1452,8 +1589,8 @@
         /**
          * This is called after {@link TvIAppManager#createSession} has been processed.
          *
-         * @param session A {@link TvIAppManager.Session} instance created. This can be {@code null}
-         *                if the creation request failed.
+         * @param session A {@link TvIAppManager.Session} instance created. This can be
+         *                {@code null} if the creation request failed.
          */
         public void onSessionCreated(@Nullable Session session) {
         }
@@ -1468,8 +1605,8 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#layoutSurface} is called to change the
-         * layout of surface.
+         * This is called when {@link TvIAppService.Session#layoutSurface} is called to
+         * change the layout of surface.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
          * @param left Left position.
@@ -1487,8 +1624,10 @@
          * @param cmdType type of the command.
          * @param parameters parameters of the command.
          */
-        public void onCommandRequest(Session session,
-                @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+        public void onCommandRequest(
+                Session session,
+                @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                Bundle parameters) {
         }
 
         /**
@@ -1500,7 +1639,8 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is called.
+         * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is
+         * called.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
          */
@@ -1508,7 +1648,8 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is called.
+         * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is
+         * called.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
          */
@@ -1516,7 +1657,8 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestStreamVolume} is called.
+         * This is called when {@link TvIAppService.Session#RequestStreamVolume} is
+         * called.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
          */
@@ -1524,7 +1666,8 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is called.
+         * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is
+         * called.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
          */
@@ -1532,6 +1675,15 @@
         }
 
         /**
+         * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
+         *
+         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @hide
+         */
+        public void onRequestCurrentTvInputId(Session session) {
+        }
+
+        /**
          * This is called when {@link TvIAppService.Session#notifySessionStateChanged} is called.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
@@ -1541,8 +1693,8 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated} is
-         * called.
+         * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated}
+         * is called.
          *
          * @param session A {@link TvIAppManager.Session} associated with this callback.
          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
@@ -1552,5 +1704,16 @@
          */
         public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) {
         }
+
+        /**
+         * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is
+         * called.
+         *
+         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param state the current state.
+         */
+        public void onTeletextAppStateChanged(
+                Session session, @TvIAppManager.TeletextAppState int state) {
+        }
     }
 }
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
old mode 100644
new mode 100755
index 4993bc3..c0ec76b
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -32,6 +32,7 @@
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -75,45 +76,48 @@
     // TODO: cleanup and unhide APIs.
 
     /**
-     * This is the interface name that a service implementing a TV IApp service should say that it
-     * supports -- that is, this is the action it uses for its intent filter. To be supported, the
-     * service must also require the android.Manifest.permission#BIND_TV_IAPP permission so
-     * that other applications cannot abuse it.
+     * This is the interface name that a service implementing a TV Interactive App service should
+     * say that it supports -- that is, this is the action it uses for its intent filter. To be
+     * supported, the service must also require the
+     * android.Manifest.permission#BIND_TV_INTERACTIVE_APP permission so that other applications
+     * cannot abuse it.
      */
-    public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService";
+    public static final String SERVICE_INTERFACE =
+            "android.media.tv.interactive.TvIAppService";
 
     /**
-     * Name under which a TvIAppService component publishes information about itself. This meta-data
-     * must reference an XML resource containing an
-     * <code>&lt;{@link android.R.styleable#TvIAppService tv-iapp}&gt;</code>
+     * Name under which a TvIAppService component publishes information about itself. This
+     * meta-data must reference an XML resource containing an
+     * <code>&lt;{@link android.R.styleable#TvIAppService tv-interactive-app}&gt;</code>
      * tag.
      */
     public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = "IAPP_SERVICE_COMMAND_TYPE_", value = {
-            IAPP_SERVICE_COMMAND_TYPE_TUNE,
-            IAPP_SERVICE_COMMAND_TYPE_TUNE_NEXT,
-            IAPP_SERVICE_COMMAND_TYPE_TUNE_PREV,
-            IAPP_SERVICE_COMMAND_TYPE_STOP,
-            IAPP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME,
-            IAPP_SERVICE_COMMAND_TYPE_SELECT_TRACK
+    @StringDef(prefix = "INTERACTIVE_APP_SERVICE_COMMAND_TYPE_", value = {
+            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE,
+            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT,
+            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV,
+            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP,
+            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME,
+            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK
     })
-    public @interface IAppServiceCommandType {}
+    public @interface InteractiveAppServiceCommandType {}
 
     /** @hide */
-    public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE = "tune";
+    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE = "tune";
     /** @hide */
-    public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next";
+    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next";
     /** @hide */
-    public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous";
+    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous";
     /** @hide */
-    public static final String IAPP_SERVICE_COMMAND_TYPE_STOP = "stop";
+    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP = "stop";
     /** @hide */
-    public static final String IAPP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME = "set_stream_volume";
+    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME =
+            "set_stream_volume";
     /** @hide */
-    public static final String IAPP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track";
+    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track";
     /** @hide */
     public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
     /** @hide */
@@ -129,29 +133,29 @@
             "command_track_select_mode";
 
     private final Handler mServiceHandler = new ServiceHandler();
-    private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks =
+    private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
             new RemoteCallbackList<>();
 
     /** @hide */
     @Override
     public final IBinder onBind(Intent intent) {
-        ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() {
+        ITvInteractiveAppService.Stub tvIAppServiceBinder = new ITvInteractiveAppService.Stub() {
             @Override
-            public void registerCallback(ITvIAppServiceCallback cb) {
+            public void registerCallback(ITvInteractiveAppServiceCallback cb) {
                 if (cb != null) {
                     mCallbacks.register(cb);
                 }
             }
 
             @Override
-            public void unregisterCallback(ITvIAppServiceCallback cb) {
+            public void unregisterCallback(ITvInteractiveAppServiceCallback cb) {
                 if (cb != null) {
                     mCallbacks.unregister(cb);
                 }
             }
 
             @Override
-            public void createSession(InputChannel channel, ITvIAppSessionCallback cb,
+            public void createSession(InputChannel channel, ITvInteractiveAppSessionCallback cb,
                     String iAppServiceId, int type) {
                 if (cb == null) {
                     return;
@@ -171,8 +175,13 @@
             }
 
             @Override
-            public void notifyAppLinkInfo(Bundle appLinkInfo) {
-                onAppLinkInfo(appLinkInfo);
+            public void registerAppLinkInfo(Bundle appLinkInfo) {
+                onRegisterAppLinkInfo(appLinkInfo);
+            }
+
+            @Override
+            public void unregisterAppLinkInfo(Bundle appLinkInfo) {
+                onUnregisterAppLinkInfo(appLinkInfo);
             }
 
             @Override
@@ -184,7 +193,7 @@
     }
 
     /**
-     * Prepares TV IApp service for the given type.
+     * Prepares TV Interactive App service for the given type.
      * @hide
      */
     public void onPrepare(int type) {
@@ -195,7 +204,15 @@
      * Registers App link info.
      * @hide
      */
-    public void onAppLinkInfo(Bundle appLinkInfo) {
+    public void onRegisterAppLinkInfo(Bundle appLinkInfo) {
+        // TODO: make it abstract when unhide
+    }
+
+    /**
+     * Unregisters App link info.
+     * @hide
+     */
+    public void onUnregisterAppLinkInfo(Bundle appLinkInfo) {
         // TODO: make it abstract when unhide
     }
 
@@ -211,11 +228,11 @@
     /**
      * Returns a concrete implementation of {@link Session}.
      *
-     * <p>May return {@code null} if this TV IApp service fails to create a session for some
-     * reason.
+     * <p>May return {@code null} if this TV Interactive App service fails to create a session for
+     * some reason.
      *
-     * @param iAppServiceId The ID of the TV IApp associated with the session.
-     * @param type The type of the TV IApp associated with the session.
+     * @param iAppServiceId The ID of the TV Interactive App associated with the session.
+     * @param type The type of the TV Interactive App associated with the session.
      * @hide
      */
     @Nullable
@@ -229,7 +246,8 @@
      * @param state the current state
      * @hide
      */
-    public final void notifyStateChanged(int type, @TvIAppManager.TvIAppRteState int state) {
+    public final void notifyStateChanged(
+            int type, @TvIAppManager.TvInteractiveAppRteState int state) {
         mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED,
                 type, state).sendToTarget();
     }
@@ -243,7 +261,7 @@
 
         private final Object mLock = new Object();
         // @GuardedBy("mLock")
-        private ITvIAppSessionCallback mSessionCallback;
+        private ITvInteractiveAppSessionCallback mSessionCallback;
         // @GuardedBy("mLock")
         private final List<Runnable> mPendingActions = new ArrayList<>();
 
@@ -276,7 +294,7 @@
          * <p>By default, the media view is disabled. Must be called explicitly after the
          * session is created to enable the media view.
          *
-         * <p>The TV IApp service can disable its media view when needed.
+         * <p>The TV Interactive App service can disable its media view when needed.
          *
          * @param enable {@code true} if you want to enable the media view. {@code false}
          *            otherwise.
@@ -304,14 +322,21 @@
          * Starts TvIAppService session.
          * @hide
          */
-        public void onStartIApp() {
+        public void onStartInteractiveApp() {
         }
 
         /**
          * Stops TvIAppService session.
          * @hide
          */
-        public void onStopIApp() {
+        public void onStopInteractiveApp() {
+        }
+
+        /**
+         * Resets TvIAppService session.
+         * @hide
+         */
+        public void onResetInteractiveApp() {
         }
 
         /**
@@ -337,6 +362,13 @@
         }
 
         /**
+         * To toggle Digital Teletext Application if there is one in AIT app list.
+         * @param enable
+         */
+        public void onSetTeletextAppEnabled(boolean enable) {
+        }
+
+        /**
          * Receives current channel URI.
          * @hide
          */
@@ -365,11 +397,18 @@
         }
 
         /**
+         * Receives current TV input ID.
+         * @hide
+         */
+        public void onCurrentTvInputId(@Nullable String inputId) {
+        }
+
+        /**
          * Called when the application sets the surface.
          *
-         * <p>The TV IApp service should render interactive app UI onto the given surface. When
-         * called with {@code null}, the IApp service should immediately free any references to the
-         * currently set surface and stop using it.
+         * <p>The TV Interactive App service should render interactive app UI onto the given
+         * surface. When called with {@code null}, the Interactive App service should immediately
+         * free any references to the currently set surface and stop using it.
          *
          * @param surface The surface to be used for interactive app UI rendering. Can be
          *                {@code null}.
@@ -394,8 +433,8 @@
          *
          * <p>This is always called at least once when the session is created regardless of whether
          * the media view is enabled or not. The media view container size is the same as the
-         * containing {@link TvIAppView}. Note that the size of the underlying surface can be
-         * different if the surface was changed by calling {@link #layoutSurface}.
+         * containing {@link TvInteractiveAppView}. Note that the size of the underlying surface can
+         * be different if the surface was changed by calling {@link #layoutSurface}.
          *
          * @param width The width of the media view.
          * @param height The height of the media view.
@@ -471,6 +510,13 @@
         }
 
         /**
+         * Called when signal strength is changed.
+         * @hide
+         */
+        public void onSignalStrength(@TvInputManager.SignalStrength int strength) {
+        }
+
+        /**
          * Called when a broadcast info response is received.
          * @hide
          */
@@ -624,7 +670,8 @@
          * @param cmdType type of the specific command
          * @param parameters parameters of the specific command
          */
-        public void requestCommand(@IAppServiceCommandType String cmdType, Bundle parameters) {
+        public void requestCommand(
+                @InteractiveAppServiceCommandType String cmdType, Bundle parameters) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -755,6 +802,31 @@
         }
 
         /**
+         * Requests current TV input ID.
+         *
+         * @see android.media.tv.TvInputInfo
+         * @hide
+         */
+        public void requestCurrentTvInputId() {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestCurrentTvInputId");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestCurrentTvInputId();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestCurrentTvInputId", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * requests an advertisement request to be processed by the related TV input.
          * @param request advertisement request
          */
@@ -777,12 +849,16 @@
             });
         }
 
-        void startIApp() {
-            onStartIApp();
+        void startInteractiveApp() {
+            onStartInteractiveApp();
         }
 
-        void stopIApp() {
-            onStopIApp();
+        void stopInteractiveApp() {
+            onStopInteractiveApp();
+        }
+
+        void resetInteractiveApp() {
+            onResetInteractiveApp();
         }
 
         void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
@@ -793,6 +869,10 @@
             onDestroyBiInteractiveApp(biIAppId);
         }
 
+        void setTeletextAppEnabled(boolean enable) {
+            onSetTeletextAppEnabled(enable);
+        }
+
         void sendCurrentChannelUri(@Nullable Uri channelUri) {
             onCurrentChannelUri(channelUri);
         }
@@ -809,6 +889,10 @@
             onTrackInfoList(tracks);
         }
 
+        void sendCurrentTvInputId(@Nullable String inputId) {
+            onCurrentTvInputId(inputId);
+        }
+
         void release() {
             onRelease();
             if (mSurface != null) {
@@ -873,6 +957,13 @@
             onContentBlocked(rating);
         }
 
+        void notifySignalStrength(int strength) {
+            if (DEBUG) {
+                Log.d(TAG, "notifySignalStrength (strength=" + strength + ")");
+            }
+            onSignalStrength(strength);
+        }
+
         /**
          * Calls {@link #onBroadcastInfoResponse}.
          */
@@ -898,7 +989,8 @@
          * Notifies when the session state is changed.
          * @param state the current state.
          */
-        public void notifySessionStateChanged(@TvIAppManager.TvIAppRteState int state) {
+        public void notifySessionStateChanged(
+                @TvIAppManager.TvInteractiveAppRteState int state) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -945,6 +1037,30 @@
         }
 
         /**
+         * Notifies when the digital teletext app state is changed.
+         * @param state the current state.
+         */
+        public final void notifyTeletextAppStateChanged(@TvIAppManager.TeletextAppState int state) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "notifyTeletextAppState (state="
+                                    + state + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onTeletextAppStateChanged(state);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyTeletextAppState", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -977,7 +1093,7 @@
             return TvIAppManager.Session.DISPATCH_NOT_HANDLED;
         }
 
-        private void initialize(ITvIAppSessionCallback callback) {
+        private void initialize(ITvInteractiveAppSessionCallback callback) {
             synchronized (mLock) {
                 mSessionCallback = callback;
                 for (Runnable runnable : mPendingActions) {
@@ -1087,8 +1203,8 @@
             if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
             if (mMediaFrame == null || mMediaFrame.width() != frame.width()
                     || mMediaFrame.height() != frame.height()) {
-                // Note: relayoutMediaView is called whenever TvIAppView's layout is changed
-                // regardless of setMediaViewEnabled.
+                // Note: relayoutMediaView is called whenever TvInteractiveAppView's layout is
+                // changed regardless of setMediaViewEnabled.
                 onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
             }
             mMediaFrame = frame;
@@ -1159,31 +1275,37 @@
     }
 
     /**
-     * Implements the internal ITvIAppSession interface.
+     * Implements the internal ITvInteractiveAppSession interface.
      * @hide
      */
-    public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub {
-        // TODO: put ITvIAppSessionWrapper in a separate Java file
+    public static class ITvInteractiveAppSessionWrapper extends ITvInteractiveAppSession.Stub {
+        // TODO: put ITvInteractiveAppSessionWrapper in a separate Java file
         private final Session mSessionImpl;
         private InputChannel mChannel;
-        private TvIAppEventReceiver mReceiver;
+        private TvInteractiveAppEventReceiver mReceiver;
 
-        public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) {
+        public ITvInteractiveAppSessionWrapper(
+                Context context, Session mSessionImpl, InputChannel channel) {
             this.mSessionImpl = mSessionImpl;
             mChannel = channel;
             if (channel != null) {
-                mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper());
+                mReceiver = new TvInteractiveAppEventReceiver(channel, context.getMainLooper());
             }
         }
 
         @Override
-        public void startIApp() {
-            mSessionImpl.startIApp();
+        public void startInteractiveApp() {
+            mSessionImpl.startInteractiveApp();
         }
 
         @Override
-        public void stopIApp() {
-            mSessionImpl.stopIApp();
+        public void stopInteractiveApp() {
+            mSessionImpl.stopInteractiveApp();
+        }
+
+        @Override
+        public void resetInteractiveApp() {
+            mSessionImpl.resetInteractiveApp();
         }
 
         @Override
@@ -1192,6 +1314,11 @@
         }
 
         @Override
+        public void setTeletextAppEnabled(boolean enable) {
+            mSessionImpl.setTeletextAppEnabled(enable);
+        }
+
+        @Override
         public void destroyBiInteractiveApp(@NonNull String biIAppId) {
             mSessionImpl.destroyBiInteractiveApp(biIAppId);
         }
@@ -1217,6 +1344,11 @@
         }
 
         @Override
+        public void sendCurrentTvInputId(@Nullable String inputId) {
+            mSessionImpl.sendCurrentTvInputId(inputId);
+        }
+
+        @Override
         public void release() {
             mSessionImpl.scheduleMediaViewCleanup();
             mSessionImpl.release();
@@ -1258,6 +1390,11 @@
         }
 
         @Override
+        public void notifySignalStrength(int strength) {
+            mSessionImpl.notifySignalStrength(strength);
+        }
+
+        @Override
         public void setSurface(Surface surface) {
             mSessionImpl.setSurface(surface);
         }
@@ -1292,8 +1429,8 @@
             mSessionImpl.removeMediaView(true);
         }
 
-        private final class TvIAppEventReceiver extends InputEventReceiver {
-            TvIAppEventReceiver(InputChannel inputChannel, Looper looper) {
+        private final class TvInteractiveAppEventReceiver extends InputEventReceiver {
+            TvInteractiveAppEventReceiver(InputChannel inputChannel, Looper looper) {
                 super(inputChannel, looper);
             }
 
@@ -1307,7 +1444,8 @@
 
                 int handled = mSessionImpl.dispatchInputEvent(event, this);
                 if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) {
-                    finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED);
+                    finishInputEvent(
+                            event, handled == TvIAppManager.Session.DISPATCH_HANDLED);
                 }
             }
         }
@@ -1337,7 +1475,8 @@
                 case DO_CREATE_SESSION: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     InputChannel channel = (InputChannel) args.arg1;
-                    ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2;
+                    ITvInteractiveAppSessionCallback cb =
+                            (ITvInteractiveAppSessionCallback) args.arg2;
                     String iAppServiceId = (String) args.arg3;
                     int type = (int) args.arg4;
                     args.recycle();
@@ -1351,8 +1490,8 @@
                         }
                         return;
                     }
-                    ITvIAppSession stub = new ITvIAppSessionWrapper(
-                            TvIAppService.this, sessionImpl, channel);
+                    ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper(
+                            android.media.tv.interactive.TvIAppService.this, sessionImpl, channel);
 
                     SomeArgs someArgs = SomeArgs.obtain();
                     someArgs.arg1 = sessionImpl;
@@ -1365,8 +1504,9 @@
                 case DO_NOTIFY_SESSION_CREATED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Session sessionImpl = (Session) args.arg1;
-                    ITvIAppSession stub = (ITvIAppSession) args.arg2;
-                    ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg3;
+                    ITvInteractiveAppSession stub = (ITvInteractiveAppSession) args.arg2;
+                    ITvInteractiveAppSessionCallback cb =
+                            (ITvInteractiveAppSessionCallback) args.arg3;
                     try {
                         cb.onSessionCreated(stub);
                     } catch (RemoteException e) {
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl
similarity index 95%
rename from media/java/android/media/tv/interactive/TvIAppInfo.aidl
rename to media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl
index 6041460..5e15016 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.media.tv.interactive;
 
-parcelable TvIAppInfo;
\ No newline at end of file
+parcelable TvInteractiveAppInfo;
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
new file mode 100644
index 0000000..2f96552
--- /dev/null
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to specify meta information of a TV interactive app.
+ * @hide
+ */
+public final class TvInteractiveAppInfo implements Parcelable {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "TvInteractiveAppInfo";
+
+    private static final String XML_START_TAG_NAME = "tv-interactive-app";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = {
+            INTERACTIVE_APP_TYPE_HBBTV,
+            INTERACTIVE_APP_TYPE_ATSC,
+            INTERACTIVE_APP_TYPE_GINGA,
+    })
+    @interface InteractiveAppType {}
+
+    /** HbbTV interactive app type */
+    public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1;
+    /** ATSC interactive app type */
+    public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2;
+    /** Ginga interactive app type */
+    public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4;
+
+    private final ResolveInfo mService;
+    private final String mId;
+    private int mTypes;
+
+    public TvInteractiveAppInfo(@NonNull Context context, @NonNull ComponentName component) {
+        if (context == null) {
+            throw new IllegalArgumentException("context cannot be null.");
+        }
+        Intent intent =
+                new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+        ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent,
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+        if (resolveInfo == null) {
+            throw new IllegalArgumentException("Invalid component. Can't find the service.");
+        }
+
+        ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
+                resolveInfo.serviceInfo.name);
+        String id;
+        id = generateInteractiveAppServiceId(componentName);
+        List<String> types = new ArrayList<>();
+        parseServiceMetadata(resolveInfo, context, types);
+
+        mService = resolveInfo;
+        mId = id;
+        mTypes = toTypesFlag(types);
+    }
+    private TvInteractiveAppInfo(ResolveInfo service, String id, int types) {
+        mService = service;
+        mId = id;
+        mTypes = types;
+    }
+
+    private TvInteractiveAppInfo(@NonNull Parcel in) {
+        mService = ResolveInfo.CREATOR.createFromParcel(in);
+        mId = in.readString();
+        mTypes = in.readInt();
+    }
+
+    public static final @NonNull Creator<TvInteractiveAppInfo> CREATOR =
+            new Creator<TvInteractiveAppInfo>() {
+                @Override
+                public TvInteractiveAppInfo createFromParcel(Parcel in) {
+                    return new TvInteractiveAppInfo(in);
+                }
+
+                @Override
+                public TvInteractiveAppInfo[] newArray(int size) {
+                    return new TvInteractiveAppInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mService.writeToParcel(dest, flags);
+        dest.writeString(mId);
+        dest.writeInt(mTypes);
+    }
+
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the component of the TV Interactive App service.
+     * @hide
+     */
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
+    }
+
+    /**
+     * Returns the information of the service that implements this TV Interactive App service.
+     */
+    @Nullable
+    public ServiceInfo getServiceInfo() {
+        return mService.serviceInfo;
+    }
+
+    /**
+     * Gets supported interactive app types
+     */
+    @InteractiveAppType
+    @NonNull
+    public int getSupportedTypes() {
+        return mTypes;
+    }
+
+    private static String generateInteractiveAppServiceId(ComponentName name) {
+        return name.flattenToShortString();
+    }
+
+    private static void parseServiceMetadata(
+            ResolveInfo resolveInfo, Context context, List<String> types) {
+        ServiceInfo si = resolveInfo.serviceInfo;
+        PackageManager pm = context.getPackageManager();
+        try (XmlResourceParser parser =
+                     si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) {
+            if (parser == null) {
+                throw new IllegalStateException(
+                        "No " + TvIAppService.SERVICE_META_DATA
+                        + " meta-data found for " + si.name);
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+                // move to the START_TAG
+            }
+
+            String nodeName = parser.getName();
+            if (!XML_START_TAG_NAME.equals(nodeName)) {
+                throw new IllegalStateException("Meta-data does not start with "
+                        + XML_START_TAG_NAME + " tag for " + si.name);
+            }
+
+            TypedArray sa = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.TvIAppService);
+            CharSequence[] textArr = sa.getTextArray(
+                    com.android.internal.R.styleable.TvIAppService_supportedTypes);
+            for (CharSequence cs : textArr) {
+                types.add(cs.toString().toLowerCase());
+            }
+
+            sa.recycle();
+        } catch (IOException | XmlPullParserException e) {
+            throw new IllegalStateException(
+                    "Failed reading meta-data for " + si.packageName, e);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("No resources found for " + si.packageName, e);
+        }
+    }
+
+    private static int toTypesFlag(List<String> types) {
+        int flag = 0;
+        for (String type : types) {
+            switch (type) {
+                case "hbbtv":
+                    flag |= INTERACTIVE_APP_TYPE_HBBTV;
+                    break;
+                case "atsc":
+                    flag |= INTERACTIVE_APP_TYPE_ATSC;
+                    break;
+                case "ginga":
+                    flag |= INTERACTIVE_APP_TYPE_GINGA;
+                    break;
+                default:
+                    break;
+            }
+        }
+        return flag;
+    }
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
old mode 100644
new mode 100755
similarity index 72%
rename from media/java/android/media/tv/interactive/TvIAppView.java
rename to media/java/android/media/tv/interactive/TvInteractiveAppView.java
index b295055..6f99d51
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.interactive;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -45,13 +46,14 @@
 import android.view.ViewRootImpl;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Displays contents of interactive TV applications.
  * @hide
  */
-public class TvIAppView extends ViewGroup {
-    private static final String TAG = "TvIAppView";
+public class TvInteractiveAppView extends ViewGroup {
+    private static final String TAG = "TvInteractiveAppView";
     private static final boolean DEBUG = false;
 
     private static final int SET_TVVIEW_SUCCESS = 1;
@@ -59,11 +61,13 @@
     private static final int UNSET_TVVIEW_SUCCESS = 3;
     private static final int UNSET_TVVIEW_FAIL = 4;
 
-    private final TvIAppManager mTvIAppManager;
+    private final TvIAppManager mTvInteractiveAppManager;
     private final Handler mHandler = new Handler();
+    private final Object mCallbackLock = new Object();
     private Session mSession;
     private MySessionCallback mSessionCallback;
-    private TvIAppCallback mCallback;
+    private TvInteractiveAppCallback mCallback;
+    private Executor mCallbackExecutor;
     private SurfaceView mSurfaceView;
     private Surface mSurface;
 
@@ -114,15 +118,16 @@
         }
     };
 
-    public TvIAppView(@NonNull Context context) {
+    public TvInteractiveAppView(@NonNull Context context) {
         this(context, null, 0);
     }
 
-    public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs) {
+    public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+    public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         int sourceResId = Resources.getAttributeSetSourceResId(attrs);
         if (sourceResId != Resources.ID_NULL) {
@@ -136,31 +141,50 @@
         }
         mDefStyleAttr = defStyleAttr;
         resetSurfaceView();
-        mTvIAppManager = (TvIAppManager) getContext().getSystemService(Context.TV_IAPP_SERVICE);
+        mTvInteractiveAppManager = (TvIAppManager) getContext().getSystemService(
+                Context.TV_IAPP_SERVICE);
     }
 
     /**
-     * Sets the callback to be invoked when an event is dispatched to this TvIAppView.
+     * Sets the callback to be invoked when an event is dispatched to this TvInteractiveAppView.
      *
      * @param callback The callback to receive events. A value of {@code null} removes the existing
-     *            callback.
+     *                 callback.
      */
-    public void setCallback(@Nullable TvIAppCallback callback) {
-        mCallback = callback;
+    public void setCallback(
+            @NonNull TvInteractiveAppCallback callback,
+            @NonNull @CallbackExecutor Executor executor) {
+        synchronized (mCallbackLock) {
+            mCallbackExecutor = executor;
+            mCallback = callback;
+        }
     }
 
+    /**
+     * Clears the callback.
+     */
+    public void clearCallback() {
+        synchronized (mCallbackLock) {
+            mCallback = null;
+            mCallbackExecutor = null;
+        }
+    }
+
+    /** @hide */
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         createSessionMediaView();
     }
 
+    /** @hide */
     @Override
     protected void onDetachedFromWindow() {
         removeSessionMediaView();
         super.onDetachedFromWindow();
     }
 
+    /** @hide */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
@@ -175,6 +199,7 @@
         }
     }
 
+    /** @hide */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
@@ -186,6 +211,7 @@
                         childState << MEASURED_HEIGHT_STATE_SHIFT));
     }
 
+    /** @hide */
     @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
@@ -216,7 +242,8 @@
     }
 
     /**
-     * Resets this TvIAppView.
+     * Resets this TvInteractiveAppView.
+     * @hide
      */
     public void reset() {
         if (DEBUG) Log.d(TAG, "reset()");
@@ -302,6 +329,7 @@
 
     /**
      * Dispatches an unhandled input event to the next receiver.
+     * @hide
      */
     public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
         if (mOnUnhandledInputEventListener != null) {
@@ -314,11 +342,13 @@
 
     /**
      * Called when an unhandled input event also has not been handled by the user provided
-     * callback. This is the last chance to handle the unhandled input event in the TvIAppView.
+     * callback. This is the last chance to handle the unhandled input event in the
+     * TvInteractiveAppView.
      *
      * @param event The input event.
      * @return If you handled the event, return {@code true}. If you want to allow the event to be
      *         handled by the next receiver, return {@code false}.
+     * @hide
      */
     public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
         return false;
@@ -329,6 +359,7 @@
      * by the TV Interactive App.
      *
      * @param listener The callback to be invoked when the unhandled input event is received.
+     * @hide
      */
     public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
         mOnUnhandledInputEventListener = listener;
@@ -350,44 +381,60 @@
 
     /**
      * Prepares the interactive application.
+     * @hide
      */
-    public void prepareIApp(@NonNull String iAppServiceId, int type) {
+    public void prepareInteractiveApp(@NonNull String iAppServiceId, int type) {
         // TODO: document and handle the cases that this method is called multiple times.
         if (DEBUG) {
-            Log.d(TAG, "prepareIApp");
+            Log.d(TAG, "prepareInteractiveApp");
         }
         mSessionCallback = new MySessionCallback(iAppServiceId, type);
-        if (mTvIAppManager != null) {
-            mTvIAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler);
+        if (mTvInteractiveAppManager != null) {
+            mTvInteractiveAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler);
         }
     }
 
     /**
      * Starts the interactive application.
      */
-    public void startIApp() {
+    public void startInteractiveApp() {
         if (DEBUG) {
-            Log.d(TAG, "startIApp");
+            Log.d(TAG, "startInteractiveApp");
         }
         if (mSession != null) {
-            mSession.startIApp();
+            mSession.startInteractiveApp();
         }
     }
 
     /**
      * Stops the interactive application.
+     * @hide
      */
-    public void stopIApp() {
+    public void stopInteractiveApp() {
         if (DEBUG) {
-            Log.d(TAG, "stopIApp");
+            Log.d(TAG, "stopInteractiveApp");
         }
         if (mSession != null) {
-            mSession.stopIApp();
+            mSession.stopInteractiveApp();
+        }
+    }
+
+    /**
+     * Resets the interactive application.
+     * @hide
+     */
+    public void resetInteractiveApp() {
+        if (DEBUG) {
+            Log.d(TAG, "resetInteractiveApp");
+        }
+        if (mSession != null) {
+            mSession.resetInteractiveApp();
         }
     }
 
     /**
      * Sends current channel URI to related TV interactive app.
+     * @hide
      */
     public void sendCurrentChannelUri(Uri channelUri) {
         if (DEBUG) {
@@ -400,6 +447,7 @@
 
     /**
      * Sends current channel logical channel number (LCN) to related TV interactive app.
+     * @hide
      */
     public void sendCurrentChannelLcn(int lcn) {
         if (DEBUG) {
@@ -412,6 +460,7 @@
 
     /**
      * Sends stream volume to related TV interactive app.
+     * @hide
      */
     public void sendStreamVolume(float volume) {
         if (DEBUG) {
@@ -424,6 +473,7 @@
 
     /**
      * Sends track info list to related TV interactive app.
+     * @hide
      */
     public void sendTrackInfoList(List<TvTrackInfo> tracks) {
         if (DEBUG) {
@@ -434,6 +484,23 @@
         }
     }
 
+    /**
+     * Sends current TV input ID to related TV interactive app.
+     *
+     * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
+     *                tuned.
+     * @see android.media.tv.TvInputInfo
+     * @hide
+     */
+    public void sendCurrentTvInputId(@Nullable String inputId) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCurrentTvInputId");
+        }
+        if (mSession != null) {
+            mSession.sendCurrentTvInputId(inputId);
+        }
+    }
+
     private void resetInternal() {
         mSessionCallback = null;
         if (mSession != null) {
@@ -478,16 +545,18 @@
         }
     }
 
-    public Session getIAppSession() {
+    /** @hide */
+    public Session getInteractiveAppSession() {
         return mSession;
     }
 
     /**
-     * Sets the TvIAppView to receive events from TIS. This method links the session of
+     * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of
      * TvIAppManager to TvInputManager session, so the TIAS can get the TIS events.
      *
-     * @param tvView the TvView to be linked to this TvIAppView via linking of Sessions.
+     * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
      * @return to be added
+     * @hide
      */
     public int setTvView(@Nullable TvView tvView) {
         if (tvView == null) {
@@ -498,7 +567,7 @@
             return SET_TVVIEW_FAIL;
         }
         mSession.setInputSession(inputSession);
-        inputSession.setIAppSession(mSession);
+        inputSession.setInteractiveAppSession(mSession);
         return SET_TVVIEW_SUCCESS;
     }
 
@@ -506,15 +575,29 @@
         if (mSession == null || mSession.getInputSession() == null) {
             return UNSET_TVVIEW_FAIL;
         }
-        mSession.getInputSession().setIAppSession(null);
+        mSession.getInputSession().setInteractiveAppSession(null);
         mSession.setInputSession(null);
         return UNSET_TVVIEW_SUCCESS;
     }
 
     /**
-     * Callback used to receive various status updates on the {@link TvIAppView}.
+     * To toggle Digital Teletext Application if there is one in AIT app list.
+     * @param enable
      */
-    public abstract static class TvIAppCallback {
+    public void setTeletextAppEnabled(boolean enable) {
+        if (DEBUG) {
+            Log.d(TAG, "setTeletextAppEnabled enable=" + enable);
+        }
+        if (mSession != null) {
+            mSession.setTeletextAppEnabled(enable);
+        }
+    }
+
+    /**
+     * Callback used to receive various status updates on the {@link TvInteractiveAppView}.
+     */
+    public abstract static class TvInteractiveAppCallback {
+        // TODO: unhide the following public APIs
 
         /**
          * This is called when a command is requested to be processed by the related TV input.
@@ -522,10 +605,11 @@
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param cmdType type of the command
          * @param parameters parameters of the command
+         * @hide
          */
         public void onCommandRequest(
                 @NonNull String iAppServiceId,
-                @NonNull @TvIAppService.IAppServiceCommandType String cmdType,
+                @NonNull @TvIAppService.InteractiveAppServiceCommandType String cmdType,
                 @Nullable Bundle parameters) {
         }
 
@@ -534,6 +618,7 @@
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param state current session state.
+         * @hide
          */
         public void onSessionStateChanged(@NonNull String iAppServiceId, int state) {
         }
@@ -546,55 +631,84 @@
          *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
          *                 app.
+         * @hide
          */
         public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri,
                 @Nullable String biIAppId) {
         }
 
         /**
+         * This is called when the digital teletext app state is changed.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param state digital teletext app current state.
+         */
+        public void onTeletextAppStateChanged(
+                @NonNull String iAppServiceId, @TvIAppManager.TeletextAppState int state) {
+        }
+
+        /**
          * This is called when {@link TvIAppService.Session#SetVideoBounds} is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
          */
         public void onSetVideoBounds(@NonNull String iAppServiceId, @NonNull Rect rect) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is called.
+         * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is
+         * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
          */
         public void onRequestCurrentChannelUri(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is called.
+         * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is
+         * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
          */
         public void onRequestCurrentChannelLcn(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestStreamVolume} is called.
+         * This is called when {@link TvIAppService.Session#RequestStreamVolume} is
+         * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
          */
         public void onRequestStreamVolume(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is called.
+         * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is
+         * called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestTrackInfoList(@NonNull String iAppServiceId) {
+        }
+
+        /**
+         * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          */
-        public void onRequestTrackInfoList(@NonNull String iAppServiceId) {
+        public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) {
         }
 
     }
 
     /**
      * Interface definition for a callback to be invoked when the unhandled input event is received.
+     * @hide
      */
     public interface OnUnhandledInputEventListener {
         /**
@@ -685,8 +799,10 @@
         }
 
         @Override
-        public void onCommandRequest(Session session,
-                @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+        public void onCommandRequest(
+                Session session,
+                @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                Bundle parameters) {
             if (DEBUG) {
                 Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
                         + parameters.toString() + ")");
@@ -695,8 +811,16 @@
                 Log.w(TAG, "onCommandRequest - session not created");
                 return;
             }
-            if (mCallback != null) {
-                mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters);
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters);
+                            }
+                        }
+                    });
+                }
             }
         }
 
@@ -709,8 +833,16 @@
                 Log.w(TAG, "onSessionStateChanged - session not created");
                 return;
             }
-            if (mCallback != null) {
-                mCallback.onSessionStateChanged(mIAppServiceId, state);
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onSessionStateChanged(mIAppServiceId, state);
+                            }
+                        }
+                    });
+                }
             }
         }
 
@@ -724,8 +856,31 @@
                 Log.w(TAG, "onBiInteractiveAppCreated - session not created");
                 return;
             }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onBiInteractiveAppCreated(
+                                        mIAppServiceId, biIAppUri, biIAppId);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
+        public void onTeletextAppStateChanged(Session session, int state) {
+            if (DEBUG) {
+                Log.d(TAG, "onTeletextAppStateChanged (state=" + state +  ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onTeletextAppStateChanged - session not created");
+                return;
+            }
             if (mCallback != null) {
-                mCallback.onBiInteractiveAppCreated(mIAppServiceId, biIAppUri, biIAppId);
+                mCallback.onTeletextAppStateChanged(mIAppServiceId, state);
             }
         }
 
@@ -738,8 +893,16 @@
                 Log.w(TAG, "onSetVideoBounds - session not created");
                 return;
             }
-            if (mCallback != null) {
-                mCallback.onSetVideoBounds(mIAppServiceId, rect);
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onSetVideoBounds(mIAppServiceId, rect);
+                            }
+                        }
+                    });
+                }
             }
         }
 
@@ -752,8 +915,16 @@
                 Log.w(TAG, "onRequestCurrentChannelUri - session not created");
                 return;
             }
-            if (mCallback != null) {
-                mCallback.onRequestCurrentChannelUri(mIAppServiceId);
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestCurrentChannelUri(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
             }
         }
 
@@ -766,8 +937,16 @@
                 Log.w(TAG, "onRequestCurrentChannelLcn - session not created");
                 return;
             }
-            if (mCallback != null) {
-                mCallback.onRequestCurrentChannelLcn(mIAppServiceId);
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestCurrentChannelLcn(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
             }
         }
 
@@ -780,8 +959,16 @@
                 Log.w(TAG, "onRequestStreamVolume - session not created");
                 return;
             }
-            if (mCallback != null) {
-                mCallback.onRequestStreamVolume(mIAppServiceId);
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestStreamVolume(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
             }
         }
 
@@ -794,8 +981,30 @@
                 Log.w(TAG, "onRequestTrackInfoList - session not created");
                 return;
             }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestTrackInfoList(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
+        public void onRequestCurrentTvInputId(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestCurrentTvInputId");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestCurrentTvInputId - session not created");
+                return;
+            }
             if (mCallback != null) {
-                mCallback.onRequestTrackInfoList(mIAppServiceId);
+                mCallback.onRequestCurrentTvInputId(mIAppServiceId);
             }
         }
     }
diff --git a/media/java/android/media/tv/tunerresourcemanager/OWNER b/media/java/android/media/tv/tunerresourcemanager/OWNER
index 76b84d9..0eb1c31 100644
--- a/media/java/android/media/tv/tunerresourcemanager/OWNER
+++ b/media/java/android/media/tv/tunerresourcemanager/OWNER
@@ -1,4 +1,3 @@
-amyjojo@google.com
-nchalko@google.com
 quxiangfang@google.com
-shubang@google.com
\ No newline at end of file
+shubang@google.com
+kemiyagi@google.com
\ No newline at end of file
diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS
index 73ea663..7554889 100644
--- a/media/tests/TunerTest/OWNERS
+++ b/media/tests/TunerTest/OWNERS
@@ -1,4 +1,4 @@
-amyjojo@google.com
-nchalko@google.com
 quxiangfang@google.com
 shubang@google.com
+hgchen@google.com
+kemiyagi@google.com
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index cc7b2a5..f74edb1 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -142,7 +142,15 @@
         setAugmentWithSubscriptionPlan(true);
     }
 
-    /** @hide */
+    /**
+     * Set poll on open flag to indicate the poll is needed before service gets statistics
+     * result. This is default enabled. However, for any non-privileged caller, the poll might
+     * be omitted in case of rate limiting.
+     *
+     * @param pollOnOpen true if poll is needed.
+     * @hide
+     */
+    // @SystemApi(client = MODULE_LIBRARIES)
     public void setPollOnOpen(boolean pollOnOpen) {
         if (pollOnOpen) {
             mFlags |= FLAG_POLL_ON_OPEN;
@@ -863,4 +871,74 @@
             return msg.getData().getParcelable(key);
         }
     }
+
+    /**
+     * Mark given UID as being in foreground for stats purposes.
+     *
+     * @hide
+     */
+    // @SystemApi
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    public void setUidForeground(int uid, boolean uidForeground) {
+        try {
+            mService.setUidForeground(uid, uidForeground);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Advise persistence threshold; may be overridden internally.
+     *
+     * @hide
+     */
+    // @SystemApi
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    public void advisePersistThreshold(long thresholdBytes) {
+        try {
+            mService.advisePersistThreshold(thresholdBytes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Force update of statistics.
+     *
+     * @hide
+     */
+    // @SystemApi
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    public void forceUpdate() {
+        try {
+            mService.forceUpdate();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set the warning and limit to all registered custom network stats providers.
+     * Note that invocation of any interface will be sent to all providers.
+     *
+     * @hide
+     */
+    // @SystemApi
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    public void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
+            long limit) {
+        try {
+            mService.setStatsProviderWarningAndLimitAsync(iface, warning, limit);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
index 12937b5..a4babb5 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
@@ -94,4 +94,16 @@
     /** Registers a network stats provider */
     INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
             in INetworkStatsProvider provider);
+
+    /** Mark given UID as being in foreground for stats purposes. */
+    void setUidForeground(int uid, boolean uidForeground);
+
+    /** Advise persistence threshold; may be overridden internally. */
+    void advisePersistThreshold(long thresholdBytes);
+
+    /**
+     * Set the warning and limit to all registered custom network stats providers.
+     * Note that invocation of any interface will be sent to all providers.
+     */
+     void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit);
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
index 0d15dff..49aa99b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -17,6 +17,7 @@
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -25,7 +26,6 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.annotations.PolicyDirection;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -41,6 +41,8 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -88,6 +90,11 @@
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int DIRECTION_FWD = 2;
 
+    /** @hide */
+    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PolicyDirection {}
+
     /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 7935d28..9f9d73f 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -46,8 +46,6 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastDataInput;
-import com.android.internal.util.FastDataOutput;
 import com.android.internal.util.FileRotator;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkStatsUtils;
@@ -58,6 +56,7 @@
 import java.io.DataInput;
 import java.io.DataInputStream;
 import java.io.DataOutput;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -83,9 +82,6 @@
     /** File header magic number: "ANET" */
     private static final int FILE_MAGIC = 0x414E4554;
 
-    /** Default buffer size from BufferedInputStream */
-    private static final int BUFFER_SIZE = 8192;
-
     private static final int VERSION_NETWORK_INIT = 1;
 
     private static final int VERSION_UID_INIT = 1;
@@ -439,8 +435,7 @@
 
     @Override
     public void read(InputStream in) throws IOException {
-        final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE);
-        read(dataIn);
+        read((DataInput) new DataInputStream(in));
     }
 
     private void read(DataInput in) throws IOException {
@@ -479,9 +474,8 @@
 
     @Override
     public void write(OutputStream out) throws IOException {
-        final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE);
-        write(dataOut);
-        dataOut.flush();
+        write((DataOutput) new DataOutputStream(out));
+        out.flush();
     }
 
     private void write(DataOutput out) throws IOException {
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java
deleted file mode 100644
index 0e9a9da..0000000
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 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.net;
-
-import android.annotation.NonNull;
-import android.net.NetworkStats;
-import android.net.NetworkTemplate;
-
-public abstract class NetworkStatsManagerInternal {
-    /** Return network layer usage total for traffic that matches template. */
-    public abstract long getNetworkTotalBytes(NetworkTemplate template, long start, long end);
-
-    /** Return network layer usage per-UID for traffic that matches template. */
-    public abstract NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end);
-
-    /** Mark given UID as being in foreground for stats purposes. */
-    public abstract void setUidForeground(int uid, boolean uidForeground);
-
-    /** Advise persistance threshold; may be overridden internally. */
-    public abstract void advisePersistThreshold(long thresholdBytes);
-
-    /** Force update of statistics. */
-    public abstract void forceUpdate();
-
-    /**
-     * Set the warning and limit to all registered custom network stats providers.
-     * Note that invocation of any interface will be sent to all providers.
-     */
-    public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
-            long limit);
-}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 3273e88..e15acf3 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -96,6 +96,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkIdentity;
 import android.net.NetworkIdentitySet;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
 import android.net.NetworkStateSnapshot;
@@ -155,7 +156,6 @@
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkStatsUtils;
 import com.android.net.module.util.PermissionUtils;
-import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 
 import java.io.File;
@@ -209,6 +209,14 @@
 
     private static final String TAG_NETSTATS_ERROR = "netstats_error";
 
+    /**
+     * EventLog tags used when logging into the event log. Note the values must be sync with
+     * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+     * name translation.
+      */
+    private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100;
+    private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101;
+
     private final Context mContext;
     private final NetworkStatsFactory mStatsFactory;
     private final AlarmManager mAlarmManager;
@@ -423,7 +431,6 @@
                 new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd),
                 new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
                 new Dependencies());
-        service.registerLocalService();
 
         return service;
     }
@@ -504,11 +511,6 @@
         }
     }
 
-    private void registerLocalService() {
-        LocalServices.addService(NetworkStatsManagerInternal.class,
-                new NetworkStatsManagerInternalImpl());
-    }
-
     /**
      * Observer that watches for {@link INetdUnsolicitedEventListener} alerts.
      */
@@ -860,7 +862,7 @@
             if (LOGD) Log.d(TAG, "Resolving plan for " + template);
             final long token = Binder.clearCallingIdentity();
             try {
-                plan = LocalServices.getService(NetworkPolicyManagerInternal.class)
+                plan = mContext.getSystemService(NetworkPolicyManager.class)
                         .getSubscriptionPlan(template);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -999,7 +1001,8 @@
     }
 
     @VisibleForTesting
-    void setUidForeground(int uid, boolean uidForeground) {
+    public void setUidForeground(int uid, boolean uidForeground) {
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         synchronized (mStatsLock) {
             final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
             final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
@@ -1035,7 +1038,7 @@
 
     @Override
     public void forceUpdate() {
-        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -1045,7 +1048,9 @@
         }
     }
 
-    private void advisePersistThreshold(long thresholdBytes) {
+    /** Advise persistence threshold; may be overridden internally. */
+    public void advisePersistThreshold(long thresholdBytes) {
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         // clamp threshold into safe range
         mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes,
                 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
@@ -1624,7 +1629,7 @@
         xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
         uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
 
-        EventLogTags.writeNetstatsMobileSample(
+        EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE,
                 devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
                 xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
                 uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
@@ -1636,7 +1641,7 @@
         xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
         uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
 
-        EventLogTags.writeNetstatsWifiSample(
+        EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE,
                 devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
                 xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
                 uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
@@ -1682,52 +1687,19 @@
         removeUidsLocked(CollectionUtils.toIntArray(uids));
     }
 
-    private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
-        @Override
-        public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
-            Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes");
-            try {
-                return NetworkStatsService.this.getNetworkTotalBytes(template, start, end);
-            } finally {
-                Trace.traceEnd(TRACE_TAG_NETWORK);
-            }
+    /**
+     * Set the warning and limit to all registered custom network stats providers.
+     * Note that invocation of any interface will be sent to all providers.
+     */
+    public void setStatsProviderWarningAndLimitAsync(
+            @NonNull String iface, long warning, long limit) {
+        PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (LOGV) {
+            Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
+                    + iface + "," + warning + "," + limit + ")");
         }
-
-        @Override
-        public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
-            Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes");
-            try {
-                return NetworkStatsService.this.getNetworkUidBytes(template, start, end);
-            } finally {
-                Trace.traceEnd(TRACE_TAG_NETWORK);
-            }
-        }
-
-        @Override
-        public void setUidForeground(int uid, boolean uidForeground) {
-            NetworkStatsService.this.setUidForeground(uid, uidForeground);
-        }
-
-        @Override
-        public void advisePersistThreshold(long thresholdBytes) {
-            NetworkStatsService.this.advisePersistThreshold(thresholdBytes);
-        }
-
-        @Override
-        public void forceUpdate() {
-            NetworkStatsService.this.forceUpdate();
-        }
-
-        @Override
-        public void setStatsProviderWarningAndLimitAsync(
-                @NonNull String iface, long warning, long limit) {
-            if (LOGV) {
-                Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
-                        + iface + "," + warning + "," + limit + ")");
-            }
-            invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
-                    warning, limit));
-        }
+        invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
+                warning, limit));
     }
 
     @Override
@@ -2031,10 +2003,12 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
         Objects.requireNonNull(provider, "provider is null");
         Objects.requireNonNull(tag, "tag is null");
+        final NetworkPolicyManager netPolicyManager = mContext
+                .getSystemService(NetworkPolicyManager.class);
         try {
             NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
                     tag, provider, mStatsProviderSem, mAlertObserver,
-                    mStatsProviderCbList);
+                    mStatsProviderCbList, netPolicyManager);
             mStatsProviderCbList.add(callback);
             Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
                     + getCallingUid() + "/" + getCallingPid());
@@ -2076,6 +2050,7 @@
         @NonNull private final Semaphore mSemaphore;
         @NonNull final AlertObserver mAlertObserver;
         @NonNull final CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+        @NonNull final NetworkPolicyManager mNetworkPolicyManager;
 
         @NonNull private final Object mProviderStatsLock = new Object();
 
@@ -2089,7 +2064,8 @@
                 @NonNull String tag, @NonNull INetworkStatsProvider provider,
                 @NonNull Semaphore semaphore,
                 @NonNull AlertObserver alertObserver,
-                @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList)
+                @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList,
+                @NonNull NetworkPolicyManager networkPolicyManager)
                 throws RemoteException {
             mTag = tag;
             mProvider = provider;
@@ -2097,6 +2073,7 @@
             mSemaphore = semaphore;
             mAlertObserver = alertObserver;
             mStatsProviderCbList = cbList;
+            mNetworkPolicyManager = networkPolicyManager;
         }
 
         @NonNull
@@ -2143,8 +2120,7 @@
         public void notifyWarningOrLimitReached() {
             Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
             BinderUtils.withCleanCallingIdentity(() ->
-                    LocalServices.getService(NetworkPolicyManagerInternal.class)
-                            .onStatsProviderWarningOrLimitReached(mTag));
+                    mNetworkPolicyManager.onStatsProviderWarningOrLimitReached());
         }
 
         @Override
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 4cdcca7..5222d8a 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -250,7 +250,7 @@
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Konektatuta daudenak"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Gailuaren xehetasunak"</string>
     <string name="adb_device_forget" msgid="193072400783068417">"Ahaztu"</string>
-    <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Gailuaren erreferentzia-gako digitala: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
+    <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Gailuaren aztarna digitala: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Ezin izan da konektatu"</string>
     <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Ziurtatu <xliff:g id="DEVICE_NAME">%1$s</xliff:g> sare berera konektatuta dagoela"</string>
     <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Parekatu gailuarekin"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 9490ede..4103a9f 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -209,7 +209,7 @@
     <string name="tts_status_checking" msgid="8026559918948285013">"Tarkistetaan…"</string>
     <string name="tts_engine_settings_title" msgid="7849477533103566291">"Asetukset: <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
     <string name="tts_engine_settings_button" msgid="477155276199968948">"Käynnistä moottorin asetukset"</string>
-    <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen kone"</string>
+    <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen moottori"</string>
     <string name="tts_general_section_title" msgid="8919671529502364567">"Yleiset"</string>
     <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Palauta äänenkorkeus"</string>
     <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Palauta tekstin lukemisen oletusäänenkorkeus"</string>
@@ -220,8 +220,8 @@
     <item msgid="1158955023692670059">"Nopea"</item>
     <item msgid="5664310435707146591">"Nopeampi"</item>
     <item msgid="5491266922147715962">"Hyvin nopea"</item>
-    <item msgid="7659240015901486196">"Nopea"</item>
-    <item msgid="7147051179282410945">"Erittäin nopea"</item>
+    <item msgid="7659240015901486196">"Vauhdikas"</item>
+    <item msgid="7147051179282410945">"Erittäin vauhdikas"</item>
     <item msgid="581904787661470707">"Nopein"</item>
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1303a62..8546b16 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -341,6 +341,9 @@
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
 
+    <!-- Permission needed to test wallpaper dimming -->
+    <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
+
     <!-- Permission required to test ContentResolver caching. -->
     <uses-permission android:name="android.permission.CACHE_CONTENT" />
 
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
similarity index 70%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
index 6041460..dc804ca 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package android.media.tv.interactive;
+package com.android.systemui.util;
 
-parcelable TvIAppInfo;
\ No newline at end of file
+/** Constants that vary by compilation configuration. */
+public class Compile {
+    /** Whether SystemUI was compiled in debug mode, and supports debug features */
+    public static final boolean IS_DEBUG = true;
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/packages/SystemUI/src-release/com/android/systemui/util/Compile.java
similarity index 70%
copy from media/java/android/media/tv/interactive/TvIAppInfo.aidl
copy to packages/SystemUI/src-release/com/android/systemui/util/Compile.java
index 6041460..8a63763 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl
+++ b/packages/SystemUI/src-release/com/android/systemui/util/Compile.java
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package android.media.tv.interactive;
+package com.android.systemui.util;
 
-parcelable TvIAppInfo;
\ No newline at end of file
+/** Constants that vary by compilation configuration. */
+public class Compile {
+    /** Whether SystemUI was compiled in debug mode, and supports debug features */
+    public static final boolean IS_DEBUG = false;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index a100cb8..9c2971c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -96,6 +96,8 @@
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -107,7 +109,7 @@
  * for antialiasing and emulation purposes.
  */
 @SysUISingleton
-public class ScreenDecorations extends CoreStartable implements Tunable {
+public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable{
     private static final boolean DEBUG = false;
     private static final String TAG = "ScreenDecorations";
 
@@ -677,6 +679,20 @@
         });
     }
 
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("ScreenDecorations state:");
+        pw.println("  DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
+        pw.println("  mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius);
+        pw.println("  mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled);
+        pw.println("  mPendingRotationChange:" + mPendingRotationChange);
+        pw.println("  mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")");
+        pw.println("  mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y
+                + ")");
+        pw.println("  mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + ","
+                + mRoundedDefaultBottom.y + ")");
+    }
+
     private void updateOrientation() {
         Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
                 "must call on " + mHandler.getLooper().getThread()
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 63962fa..daca918 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -32,6 +32,9 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Dumpable;
+import android.util.DumpableContainer;
 import android.util.Log;
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
@@ -53,13 +56,19 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements
-        SystemUIAppComponentFactory.ContextInitializer {
+        SystemUIAppComponentFactory.ContextInitializer, DumpableContainer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
 
     private ContextComponentHelper mComponentHelper;
     private BootCompleteCacheImpl mBootCompleteCache;
+    private DumpManager mDumpManager;
+
+    /**
+     * Map of dumpables added externally.
+     */
+    private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
 
     /**
      * Hold a reference on the stuff we start.
@@ -214,7 +223,7 @@
             }
         }
 
-        final DumpManager dumpManager = mSysUIComponent.createDumpManager();
+        mDumpManager = mSysUIComponent.createDumpManager();
 
         Log.v(TAG, "Starting SystemUI services for user " +
                 Process.myUserHandle().getIdentifier() + ".");
@@ -255,7 +264,7 @@
                 mServices[i].onBootCompleted();
             }
 
-            dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
+            mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
         }
         mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
@@ -263,6 +272,29 @@
         mServicesStarted = true;
     }
 
+    // TODO(b/149254050): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
+    @Override
+    public boolean addDumpable(Dumpable dumpable) {
+        String name = dumpable.getDumpableName();
+        if (mDumpables.containsKey(name)) {
+            // This is normal because SystemUIApplication is an application context that is shared
+            // among multiple components
+            if (DEBUG) {
+                Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
+                        + " with that name (" + name + "): " + mDumpables.get(name));
+            }
+            return false;
+        }
+        if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable);
+        mDumpables.put(name, dumpable);
+
+        // TODO(b/149254050): replace com.android.systemui.dump.Dumpable by
+        // com.android.util.Dumpable and get rid of the intermediate lambda
+        mDumpManager.registerDumpable(dumpable.getDumpableName(),
+                (fd, pw, args) -> dumpable.dump(pw, args));
+        return true;
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index c46ffa0..b24d08d 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcelable;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -224,10 +225,12 @@
     }
 
     public void reloadFragments() {
+        Trace.beginSection("FrargmentHostManager#reloadFragments");
         // Save the old state.
         Parcelable p = destroyFragmentHost();
         // Generate a new fragment host and restore its state.
         createFragmentHost(p);
+        Trace.endSection();
     }
 
     class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index 2e1c9fa..474a81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -138,6 +138,7 @@
         }
         setWakefulness(WAKEFULNESS_AWAKE);
         dispatch(Observer::onFinishedWakingUp);
+        dispatch(Observer::onPostFinishedWakingUp);
     }
 
     public void dispatchStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) {
@@ -236,6 +237,12 @@
     public interface Observer {
         default void onStartedWakingUp() {}
         default void onFinishedWakingUp() {}
+
+        /**
+         * Called after the finished waking up call, ensuring it's after all the other listeners,
+         * reacting to {@link #onFinishedWakingUp()}
+         */
+        default void onPostFinishedWakingUp() {}
         default void onStartedGoingToSleep() {}
         default void onFinishedGoingToSleep() {}
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 41dced6..259b786 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -25,6 +25,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Trace;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
@@ -172,9 +173,14 @@
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
             Bundle savedInstanceState) {
-        inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
-                R.style.Theme_SystemUI_QuickSettings));
-        return inflater.inflate(R.layout.qs_panel, container, false);
+        try {
+            Trace.beginSection("QSFragment#onCreateView");
+            inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+                    R.style.Theme_SystemUI_QuickSettings));
+            return inflater.inflate(R.layout.qs_panel, container, false);
+        } finally {
+            Trace.endSection();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 648e14c..c136d9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -7,7 +7,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.os.SystemClock
-import android.util.DisplayMetrics
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
 import android.view.MotionEvent
@@ -24,6 +23,7 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.MediaHierarchyManager
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
@@ -64,6 +64,7 @@
     private val scrimController: ScrimController,
     private val depthController: NotificationShadeDepthController,
     private val context: Context,
+    wakefulnessLifecycle: WakefulnessLifecycle,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager,
     dumpManager: DumpManager,
@@ -120,6 +121,12 @@
     private var nextHideKeyguardNeedsNoAnimation = false
 
     /**
+     * Are we currently waking up to the shade locked
+     */
+    var isWakingToShadeLocked: Boolean = false
+        private set
+
+    /**
      * The distance until we're showing the notifications when pulsing
      */
     val distanceUntilShowingPulsingNotifications
@@ -160,6 +167,13 @@
                 }
             }
         })
+        wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
+            override fun onPostFinishedWakingUp() {
+                // when finishing waking up, the UnlockedScreenOffAnimation has another attempt
+                // to reset keyguard. Let's do it in post
+                isWakingToShadeLocked = false
+            }
+        })
     }
 
     private fun updateResources() {
@@ -494,6 +508,10 @@
             draggedDownEntry = entry
         } else {
             logger.logGoingToLockedShade(animationHandler != null)
+            if (statusBarStateController.isDozing) {
+                // Make sure we don't go back to keyguard immediately again after waking up
+                isWakingToShadeLocked = true
+            }
             statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
             // This call needs to be after updating the shade state since otherwise
             // the scrimstate resets too early
@@ -605,6 +623,7 @@
             it.println("qSDragProgress: $qSDragProgress")
             it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled")
             it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded")
+            it.println("isWakingToShadeLocked: $isWakingToShadeLocked")
             it.println("hasPendingHandlerOnKeyguardDismiss: " +
                 "${animationHandlerOnKeyguardDismiss != null}")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index ea51bd8..3fe108f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -107,8 +107,6 @@
     private var mDraggedFarEnough: Boolean = false
     private var mStartingChild: ExpandableView? = null
     private var mPulsing: Boolean = false
-    var isWakingToShadeLocked: Boolean = false
-        private set
 
     private var velocityTracker: VelocityTracker? = null
 
@@ -235,7 +233,6 @@
             mStartingChild = null
         }
         if (statusBarStateController.isDozing) {
-            isWakingToShadeLocked = true
             wakeUpCoordinator.willWakeUp = true
             mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
                     "com.android.systemui:PULSEDRAG")
@@ -333,10 +330,6 @@
         mPulsing = pulsing
     }
 
-    fun onStartedWakingUp() {
-        isWakingToShadeLocked = false
-    }
-
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         IndentingPrintWriter(pw, "  ").let {
             it.println("PulseExpansionHandler:")
@@ -344,7 +337,6 @@
             it.println("isExpanding: $isExpanding")
             it.println("leavingLockscreen: $leavingLockscreen")
             it.println("mPulsing: $mPulsing")
-            it.println("isWakingToShadeLocked: $isWakingToShadeLocked")
             it.println("qsExpanded: $qsExpanded")
             it.println("bouncerShowing: $bouncerShowing")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 09c608d..062e239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -59,6 +59,7 @@
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.Compile;
 import com.android.systemui.util.leak.LeakDetector;
 
 import java.io.FileDescriptor;
@@ -1011,7 +1012,7 @@
     }
 
     private static final String TAG = "NotificationEntryMgr";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     /**
      * Used when a notification is removed and it doesn't have a reason that maps to one of the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 5993f1d..3b93020 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -34,9 +34,9 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Compile;
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.io.FileDescriptor;
@@ -69,8 +69,8 @@
         Dumpable {
 
     private static final String TAG = "NotifGroupManager";
-    private static final boolean DEBUG = StatusBar.DEBUG;
-    private static final boolean SPEW = StatusBar.SPEW;
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     /**
      * The maximum amount of time (in ms) between the posting of notifications that can be
      * considered part of the same update batch.
@@ -384,9 +384,9 @@
         // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
         // * Only necessary when at least one notification in the group is on a priority channel
         if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
-                != Notification.GROUP_ALERT_SUMMARY) {
+                == Notification.GROUP_ALERT_CHILDREN) {
             if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY");
+                Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN");
             }
             return null;
         }
@@ -529,8 +529,10 @@
             mIsolatedEntries.put(entry.getKey(), entry.getSbn());
             if (groupKeysChanged) {
                 updateSuppression(mGroupMap.get(oldGroupKey));
-                updateSuppression(mGroupMap.get(newGroupKey));
             }
+            // Always update the suppression of the group from which you're isolated, in case
+            // this entry was or now is the alertOverride for that group.
+            updateSuppression(mGroupMap.get(newGroupKey));
         } else if (!wasGroupChild && isGroupChild) {
             onEntryBecomingChild(entry);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 433d5e1..2b4bc91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.Compile;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -52,8 +53,8 @@
 @SysUISingleton
 public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider {
     private static final String TAG = "InterruptionStateProvider";
-    private static final boolean DEBUG = true; //false;
-    private static final boolean DEBUG_HEADS_UP = true;
+    private static final boolean DEBUG = Compile.IS_DEBUG;
+    private static final boolean DEBUG_HEADS_UP = Compile.IS_DEBUG;
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 9e8200b..dc39413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.util.Compile;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -65,7 +66,7 @@
  */
 public class NotificationLogger implements StateListener {
     private static final String TAG = "NotificationLogger";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     /** The minimum delay in ms between reports of notification visibility. */
     private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 08a230b..dbd22db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -115,6 +115,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.Compile;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.wmshell.BubblesManager;
 
@@ -136,11 +137,11 @@
         implements PluginListener<NotificationMenuRowPlugin>, SwipeableView,
         NotificationFadeAware.FadeOptimizedNotification {
 
-    private static final boolean DEBUG = false;
+    private static final String TAG = "ExpandableNotifRow";
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     private static final int MENU_VIEW_INDEX = 0;
-    private static final String TAG = "ExpandableNotifRow";
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index c0bafb7..4893490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -48,11 +48,12 @@
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.Compile;
 
 public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsContent {
 
     private static final String TAG = "FeedbackInfo";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     private NotificationGuts mGutsContainer;
     private NotificationListenerService.Ranking mRanking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index ab78d19..6abfee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
+import com.android.systemui.util.Compile;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -43,8 +44,9 @@
  */
 public class NotificationBlockingHelperManager {
     /** Enables debug logging and always makes the blocking helper show up after a dismiss. */
-    private static final boolean DEBUG = false;
     private static final String TAG = "BlockingHelper";
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG_ALWAYS_SHOW = false;
 
     private final Context mContext;
     private final NotificationGutsManager mNotificationGutsManager;
@@ -98,7 +100,7 @@
         // - The dismissed row is a valid group (>1 or 0 children from the same channel)
         // or the only child in the group
         final NotificationEntry entry = row.getEntry();
-        if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG)
+        if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG_ALWAYS_SHOW)
                 && mIsShadeExpanded
                 && !row.getIsNonblockable()
                 && ((!row.isChildInGroup() || mGroupMembershipManager.isOnlyChildInGroup(entry))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 4dec1f1..9cb5dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt;
 import com.android.systemui.statusbar.policy.SmartReplyView;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.Compile;
 import com.android.systemui.wmshell.BubblesManager;
 
 import java.io.FileDescriptor;
@@ -77,7 +78,7 @@
 public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
 
     private static final String TAG = "NotificationContentView";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     public static final int VISIBLE_TYPE_EXPANDED = 1;
     public static final int VISIBLE_TYPE_HEADSUP = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 9d599cb..d0fb416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -57,9 +57,6 @@
 public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener,
         ExpandableNotificationRow.LayoutListener {
 
-    private static final boolean DEBUG = false;
-    private static final String TAG = "swipe";
-
     // Notification must be swiped at least this fraction of a single menu item to show menu
     private static final float SWIPED_FAR_ENOUGH_MENU_FRACTION = 0.25f;
     private static final float SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION = 0.15f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 915a85d..90f5179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -132,6 +132,7 @@
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
+    private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
 
     // Delay in milli-seconds before shade closes for clear all.
     private final int DELAY_BEFORE_SHADE_CLOSE = 200;
@@ -3143,6 +3144,13 @@
             AnimationEvent event = new AnimationEvent(row, type);
             event.headsUpFromBottom = onBottom;
             mAnimationEvents.add(event);
+            if (SPEW) {
+                Log.v(TAG, "Generating HUN animation event: "
+                        + " isHeadsUp=" + isHeadsUp
+                        + " type=" + type
+                        + " onBottom=" + onBottom
+                        + " row=" + row.getEntry().getKey());
+            }
         }
         mHeadsUpChangeAnimations.clear();
         mAddedHeadsUpChildren.clear();
@@ -4677,7 +4685,22 @@
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
-        if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
+        final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
+        if (SPEW) {
+            Log.v(TAG, "generateHeadsUpAnimation:"
+                    + " willAdd=" + add
+                    + " isHeadsUp=" + isHeadsUp
+                    + " row=" + row.getEntry().getKey());
+        }
+        if (add) {
+            // If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
+            // and do not add the disappear event either.
+            if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
+                if (SPEW) {
+                    Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
+                }
+                return;
+            }
             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
             mNeedsAnimation = true;
             if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index ff75eef..7c3399d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -127,6 +127,7 @@
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.Compile;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -144,7 +145,7 @@
 @StatusBarComponent.StatusBarScope
 public class NotificationStackScrollLayoutController {
     private static final String TAG = "StackScrollerController";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 9787a944..6632c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Compile;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -54,8 +55,8 @@
 
     private static final long ALERT_TRANSFER_TIMEOUT = 300;
     private static final String TAG = "NotifGroupAlertTransfer";
-    private static final boolean DEBUG = StatusBar.DEBUG;
-    private static final boolean SPEW = StatusBar.SPEW;
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
 
     /**
      * The list of entries containing group alert metadata for each group. Keyed by group key.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 4d625cf..23ea45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -69,6 +69,7 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.provider.Settings;
@@ -4590,11 +4591,13 @@
 
         @Override
         public void onSmallestScreenWidthChanged() {
+            Trace.beginSection("onSmallestScreenWidthChanged");
             if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged");
 
             // Can affect multi-user switcher visibility as it depends on screen size by default:
             // it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
             reInflateViews();
+            Trace.endSection();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 981c843..137c519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2963,7 +2963,7 @@
         mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
             mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
-        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
+        } else if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
         updatePanelExpansionForKeyguard();
@@ -3569,7 +3569,6 @@
             // once we fully woke up.
             updateRevealEffect(true /* wakingUp */);
             updateNotificationPanelTouchState();
-            mPulseExpansionHandler.onStartedWakingUp();
 
             // If we are waking up during the screen off animation, we should undo making the
             // expanded visible (we did that so the LightRevealScrim would be visible).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 41cacf5..1030bfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -16,110 +16,53 @@
 
 package com.android.systemui.statusbar.policy;
 
-
 import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
 import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
-import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
-
-import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
 
 import android.annotation.Nullable;
 import android.hardware.devicestate.DeviceStateManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.SparseIntArray;
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.wrapper.RotationPolicyWrapper;
 
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
- * Handles reading and writing of rotation lock settings per device state, as well as setting
- * the rotation lock when device state changes.
- **/
+ * Handles reading and writing of rotation lock settings per device state, as well as setting the
+ * rotation lock when device state changes.
+ */
 @SysUISingleton
-public final class DeviceStateRotationLockSettingController implements Listenable,
-        RotationLockController.RotationLockControllerCallback {
+public final class DeviceStateRotationLockSettingController
+        implements Listenable, RotationLockController.RotationLockControllerCallback {
 
     private static final String TAG = "DSRotateLockSettingCon";
 
-    private static final String SEPARATOR_REGEX = ":";
-
-    private final SecureSettings mSecureSettings;
     private final RotationPolicyWrapper mRotationPolicyWrapper;
     private final DeviceStateManager mDeviceStateManager;
     private final Executor mMainExecutor;
-    private final String[] mDeviceStateRotationLockDefaults;
+    private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
 
-    private SparseIntArray mDeviceStateRotationLockSettings;
-    // TODO(b/183001527): Add API to query current device state and initialize this.
+    // On registration for DeviceStateCallback, we will receive a callback with the current state
+    // and this will be initialized.
     private int mDeviceState = -1;
-    @Nullable
-    private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
-
+    @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+    private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
+            mDeviceStateRotationLockSettingsListener;
 
     @Inject
     public DeviceStateRotationLockSettingController(
-            SecureSettings secureSettings,
             RotationPolicyWrapper rotationPolicyWrapper,
             DeviceStateManager deviceStateManager,
             @Main Executor executor,
-            @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
-    ) {
-        mSecureSettings = secureSettings;
+            DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
         mRotationPolicyWrapper = rotationPolicyWrapper;
         mDeviceStateManager = deviceStateManager;
         mMainExecutor = executor;
-        mDeviceStateRotationLockDefaults = deviceStateRotationLockDefaults;
-    }
-
-    /**
-     * Loads the settings from storage.
-     */
-    public void initialize() {
-        String serializedSetting =
-                mSecureSettings.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                        UserHandle.USER_CURRENT);
-        if (TextUtils.isEmpty(serializedSetting)) {
-            // No settings saved, we should load the defaults and persist them.
-            fallbackOnDefaults();
-            return;
-        }
-        String[] values = serializedSetting.split(SEPARATOR_REGEX);
-        if (values.length % 2 != 0) {
-            // Each entry should be a key/value pair, so this is corrupt.
-            Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
-            fallbackOnDefaults();
-            return;
-        }
-        mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
-        int key;
-        int value;
-
-        for (int i = 0; i < values.length - 1; ) {
-            try {
-                key = Integer.parseInt(values[i++]);
-                value = Integer.parseInt(values[i++]);
-                mDeviceStateRotationLockSettings.put(key, value);
-            } catch (NumberFormatException e) {
-                Log.wtf(TAG, "Error deserializing one of the saved settings", e);
-                fallbackOnDefaults();
-                return;
-            }
-        }
-    }
-
-    private void fallbackOnDefaults() {
-        loadDefaults();
-        persistSettings();
+        mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
     }
 
     @Override
@@ -129,10 +72,17 @@
             // is no user action.
             mDeviceStateCallback = this::updateDeviceState;
             mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
+            mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
+            mDeviceStateRotationLockSettingsManager.registerListener(
+                    mDeviceStateRotationLockSettingsListener);
         } else {
             if (mDeviceStateCallback != null) {
                 mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
             }
+            if (mDeviceStateRotationLockSettingsListener != null) {
+                mDeviceStateRotationLockSettingsManager.unregisterListener(
+                        mDeviceStateRotationLockSettingsListener);
+            }
         }
     }
 
@@ -143,7 +93,8 @@
             return;
         }
 
-        if (rotationLocked == isRotationLockedForCurrentState()) {
+        if (rotationLocked
+                == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
             Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
             return;
         }
@@ -152,19 +103,15 @@
     }
 
     private void saveNewRotationLockSetting(boolean isRotationLocked) {
-        Log.v(TAG, "saveNewRotationLockSetting [state=" + mDeviceState + "] [isRotationLocked="
-                + isRotationLocked + "]");
+        Log.v(
+                TAG,
+                "saveNewRotationLockSetting [state="
+                        + mDeviceState
+                        + "] [isRotationLocked="
+                        + isRotationLocked
+                        + "]");
 
-        mDeviceStateRotationLockSettings.put(mDeviceState,
-                isRotationLocked
-                        ? DEVICE_STATE_ROTATION_LOCK_LOCKED
-                        : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
-        persistSettings();
-    }
-
-    private boolean isRotationLockedForCurrentState() {
-        return mDeviceStateRotationLockSettings.get(mDeviceState,
-                DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+        mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
     }
 
     private void updateDeviceState(int state) {
@@ -173,8 +120,12 @@
             return;
         }
 
+        readPersistedSetting(state);
+    }
+
+    private void readPersistedSetting(int state) {
         int rotationLockSetting =
-                mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+                mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
         if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
             // We won't handle this device state. The same rotation lock setting as before should
             // apply and any changes to the rotation lock setting will be written for the previous
@@ -186,54 +137,10 @@
         // Accept the new state
         mDeviceState = state;
 
-        // Update the rotation lock setting if needed for this new device state
+        // Update the rotation policy, if needed, for this new device state
         boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
         if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
             mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
         }
     }
-
-    private void persistSettings() {
-        if (mDeviceStateRotationLockSettings.size() == 0) {
-            mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                    /* value= */"", UserHandle.USER_CURRENT);
-            return;
-        }
-
-        StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append(mDeviceStateRotationLockSettings.keyAt(0))
-                .append(SEPARATOR_REGEX)
-                .append(mDeviceStateRotationLockSettings.valueAt(0));
-
-        for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
-            stringBuilder
-                    .append(SEPARATOR_REGEX)
-                    .append(mDeviceStateRotationLockSettings.keyAt(i))
-                    .append(SEPARATOR_REGEX)
-                    .append(mDeviceStateRotationLockSettings.valueAt(i));
-        }
-        mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                stringBuilder.toString(), UserHandle.USER_CURRENT);
-    }
-
-    private void loadDefaults() {
-        if (mDeviceStateRotationLockDefaults.length == 0) {
-            Log.w(TAG, "Empty default settings");
-            mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */0);
-            return;
-        }
-        mDeviceStateRotationLockSettings =
-                new SparseIntArray(mDeviceStateRotationLockDefaults.length);
-        for (String serializedDefault : mDeviceStateRotationLockDefaults) {
-            String[] entry = serializedDefault.split(SEPARATOR_REGEX);
-            try {
-                int key = Integer.parseInt(entry[0]);
-                int value = Integer.parseInt(entry[1]);
-                mDeviceStateRotationLockSettings.put(key, value);
-            } catch (NumberFormatException e) {
-                Log.wtf(TAG, "Error deserializing default settings", e);
-            }
-        }
-    }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
new file mode 100644
index 0000000..a418c74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Manages device-state based rotation lock settings. Handles reading, writing, and listening for
+ * changes.
+ */
+public final class DeviceStateRotationLockSettingsManager {
+
+    private static final String TAG = "DSRotLockSettingsMngr";
+    private static final String SEPARATOR_REGEX = ":";
+
+    private static DeviceStateRotationLockSettingsManager sSingleton;
+
+    private final ContentResolver mContentResolver;
+    private final String[] mDeviceStateRotationLockDefaults;
+    private final Handler mMainHandler = Handler.getMain();
+    private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
+    private SparseIntArray mDeviceStateRotationLockSettings;
+
+    private DeviceStateRotationLockSettingsManager(Context context) {
+        mContentResolver = context.getContentResolver();
+        mDeviceStateRotationLockDefaults =
+                context.getResources()
+                        .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
+        initializeInMemoryMap();
+        listenForSettingsChange(context);
+    }
+
+    /** Returns a singleton instance of this class */
+    public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
+        if (sSingleton == null) {
+            sSingleton =
+                    new DeviceStateRotationLockSettingsManager(context.getApplicationContext());
+        }
+        return sSingleton;
+    }
+
+    /** Returns true if device-state based rotation lock settings are enabled. */
+    public static boolean isDeviceStateRotationLockEnabled(Context context) {
+        return context.getResources()
+                        .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
+                        .length
+                > 0;
+    }
+
+    private void listenForSettingsChange(Context context) {
+        context.getContentResolver()
+                .registerContentObserver(
+                        Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK),
+                        /* notifyForDescendents= */ false, //NOTYPO
+                        new ContentObserver(mMainHandler) {
+                            @Override
+                            public void onChange(boolean selfChange) {
+                                onPersistedSettingsChanged();
+                            }
+                        },
+                        UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings
+     * change. Can be called multiple times with different listeners.
+     */
+    public void registerListener(DeviceStateRotationLockSettingsListener runnable) {
+        mListeners.add(runnable);
+    }
+
+    /**
+     * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance
+     * was never registered.
+     */
+    public void unregisterListener(
+            DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) {
+        if (!mListeners.remove(deviceStateRotationLockSettingsListener)) {
+            Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
+        }
+    }
+
+    /** Updates the rotation lock setting for a specified device state. */
+    public void updateSetting(int deviceState, boolean rotationLocked) {
+        mDeviceStateRotationLockSettings.put(
+                deviceState,
+                rotationLocked
+                        ? DEVICE_STATE_ROTATION_LOCK_LOCKED
+                        : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+        persistSettings();
+    }
+
+    /**
+     * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting
+     * is specified for this device state, it will return {@link
+     * DEVICE_STATE_ROTATION_LOCK_IGNORED}.
+     */
+    @Settings.Secure.DeviceStateRotationLockSetting
+    public int getRotationLockSetting(int deviceState) {
+        return mDeviceStateRotationLockSettings.get(
+                deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+    }
+
+    /** Returns true if the rotation is locked for the current device state */
+    public boolean isRotationLocked(int deviceState) {
+        return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+    }
+
+    /**
+     * Returns true if there is no device state for which the current setting is {@link
+     * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
+     */
+    public boolean isRotationLockedForAllStates() {
+        for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) {
+            if (mDeviceStateRotationLockSettings.valueAt(i)
+                    == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void initializeInMemoryMap() {
+        String serializedSetting =
+                Settings.Secure.getStringForUser(
+                        mContentResolver,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        UserHandle.USER_CURRENT);
+        if (TextUtils.isEmpty(serializedSetting)) {
+            // No settings saved, we should load the defaults and persist them.
+            fallbackOnDefaults();
+            return;
+        }
+        String[] values = serializedSetting.split(SEPARATOR_REGEX);
+        if (values.length % 2 != 0) {
+            // Each entry should be a key/value pair, so this is corrupt.
+            Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
+            fallbackOnDefaults();
+            return;
+        }
+        mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
+        int key;
+        int value;
+
+        for (int i = 0; i < values.length - 1; ) {
+            try {
+                key = Integer.parseInt(values[i++]);
+                value = Integer.parseInt(values[i++]);
+                mDeviceStateRotationLockSettings.put(key, value);
+            } catch (NumberFormatException e) {
+                Log.wtf(TAG, "Error deserializing one of the saved settings", e);
+                fallbackOnDefaults();
+                return;
+            }
+        }
+    }
+
+    private void fallbackOnDefaults() {
+        loadDefaults();
+        persistSettings();
+    }
+
+    private void persistSettings() {
+        if (mDeviceStateRotationLockSettings.size() == 0) {
+            Settings.Secure.putStringForUser(
+                    mContentResolver,
+                    Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                    /* value= */ "",
+                    UserHandle.USER_CURRENT);
+            return;
+        }
+
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder
+                .append(mDeviceStateRotationLockSettings.keyAt(0))
+                .append(SEPARATOR_REGEX)
+                .append(mDeviceStateRotationLockSettings.valueAt(0));
+
+        for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
+            stringBuilder
+                    .append(SEPARATOR_REGEX)
+                    .append(mDeviceStateRotationLockSettings.keyAt(i))
+                    .append(SEPARATOR_REGEX)
+                    .append(mDeviceStateRotationLockSettings.valueAt(i));
+        }
+        Settings.Secure.putStringForUser(
+                mContentResolver,
+                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                stringBuilder.toString(),
+                UserHandle.USER_CURRENT);
+    }
+
+    private void loadDefaults() {
+        if (mDeviceStateRotationLockDefaults.length == 0) {
+            Log.w(TAG, "Empty default settings");
+            mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */ 0);
+            return;
+        }
+        mDeviceStateRotationLockSettings =
+                new SparseIntArray(mDeviceStateRotationLockDefaults.length);
+        for (String serializedDefault : mDeviceStateRotationLockDefaults) {
+            String[] entry = serializedDefault.split(SEPARATOR_REGEX);
+            try {
+                int key = Integer.parseInt(entry[0]);
+                int value = Integer.parseInt(entry[1]);
+                mDeviceStateRotationLockSettings.put(key, value);
+            } catch (NumberFormatException e) {
+                Log.wtf(TAG, "Error deserializing default settings", e);
+            }
+        }
+    }
+
+    /**
+     * Called when the persisted settings have changed, requiring a reinitialization of the
+     * in-memory map.
+     */
+    @VisibleForTesting
+    public void onPersistedSettingsChanged() {
+        initializeInMemoryMap();
+        notifyListeners();
+    }
+
+    private void notifyListeners() {
+        for (DeviceStateRotationLockSettingsListener r : mListeners) {
+            r.onSettingsChanged();
+        }
+    }
+
+    /** Listener for changes in device-state based rotation lock settings */
+    public interface DeviceStateRotationLockSettingsListener {
+        /** Called whenever the settings have changed. */
+        void onSettingsChanged();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 3143a47..1eeb0ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -64,7 +64,6 @@
         mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController;
         mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0;
         if (mIsPerDeviceStateRotationLockEnabled) {
-            deviceStateRotationLockSettingController.initialize();
             mCallbacks.add(mDeviceStateRotationLockSettingController);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b6a96a7..60938fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy.dagger;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.os.UserManager;
 
@@ -35,6 +36,7 @@
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
@@ -163,6 +165,14 @@
         return controller;
     }
 
+    /** Returns a singleton instance of DeviceStateRotationLockSettingsManager */
+    @SysUISingleton
+    @Provides
+    static DeviceStateRotationLockSettingsManager provideAutoRotateSettingsManager(
+            Context context) {
+        return DeviceStateRotationLockSettingsManager.getInstance(context);
+    }
+
     /**
      * Default values for per-device state rotation lock settings.
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index e453ff2d..fd282cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -80,6 +80,7 @@
         assertEquals(WakefulnessLifecycle.WAKEFULNESS_AWAKE, mWakefulness.getWakefulness());
 
         verify(mWakefulnessObserver).onFinishedWakingUp();
+        verify(mWakefulnessObserver).onPostFinishedWakingUp();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 13b8e81..42647f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -4,13 +4,12 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
-import android.util.DisplayMetrics
 import com.android.systemui.ExpandHelper
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.MediaHierarchyManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
@@ -64,12 +63,11 @@
     @Mock lateinit var lockScreenUserManager: NotificationLockscreenUserManager
     @Mock lateinit var falsingCollector: FalsingCollector
     @Mock lateinit var ambientState: AmbientState
-    @Mock lateinit var displayMetrics: DisplayMetrics
+    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock lateinit var mediaHierarchyManager: MediaHierarchyManager
     @Mock lateinit var scrimController: ScrimController
     @Mock lateinit var configurationController: ConfigurationController
     @Mock lateinit var falsingManager: FalsingManager
-    @Mock lateinit var buffer: LogBuffer
     @Mock lateinit var notificationPanelController: NotificationPanelViewController
     @Mock lateinit var nsslController: NotificationStackScrollLayoutController
     @Mock lateinit var depthController: NotificationShadeDepthController
@@ -98,6 +96,7 @@
             mediaHierarchyManager = mediaHierarchyManager,
             scrimController = scrimController,
             depthController = depthController,
+            wakefulnessLifecycle = wakefulnessLifecycle,
             context = context,
             configurationController = configurationController,
             falsingManager = falsingManager,
@@ -148,6 +147,23 @@
     }
 
     @Test
+    fun testWakingToShadeLockedWhenDozing() {
+        whenever(statusbarStateController.isDozing).thenReturn(true)
+        transitionController.goToLockedShade(null)
+        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+        assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
+    }
+
+    @Test
+    fun testNotWakingToShadeLockedWhenNotDozing() {
+        whenever(statusbarStateController.isDozing).thenReturn(false)
+        transitionController.goToLockedShade(null)
+        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+        assertFalse("Waking to shade locked when not dozing",
+                transitionController.isWakingToShadeLocked)
+    }
+
+    @Test
     fun testGoToLockedShadeOnlyOnKeyguard() {
         whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
         transitionController.goToLockedShade(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 1be27da..6d170b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -178,20 +178,68 @@
     }
 
     /**
+     * Helper for testing various sibling counts
+     */
+    private void helpTestAlertOverrideWithSiblings(int numSiblings) {
+        helpTestAlertOverride(
+                /* numSiblings */ numSiblings,
+                /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* childAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* expectAlertOverride */ true);
+    }
+
+    @Test
+    public void testAlertOverrideWithParentAlertAll() {
+        // tests that summary can have GROUP_ALERT_ALL and this still works
+        helpTestAlertOverride(
+                /* numSiblings */ 1,
+                /* summaryAlert */ Notification.GROUP_ALERT_ALL,
+                /* childAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* expectAlertOverride */ true);
+    }
+
+    @Test
+    public void testAlertOverrideWithParentAlertChild() {
+        // Tests that if the summary alerts CHILDREN, there's no alertOverride
+        helpTestAlertOverride(
+                /* numSiblings */ 1,
+                /* summaryAlert */ Notification.GROUP_ALERT_CHILDREN,
+                /* childAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* expectAlertOverride */ false);
+    }
+
+    @Test
+    public void testAlertOverrideWithChildrenAlertAll() {
+        // Tests that if the children alert ALL, there's no alertOverride
+        helpTestAlertOverride(
+                /* numSiblings */ 1,
+                /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY,
+                /* childAlert */ Notification.GROUP_ALERT_ALL,
+                /* siblingAlert */ Notification.GROUP_ALERT_ALL,
+                /* expectAlertOverride */ false);
+    }
+
+    /**
      * This tests, for a group with a priority entry and the given number of siblings, that:
      * 1) the priority entry is identified as the alertOverride for the group
      * 2) the onAlertOverrideChanged method is called at that time
      * 3) when the priority entry is removed, these are reversed
      */
-    private void helpTestAlertOverrideWithSiblings(int numSiblings) {
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
+    private void helpTestAlertOverride(int numSiblings,
+            @Notification.GroupAlertBehavior int summaryAlert,
+            @Notification.GroupAlertBehavior int childAlert,
+            @Notification.GroupAlertBehavior int siblingAlert,
+            boolean expectAlertOverride) {
         // Create entries in an order so that the priority entry can be deemed the newest child.
         NotificationEntry[] siblings = new NotificationEntry[numSiblings];
         for (int i = 0; i < numSiblings; i++) {
-            siblings[i] = mGroupTestHelper.createChildNotification(groupAlert);
+            siblings[i] = mGroupTestHelper.createChildNotification(siblingAlert);
         }
-        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
+        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(childAlert);
+        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(summaryAlert);
 
         // The priority entry is an important conversation.
         when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
@@ -208,6 +256,14 @@
         }
         mGroupManager.onEntryAdded(priorityEntry);
 
+        if (!expectAlertOverride) {
+            // Test expectation is that there will NOT be an alert, so verify that!
+            NotificationGroup summaryGroup =
+                    mGroupManager.getGroupForSummary(summaryEntry.getSbn());
+            assertNull(summaryGroup.alertOverride);
+            return;
+        }
+
         // Verify that the summary group has the priority child as its alertOverride
         NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
         assertEquals(priorityEntry, summaryGroup.alertOverride);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index db7b2f2..a8522c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -25,14 +29,15 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableContentResolver;
 import android.testing.TestableResources;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.internal.view.RotationPolicy;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wrapper.RotationPolicyWrapper;
 
@@ -47,63 +52,55 @@
 @SmallTest
 public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
 
-    private static final String[] DEFAULT_SETTINGS = new String[]{
-            "0:0",
-            "1:2"
-    };
+    private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"};
 
-    private final FakeSettings mFakeSettings = new FakeSettings();
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
     @Mock DeviceStateManager mDeviceStateManager;
     RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
     DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
     private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+    private DeviceStateRotationLockSettingsManager mSettingsManager;
+    private TestableContentResolver mContentResolver;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(/* testClass= */ this);
         TestableResources resources = mContext.getOrCreateTestableResources();
+        resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS);
 
         ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor =
-                ArgumentCaptor.forClass(
-                        DeviceStateManager.DeviceStateCallback.class);
+                ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
 
-        mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController(
-                mFakeSettings,
-                mFakeRotationPolicy,
-                mDeviceStateManager,
-                mFakeExecutor,
-                DEFAULT_SETTINGS
-        );
+        mContentResolver = mContext.getContentResolver();
+        mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext);
+        mDeviceStateRotationLockSettingController =
+                new DeviceStateRotationLockSettingController(
+                        mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager);
 
         mDeviceStateRotationLockSettingController.setListening(true);
-        verify(mDeviceStateManager).registerCallback(any(),
-                deviceStateCallbackArgumentCaptor.capture());
+        verify(mDeviceStateManager)
+                .registerCallback(any(), deviceStateCallbackArgumentCaptor.capture());
         mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue();
     }
 
     @Test
     public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() {
-        mFakeSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, "",
-                UserHandle.USER_CURRENT);
+        initializeSettingsWith();
 
-        mDeviceStateRotationLockSettingController.initialize();
-
-        assertThat(mFakeSettings
-                .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                        UserHandle.USER_CURRENT))
+        assertThat(
+                        Settings.Secure.getStringForUser(
+                                mContentResolver,
+                                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                                UserHandle.USER_CURRENT))
                 .isEqualTo("0:0:1:2");
     }
 
     @Test
     public void whenNoSavedValueForDeviceState_assumeIgnored() {
-        mFakeSettings.putStringForUser(
-                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                /* value= */"0:2:1:2",
-                UserHandle.USER_CURRENT);
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(true);
-        mDeviceStateRotationLockSettingController.initialize();
 
         mDeviceStateCallback.onStateChanged(1);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -116,52 +113,43 @@
 
     @Test
     public void whenDeviceStateSwitched_loadCorrectSetting() {
-        mFakeSettings.putStringForUser(
-                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                /* value= */"0:2:1:1",
-                UserHandle.USER_CURRENT);
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED);
         mFakeRotationPolicy.setRotationLock(true);
-        mDeviceStateRotationLockSettingController.initialize();
 
         mDeviceStateCallback.onStateChanged(0);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
         mDeviceStateCallback.onStateChanged(1);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
-
     }
 
     @Test
     public void whenUserChangesSetting_saveSettingForCurrentState() {
-        mFakeSettings.putStringForUser(
-                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                /* value= */"0:1:1:2",
-                UserHandle.USER_CURRENT);
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+        mSettingsManager.onPersistedSettingsChanged();
         mFakeRotationPolicy.setRotationLock(true);
-        mDeviceStateRotationLockSettingController.initialize();
 
         mDeviceStateCallback.onStateChanged(0);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
 
-        mDeviceStateRotationLockSettingController
-                .onRotationLockStateChanged(/* rotationLocked= */false,
-                        /* affordanceVisible= */ true);
+        mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+                /* rotationLocked= */ false, /* affordanceVisible= */ true);
 
-        assertThat(mFakeSettings
-                .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                        UserHandle.USER_CURRENT))
+        assertThat(
+                        Settings.Secure.getStringForUser(
+                                mContentResolver,
+                                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                                UserHandle.USER_CURRENT))
                 .isEqualTo("0:2:1:2");
     }
 
-
     @Test
     public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
-        mFakeSettings.putStringForUser(
-                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                /* value= */"0:0:1:2",
-                UserHandle.USER_CURRENT);
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(true);
-        mDeviceStateRotationLockSettingController.initialize();
 
         mDeviceStateCallback.onStateChanged(1);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -172,12 +160,9 @@
 
     @Test
     public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() {
-        mFakeSettings.putStringForUser(
-                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                /* value= */"0:0:1:2",
-                UserHandle.USER_CURRENT);
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(true);
-        mDeviceStateRotationLockSettingController.initialize();
 
         mDeviceStateCallback.onStateChanged(1);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -185,16 +170,52 @@
         mDeviceStateCallback.onStateChanged(0);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
-        mDeviceStateRotationLockSettingController
-                .onRotationLockStateChanged(/* rotationLocked= */true,
-                        /* affordanceVisible= */ true);
+        mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+                /* rotationLocked= */ true, /* affordanceVisible= */ true);
 
-        assertThat(mFakeSettings
-                .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                        UserHandle.USER_CURRENT))
+        assertThat(
+                        Settings.Secure.getStringForUser(
+                                mContentResolver,
+                                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                                UserHandle.USER_CURRENT))
                 .isEqualTo("0:0:1:1");
     }
 
+    @Test
+    public void whenSettingsChangedExternally_updateRotationPolicy() throws InterruptedException {
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
+                1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+        mFakeRotationPolicy.setRotationLock(false);
+        mDeviceStateCallback.onStateChanged(0);
+
+        assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+        // Changing device state 0 to LOCKED
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+
+        assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+    }
+
+    private void initializeSettingsWith(int... values) {
+        if (values.length % 2 != 0) {
+            throw new IllegalArgumentException("Expecting key-value pairs");
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < values.length; sb.append(":")) {
+            sb.append(values[i++]).append(":").append(values[i++]);
+        }
+
+        Settings.Secure.putStringForUser(
+                mContentResolver,
+                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                sb.toString(),
+                UserHandle.USER_CURRENT);
+
+        mSettingsManager.onPersistedSettingsChanged();
+    }
+
     private static class FakeRotationPolicy implements RotationPolicyWrapper {
 
         private boolean mRotationLock;
@@ -230,8 +251,8 @@
         }
 
         @Override
-        public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener,
-                int userHandle) {
+        public void registerRotationPolicyListener(
+                RotationPolicy.RotationPolicyListener listener, int userHandle) {
             throw new AssertionError("Not implemented");
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
index 0581264..ea620a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
@@ -23,7 +23,6 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.testing.TestableResources;
 
 import androidx.test.filters.SmallTest;
 
@@ -43,25 +42,19 @@
 @SmallTest
 public class RotationLockControllerImplTest extends SysuiTestCase {
 
-    private static final String[] DEFAULT_SETTINGS = new String[]{
-            "0:0",
-            "1:2"
-    };
+    private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"};
 
     @Mock RotationPolicyWrapper mRotationPolicyWrapper;
     @Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
 
-    private TestableResources mResources;
-    private ArgumentCaptor<RotationPolicy.RotationPolicyListener>
-            mRotationPolicyListenerCaptor;
+    private ArgumentCaptor<RotationPolicy.RotationPolicyListener> mRotationPolicyListenerCaptor;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(/* testClass= */ this);
-        mResources = mContext.getOrCreateTestableResources();
 
-        mRotationPolicyListenerCaptor = ArgumentCaptor.forClass(
-                RotationPolicy.RotationPolicyListener.class);
+        mRotationPolicyListenerCaptor =
+                ArgumentCaptor.forClass(RotationPolicy.RotationPolicyListener.class);
     }
 
     @Test
@@ -79,14 +72,7 @@
     }
 
     @Test
-    public void whenFlagOn_initializesDeviceStateRotationController() {
-        createRotationLockController();
-
-        verify(mDeviceStateRotationLockSettingController).initialize();
-    }
-
-    @Test
-    public void whenFlagOn_dviceStateRotationControllerAddedToCallbacks() {
+    public void whenFlagOn_deviceStateRotationControllerAddedToCallbacks() {
         createRotationLockController();
         captureRotationPolicyListener().onChange();
 
@@ -103,11 +89,11 @@
     private void createRotationLockController() {
         createRotationLockController(DEFAULT_SETTINGS);
     }
+
     private void createRotationLockController(String[] deviceStateRotationLockDefaults) {
         new RotationLockControllerImpl(
                 mRotationPolicyWrapper,
                 mDeviceStateRotationLockSettingController,
-                deviceStateRotationLockDefaults
-        );
+                deviceStateRotationLockDefaults);
     }
 }
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 6d0eadb..196c6aa 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -279,6 +279,9 @@
     // Notify the user that a CA certificate is pending for the wifi connection.
     NOTE_SERVER_CA_CERTIFICATE = 67;
 
+    // Notify the user to set up dream
+    NOTE_SETUP_DREAM = 68;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index aba32ec..59c1461 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -667,10 +667,6 @@
                 return null;
             }
 
-            // Don't need to add the embedded hierarchy windows into the accessibility windows list.
-            if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
-                return null;
-            }
             final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
 
             reportedWindow.setId(windowId);
@@ -703,21 +699,6 @@
             return reportedWindow;
         }
 
-        private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
-            final IBinder leashToken = mWindowIdMap.get(windowId);
-            if (leashToken == null) {
-                return false;
-            }
-
-            for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
-                if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         private int getTypeForWindowManagerWindowType(int windowType) {
             switch (windowType) {
                 case WindowManager.LayoutParams.TYPE_APPLICATION:
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index 3f0200e..5b318d3 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -136,6 +136,8 @@
 
             // Update the ID-to-Association map.
             mIdMap.put(id, updated);
+            // Invalidate the corresponding user cache entry.
+            invalidateCacheForUserLocked(current.getUserId());
 
             // Update the MacAddress-to-List<Association> map if needed.
             final MacAddress updatedAddress = updated.getDeviceMacAddress();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 94a97d8..1c98347 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -600,11 +600,13 @@
                 return;
             }
 
-            association.setLastTimeConnected(System.currentTimeMillis());
-            mAssociationStore.updateAssociation(association);
+            AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association)
+                    .setLastTimeConnected(System.currentTimeMillis())
+                    .build();
+            mAssociationStore.updateAssociation(updatedAssociationInfo);
 
             mCompanionDevicePresenceController.onDeviceNotifyAppeared(
-                    association, getContext(), mMainHandler);
+                    updatedAssociationInfo, getContext(), mMainHandler);
         }
 
         @Override
@@ -651,8 +653,10 @@
                         + " for user " + userId));
             }
 
-            association.setNotifyOnDeviceNearby(active);
-            mAssociationStore.updateAssociation(association);
+            AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association)
+                    .setNotifyOnDeviceNearby(active)
+                    .build();
+            mAssociationStore.updateAssociation(updatedAssociationInfo);
         }
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 8e71dd3..734e5c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -84,11 +84,9 @@
     }
 
     @Override
-    public void onRunningAppsChanged(int[] runningUids) {
+    public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
         mRunningUids.clear();
-        for (int i = 0; i < runningUids.length; i++) {
-            mRunningUids.add(runningUids[i]);
-        }
+        mRunningUids.addAll(runningUids);
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 1bb95f8..0c0ee52 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -23,6 +23,9 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDevice;
@@ -38,9 +41,11 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.window.DisplayWindowPolicyController;
 
@@ -55,10 +60,12 @@
 final class VirtualDeviceImpl extends IVirtualDevice.Stub
         implements IBinder.DeathRecipient {
 
+    private static final String TAG = "VirtualDeviceImpl";
     private final Object mVirtualDeviceLock = new Object();
 
     private final Context mContext;
     private final AssociationInfo mAssociationInfo;
+    private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
     private final InputController mInputController;
     @VisibleForTesting
@@ -76,17 +83,18 @@
 
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
             IBinder token, int ownerUid, OnDeviceCloseListener listener,
-            VirtualDeviceParams params) {
+            PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
         this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
-                params);
+                pendingTrampolineCallback, params);
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
             int ownerUid, InputController inputController, OnDeviceCloseListener listener,
-            VirtualDeviceParams params) {
+            PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
         mContext = context;
         mAssociationInfo = associationInfo;
+        mPendingTrampolineCallback = pendingTrampolineCallback;
         mOwnerUid = ownerUid;
         mAppToken = token;
         mParams = params;
@@ -121,6 +129,49 @@
     }
 
     @Override // Binder call
+    public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
+            ResultReceiver resultReceiver) {
+        if (!mVirtualDisplayIds.contains(displayId)) {
+            throw new SecurityException("Display ID " + displayId
+                    + " not found for this virtual device");
+        }
+        if (pendingIntent.isActivity()) {
+            try {
+                sendPendingIntent(displayId, pendingIntent);
+                resultReceiver.send(Activity.RESULT_OK, null);
+            } catch (PendingIntent.CanceledException e) {
+                Slog.w(TAG, "Pending intent canceled", e);
+                resultReceiver.send(Activity.RESULT_CANCELED, null);
+            }
+        } else {
+            PendingTrampoline pendingTrampoline = new PendingTrampoline(pendingIntent,
+                    resultReceiver, displayId);
+            mPendingTrampolineCallback.startWaitingForPendingTrampoline(pendingTrampoline);
+            try {
+                sendPendingIntent(displayId, pendingIntent);
+            } catch (PendingIntent.CanceledException e) {
+                Slog.w(TAG, "Pending intent canceled", e);
+                resultReceiver.send(Activity.RESULT_CANCELED, null);
+                mPendingTrampolineCallback.stopWaitingForPendingTrampoline(pendingTrampoline);
+            }
+        }
+    }
+
+    private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
+            throws PendingIntent.CanceledException {
+        pendingIntent.send(
+                mContext,
+                /* code= */ 0,
+                /* intent= */ null,
+                /* onFinished= */ null,
+                /* handler= */ null,
+                /* requiredPermission= */ null,
+                ActivityOptions.makeBasic()
+                        .setLaunchDisplayId(displayId)
+                        .toBundle());
+    }
+
+    @Override // Binder call
     public void close() {
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
@@ -276,6 +327,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         fout.println("  VirtualDevice: ");
+        fout.println("    mAssociationId: " + mAssociationInfo.getId());
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
             for (int id : mVirtualDisplayIds) {
@@ -346,4 +398,58 @@
     interface OnDeviceCloseListener {
         void onClose(int associationId);
     }
+
+    interface PendingTrampolineCallback {
+        /**
+         * Called when the callback should start waiting for the given pending trampoline.
+         * Implementations should try to listen for activity starts associated with the given
+         * {@code pendingTrampoline}, and launch the activity on the display with
+         * {@link PendingTrampoline#mDisplayId}.
+         */
+        void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline);
+
+        /**
+         * Called when the callback should stop waiting for the given pending trampoline. This can
+         * happen, for example, when the pending intent failed to send.
+         */
+        void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline);
+    }
+
+    /**
+     * A data class storing a pending trampoline this device is expecting.
+     */
+    static class PendingTrampoline {
+
+        /**
+         * The original pending intent sent, for which a trampoline activity launch is expected.
+         */
+        final PendingIntent mPendingIntent;
+
+        /**
+         * The result receiver associated with this pending call. {@link Activity#RESULT_OK} will
+         * be sent to the receiver if the trampoline activity was captured successfully.
+         * {@link Activity#RESULT_CANCELED} is sent otherwise.
+         */
+        final ResultReceiver mResultReceiver;
+
+        /**
+         * The display ID to send the captured trampoline activity launch to.
+         */
+        final int mDisplayId;
+
+        private PendingTrampoline(PendingIntent pendingIntent, ResultReceiver resultReceiver,
+                int displayId) {
+            mPendingIntent = pendingIntent;
+            mResultReceiver = resultReceiver;
+            mDisplayId = displayId;
+        }
+
+        @Override
+        public String toString() {
+            return "PendingTrampoline{"
+                    + "pendingIntent=" + mPendingIntent
+                    + ", resultReceiver=" + mResultReceiver
+                    + ", displayId=" + mDisplayId + "}";
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 12df79d..7e0c2fc 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -16,9 +16,13 @@
 
 package com.android.server.companion.virtual;
 
+import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityOptions;
 import android.companion.AssociationInfo;
 import android.companion.CompanionDeviceManager;
 import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
@@ -26,7 +30,9 @@
 import android.companion.virtual.IVirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ExceptionUtils;
@@ -37,6 +43,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceImpl.PendingTrampoline;
+import com.android.server.wm.ActivityInterceptorCallback;
+import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -48,10 +57,12 @@
 public class VirtualDeviceManagerService extends SystemService {
 
     private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "VirtualDeviceManagerService";
+    private static final String TAG = "VirtualDeviceManagerService";
 
     private final Object mVirtualDeviceManagerLock = new Object();
     private final VirtualDeviceManagerImpl mImpl;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
 
     /**
      * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
@@ -80,10 +91,39 @@
         mImpl = new VirtualDeviceManagerImpl();
     }
 
+    private final ActivityInterceptorCallback mActivityInterceptorCallback =
+            new ActivityInterceptorCallback() {
+
+        @Nullable
+        @Override
+        public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+            if (info.callingPackage == null) {
+                return null;
+            }
+            PendingTrampoline pt = mPendingTrampolines.remove(info.callingPackage);
+            if (pt == null) {
+                return null;
+            }
+            pt.mResultReceiver.send(Activity.RESULT_OK, null);
+            ActivityOptions options = info.checkedOptions;
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            return new ActivityInterceptResult(
+                    info.intent, options.setLaunchDisplayId(pt.mDisplayId));
+        }
+    };
+
     @Override
     public void onStart() {
         publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
         publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
+
+        ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
+                ActivityTaskManagerInternal.class);
+        activityTaskManagerInternal.registerActivityStartInterceptor(
+                VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
+                mActivityInterceptorCallback);
     }
 
     @GuardedBy("mVirtualDeviceManagerLock")
@@ -128,7 +168,8 @@
         }
     }
 
-    class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
+    class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
+            VirtualDeviceImpl.PendingTrampolineCallback {
 
         @Override // Binder call
         public IVirtualDevice createVirtualDevice(
@@ -164,7 +205,8 @@
                                     mVirtualDevices.remove(associationId);
                                 }
                             }
-                        }, params);
+                        },
+                        this, params);
                 mVirtualDevices.put(associationInfo.getId(), virtualDevice);
                 return virtualDevice;
             }
@@ -185,7 +227,7 @@
                     }
                 }
             } else {
-                Slog.w(LOG_TAG, "No associations for user " + callingUserId);
+                Slog.w(TAG, "No associations for user " + callingUserId);
             }
             return null;
         }
@@ -196,7 +238,7 @@
             try {
                 return super.onTransact(code, data, reply, flags);
             } catch (Throwable e) {
-                Slog.e(LOG_TAG, "Error during IPC", e);
+                Slog.e(TAG, "Error during IPC", e);
                 throw ExceptionUtils.propagate(e, RemoteException.class);
             }
         }
@@ -205,7 +247,7 @@
         public void dump(@NonNull FileDescriptor fd,
                 @NonNull PrintWriter fout,
                 @Nullable String[] args) {
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, fout)) {
                 return;
             }
             fout.println("Created virtual devices: ");
@@ -215,10 +257,24 @@
                 }
             }
         }
+
+        @Override
+        public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
+            PendingTrampoline existing = mPendingTrampolines.put(
+                    pendingTrampoline.mPendingIntent.getCreatorPackage(),
+                    pendingTrampoline);
+            if (existing != null) {
+                existing.mResultReceiver.send(Activity.RESULT_CANCELED, null);
+            }
+        }
+
+        @Override
+        public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
+            mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
+        }
     }
 
     private final class LocalService extends VirtualDeviceManagerInternal {
-
         @Override
         public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) {
             synchronized (mVirtualDeviceManagerLock) {
@@ -272,4 +328,42 @@
             return false;
         }
     }
+
+    private static final class PendingTrampolineMap {
+        /**
+         * The maximum duration, in milliseconds, to wait for a trampoline activity launch after
+         * invoking a pending intent.
+         */
+        private static final int TRAMPOLINE_WAIT_MS = 5000;
+
+        private final ConcurrentHashMap<String, PendingTrampoline> mMap = new ConcurrentHashMap<>();
+        private final Handler mHandler;
+
+        PendingTrampolineMap(Handler handler) {
+            mHandler = handler;
+        }
+
+        PendingTrampoline put(
+                @NonNull String packageName, @NonNull PendingTrampoline pendingTrampoline) {
+            PendingTrampoline existing = mMap.put(packageName, pendingTrampoline);
+            mHandler.removeCallbacksAndMessages(existing);
+            mHandler.postDelayed(
+                    () -> {
+                        final String creatorPackage =
+                                pendingTrampoline.mPendingIntent.getCreatorPackage();
+                        if (creatorPackage != null) {
+                            remove(creatorPackage);
+                        }
+                    },
+                    pendingTrampoline,
+                    TRAMPOLINE_WAIT_MS);
+            return existing;
+        }
+
+        PendingTrampoline remove(@NonNull String packageName) {
+            PendingTrampoline pendingTrampoline = mMap.remove(packageName);
+            mHandler.removeCallbacksAndMessages(pendingTrampoline);
+            return pendingTrampoline;
+        }
+    }
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7b17162..95ec9e9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,7 +100,7 @@
     name: "services.core.unboosted",
     defaults: ["platform_service_defaults"],
     srcs: [
-        ":android.hardware.biometrics.face-V1-java-source",
+        ":android.hardware.biometrics.face-V2-java-source",
         ":statslog-art-java-gen",
         ":statslog-contexthub-java-gen",
         ":services.core-sources",
diff --git a/services/core/java/com/android/server/Dumpable.java b/services/core/java/com/android/server/Dumpable.java
index 866f81c..004f923 100644
--- a/services/core/java/com/android/server/Dumpable.java
+++ b/services/core/java/com/android/server/Dumpable.java
@@ -24,6 +24,8 @@
  *
  * <p>See {@link SystemServer.SystemServerDumper} for usage example.
  */
+// TODO(b/149254050): replace / merge with package android.util.Dumpable (it would require
+// exporting IndentingPrintWriter as @SystemApi) and/or changing the method to use a prefix
 public interface Dumpable {
 
     /**
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 513d86e7..62dc2733 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -126,8 +126,8 @@
 
     private boolean wipeUser(Context context, @UserIdInt int userId, String wipeReason) {
         final UserManager userManager = context.getSystemService(UserManager.class);
-        final int result = userManager.removeUserOrSetEphemeral(
-                userId, /* evenWhenDisallowed= */ false);
+        final int result = userManager.removeUserWhenPossible(
+                UserHandle.of(userId), /* overrideDevicePolicy= */ false);
         if (result == UserManager.REMOVE_RESULT_ERROR) {
             Slogf.e(TAG, "Can't remove user %d", userId);
             return false;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 023a11e..0961fcb3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1124,6 +1124,12 @@
     private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
             String eventSource) {
         if (device != AudioSystem.DEVICE_NONE) {
+
+            /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
+             * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
+             */
+            mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
+
             AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE,
                     address, name, AudioSystem.AUDIO_FORMAT_DEFAULT);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 805e45d..346ae0f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2611,6 +2611,16 @@
         return getDevicesForAttributesInt(attributes);
     }
 
+    /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes)
+     * This method is similar with AudioService#getDevicesForAttributes,
+     * only it doesn't enforce permissions because it is used by an unprivileged public API
+     * instead of the system API.
+     */
+    public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
+            @NonNull AudioAttributes attributes) {
+        return getDevicesForAttributesInt(attributes);
+    }
+
     /**
      * @see AudioManager#isMusicActive()
      * @param remotely true if query is for remote playback (cast), false for local playback.
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 406b2dd2..74c8999 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -252,6 +252,9 @@
             AudioAttributes.FLAG_BYPASS_MUTE;
 
     private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
+        if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID) {
+            return;
+        }
         if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
                 apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
             if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index b73e911..26bbb40 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -48,7 +50,6 @@
      * Interface that ClientMonitor holders should use to receive callbacks.
      */
     public interface Callback {
-
         /**
          * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
          * the queue and becomes the current operation).
@@ -203,7 +204,8 @@
     }
 
     /** Signals this operation has completed its lifecycle and should no longer be used. */
-    void destroy() {
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void destroy() {
         mAlreadyDone = true;
         if (mToken != null) {
             try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index a358bc2..39c5944 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -17,10 +17,10 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.IntDef;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Handler;
@@ -55,6 +55,7 @@
  * We currently assume (and require) that each biometric sensor have its own instance of a
  * {@link BiometricScheduler}. See {@link CoexCoordinator}.
  */
+@MainThread
 public class BiometricScheduler {
 
     private static final String BASE_TAG = "BiometricScheduler";
@@ -110,123 +111,6 @@
         }
     }
 
-    /**
-     * Contains all the necessary information for a HAL operation.
-     */
-    @VisibleForTesting
-    static final class Operation {
-
-        /**
-         * The operation is added to the list of pending operations and waiting for its turn.
-         */
-        static final int STATE_WAITING_IN_QUEUE = 0;
-
-        /**
-         * The operation is added to the list of pending operations, but a subsequent operation
-         * has been added. This state only applies to {@link Interruptable} operations. When this
-         * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
-         */
-        static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
-
-        /**
-         * The operation has reached the front of the queue and has started.
-         */
-        static final int STATE_STARTED = 2;
-
-        /**
-         * The operation was started, but is now canceling. Operations should wait for the HAL to
-         * acknowledge that the operation was canceled, at which point it finishes.
-         */
-        static final int STATE_STARTED_CANCELING = 3;
-
-        /**
-         * The operation has reached the head of the queue but is waiting for BiometricService
-         * to acknowledge and start the operation.
-         */
-        static final int STATE_WAITING_FOR_COOKIE = 4;
-
-        /**
-         * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
-         */
-        static final int STATE_FINISHED = 5;
-
-        @IntDef({STATE_WAITING_IN_QUEUE,
-                STATE_WAITING_IN_QUEUE_CANCELING,
-                STATE_STARTED,
-                STATE_STARTED_CANCELING,
-                STATE_WAITING_FOR_COOKIE,
-                STATE_FINISHED})
-        @Retention(RetentionPolicy.SOURCE)
-        @interface OperationState {}
-
-        @NonNull final BaseClientMonitor mClientMonitor;
-        @Nullable final BaseClientMonitor.Callback mClientCallback;
-        @OperationState int mState;
-
-        Operation(
-                @NonNull BaseClientMonitor clientMonitor,
-                @Nullable BaseClientMonitor.Callback callback
-        ) {
-            this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
-        }
-
-        protected Operation(
-                @NonNull BaseClientMonitor clientMonitor,
-                @Nullable BaseClientMonitor.Callback callback,
-                @OperationState int state
-        ) {
-            mClientMonitor = clientMonitor;
-            mClientCallback = callback;
-            mState = state;
-        }
-
-        public boolean isHalOperation() {
-            return mClientMonitor instanceof HalClientMonitor<?>;
-        }
-
-        /**
-         * @return true if the operation requires the HAL, and the HAL is null.
-         */
-        public boolean isUnstartableHalOperation() {
-            if (isHalOperation()) {
-                final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
-                if (client.getFreshDaemon() == null) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return mClientMonitor + ", State: " + mState;
-        }
-    }
-
-    /**
-     * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
-     * kill the current operation and forcibly start the next.
-     */
-    private static final class CancellationWatchdog implements Runnable {
-        static final int DELAY_MS = 3000;
-
-        final String tag;
-        final Operation operation;
-        CancellationWatchdog(String tag, Operation operation) {
-            this.tag = tag;
-            this.operation = operation;
-        }
-
-        @Override
-        public void run() {
-            if (operation.mState != Operation.STATE_FINISHED) {
-                Slog.e(tag, "[Watchdog Triggered]: " + operation);
-                operation.mClientMonitor.mCallback
-                        .onClientFinished(operation.mClientMonitor, false /* success */);
-            }
-        }
-    }
-
     private static final class CrashState {
         static final int NUM_ENTRIES = 10;
         final String timestamp;
@@ -263,10 +147,9 @@
     private final @SensorType int mSensorType;
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
-    @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
-    @NonNull private final InternalCallback mInternalCallback;
-    @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
-    @VisibleForTesting @Nullable Operation mCurrentOperation;
+    @NonNull protected final Handler mHandler;
+    @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
+    @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
     @NonNull private final ArrayDeque<CrashState> mCrashStates;
 
     private int mTotalOperationsHandled;
@@ -277,7 +160,7 @@
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
     // starting the next client).
-    public class InternalCallback implements BaseClientMonitor.Callback {
+    private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
             Slog.d(getTag(), "[Started] " + clientMonitor);
@@ -286,16 +169,11 @@
                 mCoexCoordinator.addAuthenticationClient(mSensorType,
                         (AuthenticationClient<?>) clientMonitor);
             }
-
-            if (mCurrentOperation.mClientCallback != null) {
-                mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
-            }
         }
 
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
-                clientMonitor.destroy();
                 if (mCurrentOperation == null) {
                     Slog.e(getTag(), "[Finishing] " + clientMonitor
                             + " but current operation is null, success: " + success
@@ -303,9 +181,9 @@
                     return;
                 }
 
-                if (clientMonitor != mCurrentOperation.mClientMonitor) {
+                if (!mCurrentOperation.isFor(clientMonitor)) {
                     Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
-                            + " current: " + mCurrentOperation.mClientMonitor);
+                            + " current: " + mCurrentOperation);
                     return;
                 }
 
@@ -315,36 +193,33 @@
                             (AuthenticationClient<?>) clientMonitor);
                 }
 
-                mCurrentOperation.mState = Operation.STATE_FINISHED;
-
-                if (mCurrentOperation.mClientCallback != null) {
-                    mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
-                }
-
                 if (mGestureAvailabilityDispatcher != null) {
                     mGestureAvailabilityDispatcher.markSensorActive(
-                            mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
+                            mCurrentOperation.getSensorId(), false /* active */);
                 }
 
                 if (mRecentOperations.size() >= mRecentOperationsLimit) {
                     mRecentOperations.remove(0);
                 }
-                mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
+                mRecentOperations.add(mCurrentOperation.getProtoEnum());
                 mCurrentOperation = null;
                 mTotalOperationsHandled++;
                 startNextOperationIfIdle();
             });
         }
-    }
+    };
 
     @VisibleForTesting
-    BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
+    BiometricScheduler(@NonNull String tag,
+            @NonNull Handler handler,
+            @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull IBiometricService biometricService, int recentOperationsLimit,
+            @NonNull IBiometricService biometricService,
+            int recentOperationsLimit,
             @NonNull CoexCoordinator coexCoordinator) {
         mBiometricTag = tag;
+        mHandler = handler;
         mSensorType = sensorType;
-        mInternalCallback = new InternalCallback();
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
         mPendingOperations = new ArrayDeque<>();
         mBiometricService = biometricService;
@@ -356,6 +231,7 @@
 
     /**
      * Creates a new scheduler.
+     *
      * @param tag for the specific instance of the scheduler. Should be unique.
      * @param sensorType the sensorType that this scheduler is handling.
      * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
@@ -364,16 +240,14 @@
     public BiometricScheduler(@NonNull String tag,
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
-                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
-                CoexCoordinator.getInstance());
+        this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
+                IBiometricService.Stub.asInterface(
+                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+                LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
     }
 
-    /**
-     * @return A reference to the internal callback that should be invoked whenever the scheduler
-     *         needs to (e.g. client started, client finished).
-     */
-    @NonNull protected InternalCallback getInternalCallback() {
+    @VisibleForTesting
+    public BaseClientMonitor.Callback getInternalCallback() {
         return mInternalCallback;
     }
 
@@ -392,72 +266,46 @@
         }
 
         mCurrentOperation = mPendingOperations.poll();
-        final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
         Slog.d(getTag(), "[Polled] " + mCurrentOperation);
 
         // If the operation at the front of the queue has been marked for cancellation, send
         // ERROR_CANCELED. No need to start this client.
-        if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+        if (mCurrentOperation.isMarkedCanceling()) {
             Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
-            if (!(currentClient instanceof Interruptable)) {
-                throw new IllegalStateException("Mis-implemented client or scheduler, "
-                        + "trying to cancel non-interruptable operation: " + mCurrentOperation);
-            }
-
-            final Interruptable interruptable = (Interruptable) currentClient;
-            interruptable.cancelWithoutStarting(getInternalCallback());
+            mCurrentOperation.cancel(mHandler, mInternalCallback);
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
             return;
         }
 
-        if (mGestureAvailabilityDispatcher != null
-                && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
+        if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
             mGestureAvailabilityDispatcher.markSensorActive(
-                    mCurrentOperation.mClientMonitor.getSensorId(),
-                    true /* active */);
+                    mCurrentOperation.getSensorId(), true /* active */);
         }
 
         // Not all operations start immediately. BiometricPrompt waits for its operation
         // to arrive at the head of the queue, before pinging it to start.
-        final boolean shouldStartNow = currentClient.getCookie() == 0;
-        if (shouldStartNow) {
-            if (mCurrentOperation.isUnstartableHalOperation()) {
-                final HalClientMonitor<?> halClientMonitor =
-                        (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
+        final int cookie = mCurrentOperation.isReadyToStart();
+        if (cookie == 0) {
+            if (!mCurrentOperation.start(mInternalCallback)) {
                 // Note down current length of queue
                 final int pendingOperationsLength = mPendingOperations.size();
-                final Operation lastOperation = mPendingOperations.peekLast();
+                final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
                 Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
                         + ". Last pending operation: " + lastOperation);
 
-                // For current operations, 1) unableToStart, which notifies the caller-side, then
-                // 2) notify operation's callback, to notify applicable system service that the
-                // operation failed.
-                halClientMonitor.unableToStart();
-                if (mCurrentOperation.mClientCallback != null) {
-                    mCurrentOperation.mClientCallback.onClientFinished(
-                            mCurrentOperation.mClientMonitor, false /* success */);
-                }
-
                 // Then for each operation currently in the pending queue at the time of this
                 // failure, do the same as above. Otherwise, it's possible that something like
                 // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
                 for (int i = 0; i < pendingOperationsLength; i++) {
-                    final Operation operation = mPendingOperations.pollFirst();
-                    if (operation == null) {
+                    final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
+                    if (operation != null) {
+                        Slog.w(getTag(), "[Aborting Operation] " + operation);
+                        operation.abort();
+                    } else {
                         Slog.e(getTag(), "Null operation, index: " + i
                                 + ", expected length: " + pendingOperationsLength);
-                        break;
                     }
-                    if (operation.isHalOperation()) {
-                        ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
-                    }
-                    if (operation.mClientCallback != null) {
-                        operation.mClientCallback.onClientFinished(operation.mClientMonitor,
-                                false /* success */);
-                    }
-                    Slog.w(getTag(), "[Aborted Operation] " + operation);
                 }
 
                 // It's possible that during cleanup a new set of operations came in. We can try to
@@ -465,25 +313,20 @@
                 // actually be multiple operations (i.e. updateActiveUser + authenticate).
                 mCurrentOperation = null;
                 startNextOperationIfIdle();
-            } else {
-                Slog.d(getTag(), "[Starting] " + mCurrentOperation);
-                currentClient.start(getInternalCallback());
-                mCurrentOperation.mState = Operation.STATE_STARTED;
             }
         } else {
             try {
-                mBiometricService.onReadyForAuthentication(currentClient.getCookie());
+                mBiometricService.onReadyForAuthentication(cookie);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
             }
             Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
-            mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
         }
     }
 
     /**
      * Starts the {@link #mCurrentOperation} if
-     * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
+     * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and
      * 2) its cookie matches this cookie
      *
      * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
@@ -499,45 +342,13 @@
             Slog.e(getTag(), "Current operation is null");
             return;
         }
-        if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
-            if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
-                Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
-                        + mCurrentOperation);
-                // This should trigger the internal onClientFinished callback, which clears the
-                // operation and starts the next one.
-                final ErrorConsumer errorConsumer =
-                        (ErrorConsumer) mCurrentOperation.mClientMonitor;
-                errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
-                        0 /* vendorCode */);
-                return;
-            } else {
-                Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
-                        + ", expected STATE_WAITING_FOR_COOKIE");
-                return;
-            }
-        }
-        if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
-            Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
-                    + ", received: " + cookie);
-            return;
-        }
 
-        if (mCurrentOperation.isUnstartableHalOperation()) {
+        if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
+            Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
+        } else {
             Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
-            // This is BiometricPrompt trying to auth but something's wrong with the HAL.
-            final HalClientMonitor<?> halClientMonitor =
-                    (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
-            halClientMonitor.unableToStart();
-            if (mCurrentOperation.mClientCallback != null) {
-                mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
-                        false /* success */);
-            }
             mCurrentOperation = null;
             startNextOperationIfIdle();
-        } else {
-            Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
-            mCurrentOperation.mState = Operation.STATE_STARTED;
-            mCurrentOperation.mClientMonitor.start(getInternalCallback());
         }
     }
 
@@ -562,17 +373,14 @@
         // pending clients as canceling. Once they reach the head of the queue, the scheduler will
         // send ERROR_CANCELED and skip the operation.
         if (clientMonitor.interruptsPrecedingClients()) {
-            for (Operation operation : mPendingOperations) {
-                if (operation.mClientMonitor instanceof Interruptable
-                        && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
-                    Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
-                            + operation.mClientMonitor);
-                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+            for (BiometricSchedulerOperation operation : mPendingOperations) {
+                if (operation.markCanceling()) {
+                    Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
                 }
             }
         }
 
-        mPendingOperations.add(new Operation(clientMonitor, clientCallback));
+        mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
         Slog.d(getTag(), "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
@@ -580,67 +388,34 @@
         // cancellable, start the cancellation process.
         if (clientMonitor.interruptsPrecedingClients()
                 && mCurrentOperation != null
-                && mCurrentOperation.mClientMonitor instanceof Interruptable
-                && mCurrentOperation.mState == Operation.STATE_STARTED) {
+                && mCurrentOperation.isInterruptable()
+                && mCurrentOperation.isStarted()) {
             Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
-            cancelInternal(mCurrentOperation);
-        }
-
-        startNextOperationIfIdle();
-    }
-
-    private void cancelInternal(Operation operation) {
-        if (operation != mCurrentOperation) {
-            Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
-            return;
-        }
-        if (!(operation.mClientMonitor instanceof Interruptable)) {
-            Slog.w(getTag(), "Operation not interruptable: " + operation);
-            return;
-        }
-        if (operation.mState == Operation.STATE_STARTED_CANCELING) {
-            Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
-            return;
-        }
-        if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
-            Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
-            // We can set it to null immediately, since the HAL was never notified to start.
-            if (mCurrentOperation != null) {
-                mCurrentOperation.mClientMonitor.destroy();
-            }
-            mCurrentOperation = null;
+            mCurrentOperation.cancel(mHandler, mInternalCallback);
+        } else {
             startNextOperationIfIdle();
-            return;
         }
-        Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor);
-        final Interruptable interruptable = (Interruptable) operation.mClientMonitor;
-        interruptable.cancel();
-        operation.mState = Operation.STATE_STARTED_CANCELING;
-
-        // Add a watchdog. If the HAL does not acknowledge within the timeout, we will
-        // forcibly finish this client.
-        mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
-                CancellationWatchdog.DELAY_MS);
     }
 
     /**
      * Requests to cancel enrollment.
      * @param token from the caller, should match the token passed in when requesting enrollment
      */
-    public void cancelEnrollment(IBinder token) {
-        if (mCurrentOperation == null) {
-            Slog.e(getTag(), "Unable to cancel enrollment, null operation");
-            return;
-        }
-        final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
-        final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
-        if (!isEnrolling || !tokenMatches) {
-            Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
-                    + " tokenMatches: " + tokenMatches);
-            return;
-        }
+    public void cancelEnrollment(IBinder token, long requestId) {
+        Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
 
-        cancelInternal(mCurrentOperation);
+        if (mCurrentOperation != null
+                && canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
+            Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
+            mCurrentOperation.cancel(mHandler, mInternalCallback);
+        } else {
+            for (BiometricSchedulerOperation operation : mPendingOperations) {
+                if (canCancelEnrollOperation(operation, token, requestId)) {
+                    Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
+                    operation.markCanceling();
+                }
+            }
+        }
     }
 
     /**
@@ -649,62 +424,42 @@
      * @param requestId the id returned when requesting authentication
      */
     public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
-                + " current: " + mCurrentOperation
-                + " stack size: " + mPendingOperations.size());
+        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
 
         if (mCurrentOperation != null
                 && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
-            cancelInternal(mCurrentOperation);
+            Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
+            mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
-            // Look through the current queue for all authentication clients for the specified
-            // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
-            // all of them, instead of just the first one, since the API surface currently doesn't
-            // allow us to distinguish between multiple authentication requests from the same
-            // process. However, this generally does not happen anyway, and would be a class of
-            // bugs on its own.
-            for (Operation operation : mPendingOperations) {
+            for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (canCancelAuthOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Marking " + operation
-                            + " as STATE_WAITING_IN_QUEUE_CANCELING");
-                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+                    Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
+                    operation.markCanceling();
                 }
             }
         }
     }
 
-    private static boolean canCancelAuthOperation(Operation operation, IBinder token,
-            long requestId) {
+    private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
+            IBinder token, long requestId) {
+        return operation.isEnrollOperation()
+                && operation.isMatchingToken(token)
+                && operation.isMatchingRequestId(requestId);
+    }
+
+    private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation,
+            IBinder token, long requestId) {
         // TODO: restrict callers that can cancel without requestId (negative value)?
-        return isAuthenticationOrDetectionOperation(operation)
-                && operation.mClientMonitor.getToken() == token
-                && isMatchingRequestId(operation, requestId);
-    }
-
-    // By default, monitors are not associated with a request id to retain the original
-    // behavior (i.e. if no requestId is explicitly set then assume it matches)
-    private static boolean isMatchingRequestId(Operation operation, long requestId) {
-        return !operation.mClientMonitor.hasRequestId()
-                || operation.mClientMonitor.getRequestId() == requestId;
-    }
-
-    private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
-        final boolean isAuthentication =
-                operation.mClientMonitor instanceof AuthenticationConsumer;
-        final boolean isDetection =
-                operation.mClientMonitor instanceof DetectionConsumer;
-        return isAuthentication || isDetection;
+        return operation.isAuthenticationOrDetectionOperation()
+                && operation.isMatchingToken(token)
+                && operation.isMatchingRequestId(requestId);
     }
 
     /**
      * @return the current operation
      */
     public BaseClientMonitor getCurrentClient() {
-        if (mCurrentOperation == null) {
-            return null;
-        }
-        return mCurrentOperation.mClientMonitor;
+        return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
     }
 
     public int getCurrentPendingCount() {
@@ -719,7 +474,7 @@
                 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
         final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
         final List<String> pendingOperations = new ArrayList<>();
-        for (Operation operation : mPendingOperations) {
+        for (BiometricSchedulerOperation operation : mPendingOperations) {
             pendingOperations.add(operation.toString());
         }
 
@@ -735,7 +490,7 @@
         pw.println("Type: " + mSensorType);
         pw.println("Current operation: " + mCurrentOperation);
         pw.println("Pending operations: " + mPendingOperations.size());
-        for (Operation operation : mPendingOperations) {
+        for (BiometricSchedulerOperation operation : mPendingOperations) {
             pw.println("Pending operation: " + operation);
         }
         for (CrashState crashState : mCrashStates) {
@@ -746,7 +501,7 @@
     public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
         final ProtoOutputStream proto = new ProtoOutputStream();
         proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
-                ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
+                ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
         proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
 
         if (!mRecentOperations.isEmpty()) {
@@ -771,6 +526,7 @@
      * HAL dies.
      */
     public void reset() {
+        Slog.d(getTag(), "Resetting scheduler");
         mPendingOperations.clear();
         mCurrentOperation = null;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
new file mode 100644
index 0000000..e8b50d9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -0,0 +1,421 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains all the necessary information for a HAL operation.
+ */
+public class BiometricSchedulerOperation {
+    protected static final String TAG = "BiometricSchedulerOperation";
+
+    /**
+     * The operation is added to the list of pending operations and waiting for its turn.
+     */
+    protected static final int STATE_WAITING_IN_QUEUE = 0;
+
+    /**
+     * The operation is added to the list of pending operations, but a subsequent operation
+     * has been added. This state only applies to {@link Interruptable} operations. When this
+     * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
+     */
+    protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
+
+    /**
+     * The operation has reached the front of the queue and has started.
+     */
+    protected static final int STATE_STARTED = 2;
+
+    /**
+     * The operation was started, but is now canceling. Operations should wait for the HAL to
+     * acknowledge that the operation was canceled, at which point it finishes.
+     */
+    protected static final int STATE_STARTED_CANCELING = 3;
+
+    /**
+     * The operation has reached the head of the queue but is waiting for BiometricService
+     * to acknowledge and start the operation.
+     */
+    protected static final int STATE_WAITING_FOR_COOKIE = 4;
+
+    /**
+     * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
+     */
+    protected static final int STATE_FINISHED = 5;
+
+    @IntDef({STATE_WAITING_IN_QUEUE,
+            STATE_WAITING_IN_QUEUE_CANCELING,
+            STATE_STARTED,
+            STATE_STARTED_CANCELING,
+            STATE_WAITING_FOR_COOKIE,
+            STATE_FINISHED})
+    @Retention(RetentionPolicy.SOURCE)
+    protected @interface OperationState {}
+
+    private static final int CANCEL_WATCHDOG_DELAY_MS = 3000;
+
+    @NonNull
+    private final BaseClientMonitor mClientMonitor;
+    @Nullable
+    private final BaseClientMonitor.Callback mClientCallback;
+    @OperationState
+    private int mState;
+    @VisibleForTesting
+    @NonNull
+    final Runnable mCancelWatchdog;
+
+    BiometricSchedulerOperation(
+            @NonNull BaseClientMonitor clientMonitor,
+            @Nullable BaseClientMonitor.Callback callback
+    ) {
+        this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
+    }
+
+    protected BiometricSchedulerOperation(
+            @NonNull BaseClientMonitor clientMonitor,
+            @Nullable BaseClientMonitor.Callback callback,
+            @OperationState int state
+    ) {
+        mClientMonitor = clientMonitor;
+        mClientCallback = callback;
+        mState = state;
+        mCancelWatchdog = () -> {
+            if (!isFinished()) {
+                Slog.e(TAG, "[Watchdog Triggered]: " + this);
+                getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
+            }
+        };
+    }
+
+    /**
+     * Zero if this operation is ready to start or has already started. A non-zero cookie
+     * is returned if the operation has not started and is waiting on
+     * {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}.
+     *
+     * @return cookie or 0 if ready/started
+     */
+    public int isReadyToStart() {
+        if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) {
+            final int cookie = mClientMonitor.getCookie();
+            if (cookie != 0) {
+                mState = STATE_WAITING_FOR_COOKIE;
+            }
+            return cookie;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Start this operation without waiting for a cookie
+     * (i.e. {@link #isReadyToStart() returns zero}
+     *
+     * @param callback lifecycle callback
+     * @return if this operation started
+     */
+    public boolean start(@NonNull BaseClientMonitor.Callback callback) {
+        checkInState("start",
+                STATE_WAITING_IN_QUEUE,
+                STATE_WAITING_FOR_COOKIE,
+                STATE_WAITING_IN_QUEUE_CANCELING);
+
+        if (mClientMonitor.getCookie() != 0) {
+            throw new IllegalStateException("operation requires cookie");
+        }
+
+        return doStart(callback);
+    }
+
+    /**
+     * Start this operation after receiving the given cookie.
+     *
+     * @param callback lifecycle callback
+     * @param cookie   cookie indicting the operation should begin
+     * @return if this operation started
+     */
+    public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) {
+        checkInState("start",
+                STATE_WAITING_IN_QUEUE,
+                STATE_WAITING_FOR_COOKIE,
+                STATE_WAITING_IN_QUEUE_CANCELING);
+
+        if (mClientMonitor.getCookie() != cookie) {
+            Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
+            return false;
+        }
+
+        return doStart(callback);
+    }
+
+    private boolean doStart(@NonNull BaseClientMonitor.Callback callback) {
+        final BaseClientMonitor.Callback cb = getWrappedCallback(callback);
+
+        if (mState == STATE_WAITING_IN_QUEUE_CANCELING) {
+            Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this);
+
+            cb.onClientFinished(mClientMonitor, true /* success */);
+            if (mClientMonitor instanceof ErrorConsumer) {
+                final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor;
+                errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                        0 /* vendorCode */);
+            } else {
+                Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer");
+            }
+
+            return false;
+        }
+
+        if (isUnstartableHalOperation()) {
+            Slog.v(TAG, "unable to start: " + this);
+            ((HalClientMonitor<?>) mClientMonitor).unableToStart();
+            cb.onClientFinished(mClientMonitor, false /* success */);
+            return false;
+        }
+
+        mState = STATE_STARTED;
+        mClientMonitor.start(cb);
+
+        Slog.v(TAG, "started: " + this);
+        return true;
+    }
+
+    /**
+     * Abort a pending operation.
+     *
+     * This is similar to cancel but the operation must not have been started. It will
+     * immediately abort the operation and notify the client that it has finished unsuccessfully.
+     */
+    public void abort() {
+        checkInState("cannot abort a non-pending operation",
+                STATE_WAITING_IN_QUEUE,
+                STATE_WAITING_FOR_COOKIE,
+                STATE_WAITING_IN_QUEUE_CANCELING);
+
+        if (isHalOperation()) {
+            ((HalClientMonitor<?>) mClientMonitor).unableToStart();
+        }
+        getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
+
+        Slog.v(TAG, "Aborted: " + this);
+    }
+
+    /** Flags this operation as canceled, if possible, but does not cancel it until started. */
+    public boolean markCanceling() {
+        if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
+            mState = STATE_WAITING_IN_QUEUE_CANCELING;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Cancel the operation now.
+     *
+     * @param handler handler to use for the cancellation watchdog
+     * @param callback lifecycle callback (only used if this operation hasn't started, otherwise
+     *                 the callback used from {@link #start(BaseClientMonitor.Callback)} is used)
+     */
+    public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) {
+        checkNotInState("cancel", STATE_FINISHED);
+
+        final int currentState = mState;
+        if (!isInterruptable()) {
+            Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this);
+            return;
+        }
+        if (currentState == STATE_STARTED_CANCELING) {
+            Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this);
+            return;
+        }
+
+        mState = STATE_STARTED_CANCELING;
+        if (currentState == STATE_WAITING_IN_QUEUE
+                || currentState == STATE_WAITING_IN_QUEUE_CANCELING
+                || currentState == STATE_WAITING_FOR_COOKIE) {
+            Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor);
+            ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback));
+        } else {
+            Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor);
+            ((Interruptable) mClientMonitor).cancel();
+        }
+
+        // forcibly finish this client if the HAL does not acknowledge within the timeout
+        handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS);
+    }
+
+    @NonNull
+    private BaseClientMonitor.Callback getWrappedCallback() {
+        return getWrappedCallback(null);
+    }
+
+    @NonNull
+    private BaseClientMonitor.Callback getWrappedCallback(
+            @Nullable BaseClientMonitor.Callback callback) {
+        final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                Slog.d(TAG, "[Finished / destroy]: " + clientMonitor);
+                mClientMonitor.destroy();
+                mState = STATE_FINISHED;
+            }
+        };
+        return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback);
+    }
+
+    /** {@link BaseClientMonitor#getSensorId()}. */
+    public int getSensorId() {
+        return mClientMonitor.getSensorId();
+    }
+
+    /** {@link BaseClientMonitor#getProtoEnum()}. */
+    public int getProtoEnum() {
+        return mClientMonitor.getProtoEnum();
+    }
+
+    /** {@link BaseClientMonitor#getTargetUserId()}. */
+    public int getTargetUserId() {
+        return mClientMonitor.getTargetUserId();
+    }
+
+    /** If the given clientMonitor is the same as the one in the constructor. */
+    public boolean isFor(@NonNull BaseClientMonitor clientMonitor) {
+        return mClientMonitor == clientMonitor;
+    }
+
+    /** If this operation is {@link Interruptable}. */
+    public boolean isInterruptable() {
+        return mClientMonitor instanceof Interruptable;
+    }
+
+    private boolean isHalOperation() {
+        return mClientMonitor instanceof HalClientMonitor<?>;
+    }
+
+    private boolean isUnstartableHalOperation() {
+        if (isHalOperation()) {
+            final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
+            if (client.getFreshDaemon() == null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** If this operation is an enrollment. */
+    public boolean isEnrollOperation() {
+        return mClientMonitor instanceof EnrollClient;
+    }
+
+    /** If this operation is authentication. */
+    public boolean isAuthenticateOperation() {
+        return mClientMonitor instanceof AuthenticationClient;
+    }
+
+    /** If this operation is authentication or detection. */
+    public boolean isAuthenticationOrDetectionOperation() {
+        final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer;
+        final boolean isDetection = mClientMonitor instanceof DetectionConsumer;
+        return isAuthentication || isDetection;
+    }
+
+    /** If this operation performs acquisition {@link AcquisitionClient}. */
+    public boolean isAcquisitionOperation() {
+        return mClientMonitor instanceof AcquisitionClient;
+    }
+
+    /**
+     * If this operation matches the original requestId.
+     *
+     * By default, monitors are not associated with a request id to retain the original
+     * behavior (i.e. if no requestId is explicitly set then assume it matches)
+     *
+     * @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}.
+     */
+    public boolean isMatchingRequestId(long requestId) {
+        return !mClientMonitor.hasRequestId()
+                || mClientMonitor.getRequestId() == requestId;
+    }
+
+    /** If the token matches */
+    public boolean isMatchingToken(@Nullable IBinder token) {
+        return mClientMonitor.getToken() == token;
+    }
+
+    /** If this operation has started. */
+    public boolean isStarted() {
+        return mState == STATE_STARTED;
+    }
+
+    /** If this operation is cancelling but has not yet completed. */
+    public boolean isCanceling() {
+        return mState == STATE_STARTED_CANCELING;
+    }
+
+    /** If this operation has finished and completed its lifecycle. */
+    public boolean isFinished() {
+        return mState == STATE_FINISHED;
+    }
+
+    /** If {@link #markCanceling()} was called but the operation hasn't been canceled. */
+    public boolean isMarkedCanceling() {
+        return mState == STATE_WAITING_IN_QUEUE_CANCELING;
+    }
+
+    /**
+     * The monitor passed to the constructor.
+     * @deprecated avoid using and move to encapsulate within the operation
+     */
+    @Deprecated
+    public BaseClientMonitor getClientMonitor() {
+        return mClientMonitor;
+    }
+
+    private void checkNotInState(String message, @OperationState int... states) {
+        for (int state : states) {
+            if (mState == state) {
+                throw new IllegalStateException(message + ": illegal state= " + state);
+            }
+        }
+    }
+
+    private void checkInState(String message, @OperationState int... states) {
+        for (int state : states) {
+            if (mState == state) {
+                return;
+            }
+        }
+        throw new IllegalStateException(message + ": illegal state= " + mState);
+    }
+
+    @Override
+    public String toString() {
+        return mClientMonitor + ", State: " + mState;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
index fab98b6..d5093c75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -32,6 +32,11 @@
      * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens
      * if the client is still waiting in the pending queue and got notified that a subsequent
      * operation is preempting it.
+     *
+     * This method must invoke
+     * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the
+     * given callback (with success).
+     *
      * @param callback invoked when the operation is completed.
      */
     void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index b056bf8..603cc22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -16,10 +16,14 @@
 
 package com.android.server.biometrics.sensors;
 
+import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Slog;
@@ -68,9 +72,8 @@
                     return;
                 }
 
-                Slog.d(getTag(), "[Client finished] "
-                        + clientMonitor + ", success: " + success);
-                if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) {
+                Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+                if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
                     mCurrentOperation = null;
                     startNextOperationIfIdle();
                 } else {
@@ -83,26 +86,30 @@
     }
 
     @VisibleForTesting
-    UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
+    public UserAwareBiometricScheduler(@NonNull String tag,
+            @NonNull Handler handler,
+            @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull IBiometricService biometricService,
             @NonNull CurrentUserRetriever currentUserRetriever,
             @NonNull UserSwitchCallback userSwitchCallback,
             @NonNull CoexCoordinator coexCoordinator) {
-        super(tag, sensorType, gestureAvailabilityDispatcher, biometricService,
+        super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
                 LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
 
         mCurrentUserRetriever = currentUserRetriever;
         mUserSwitchCallback = userSwitchCallback;
     }
 
-    public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
+    public UserAwareBiometricScheduler(@NonNull String tag,
+            @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull CurrentUserRetriever currentUserRetriever,
             @NonNull UserSwitchCallback userSwitchCallback) {
-        this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
-                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
-                userSwitchCallback, CoexCoordinator.getInstance());
+        this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
+                IBiometricService.Stub.asInterface(
+                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+                currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
     }
 
     @Override
@@ -122,7 +129,7 @@
         }
 
         final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
-        final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
+        final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
 
         if (nextUserId == currentUserId) {
             super.startNextOperationIfIdle();
@@ -133,8 +140,8 @@
                     new ClientFinishedCallback(startClient);
 
             Slog.d(getTag(), "[Starting User] " + startClient);
-            mCurrentOperation = new Operation(
-                    startClient, finishedCallback, Operation.STATE_STARTED);
+            mCurrentOperation = new BiometricSchedulerOperation(
+                    startClient, finishedCallback, STATE_STARTED);
             startClient.start(finishedCallback);
         } else {
             if (mStopUserClient != null) {
@@ -147,8 +154,8 @@
 
                 Slog.d(getTag(), "[Stopping User] current: " + currentUserId
                         + ", next: " + nextUserId + ". " + mStopUserClient);
-                mCurrentOperation = new Operation(
-                        mStopUserClient, finishedCallback, Operation.STATE_STARTED);
+                mCurrentOperation = new BiometricSchedulerOperation(
+                        mStopUserClient, finishedCallback, STATE_STARTED);
                 mStopUserClient.start(finishedCallback);
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 675ee545..039b08e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -213,7 +213,7 @@
         }
 
         @Override // Binder call
-        public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
+        public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -221,23 +221,24 @@
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
-                return;
+                return -1;
             }
 
-            provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+            return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
                     receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
         }
 
         @Override // Binder call
-        public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
+        public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
             // TODO(b/145027036): Implement this.
+            return -1;
         }
 
         @Override // Binder call
-        public void cancelEnrollment(final IBinder token) {
+        public void cancelEnrollment(final IBinder token, long requestId) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -246,7 +247,7 @@
                 return;
             }
 
-            provider.second.cancelEnrollment(provider.first, token);
+            provider.second.cancelEnrollment(provider.first, token, requestId);
         }
 
         @Override // Binder call
@@ -624,7 +625,7 @@
         private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
             for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                 mServiceProviders.add(
-                        new Face10(getContext(), hidlSensor, mLockoutResetDispatcher));
+                        Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
             }
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index e099ba3..77e431c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -94,12 +94,12 @@
     void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge);
 
-    void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+    long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
             @NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
             boolean debugConsent);
 
-    void cancelEnrollment(int sensorId, @NonNull IBinder token);
+    void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
 
     long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index a806277..aae4fbe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -82,13 +82,14 @@
 
     FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName,
+            @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
             @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
             boolean debugConsent) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
                 false /* shouldVibrate */);
+        setRequestId(requestId);
         mEnrollIgnoreList = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
         mEnrollIgnoreListVendor = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 4bae775..ae507ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -327,17 +327,18 @@
     }
 
     @Override
-    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
             @Nullable Surface previewSurface, boolean debugConsent) {
+        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             final int maxTemplatesPerUser = mSensors.get(
                     sensorId).getSensorProperties().maxEnrollmentsPerUser;
             final FaceEnrollClient client = new FaceEnrollClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
+                    opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
                     debugConsent);
             scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
@@ -351,11 +352,13 @@
                 }
             });
         });
+        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
-        mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+        mHandler.post(() ->
+                mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 525e508..15d6a89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.face.EnrollmentStageConfig;
 import android.hardware.biometrics.face.Error;
 import android.hardware.biometrics.face.IFace;
@@ -188,6 +189,24 @@
                 Slog.w(TAG, "close");
                 cb.onSessionClosed();
             }
+
+            @Override
+            public ICancellationSignal authenticateWithContext(
+                    long operationId, OperationContext context) {
+                return authenticate(operationId);
+            }
+
+            @Override
+            public ICancellationSignal enrollWithContext(
+                    HardwareAuthToken hat, byte enrollmentType, byte[] features,
+                    NativeHandle previewSurface, OperationContext context) {
+                return enroll(hat, enrollmentType, features, previewSurface);
+            }
+
+            @Override
+            public ICancellationSignal detectInteractionWithContext(OperationContext context) {
+                return detectInteraction();
+            }
         };
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index f4dcbbb..e957794 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -333,12 +333,13 @@
     Face10(@NonNull Context context,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull Handler handler,
             @NonNull BiometricScheduler scheduler) {
         mSensorProperties = sensorProps;
         mContext = context;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
-        mHandler = new Handler(Looper.getMainLooper());
+        mHandler = handler;
         mUsageStats = new UsageStats(context);
         mAuthenticatorIds = new HashMap<>();
         mLazyDaemon = Face10.this::getDaemon;
@@ -357,9 +358,11 @@
         }
     }
 
-    public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps,
+    public static Face10 newInstance(@NonNull Context context,
+            @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-        this(context, sensorProps, lockoutResetDispatcher,
+        final Handler handler = new Handler(Looper.getMainLooper());
+        return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
                 new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
                         null /* gestureAvailabilityTracker */));
     }
@@ -573,10 +576,11 @@
     }
 
     @Override
-    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
             @Nullable Surface previewSurface, boolean debugConsent) {
+        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -584,7 +588,7 @@
 
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+                    opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
 
             mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@@ -598,13 +602,12 @@
                 }
             });
         });
+        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
-        mHandler.post(() -> {
-            mScheduler.cancelEnrollment(token);
-        });
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+        mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
     }
 
     @Override
@@ -893,6 +896,8 @@
                     boolean success) {
                 if (success) {
                     mCurrentUserId = targetUserId;
+                } else {
+                    Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
                 }
             }
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 80828cced..31e5c86 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -53,12 +53,13 @@
 
     FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull byte[] hardwareAuthToken, @NonNull String owner,
+            @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
             @Nullable Surface previewSurface, int sensorId) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
                 false /* shouldVibrate */);
+        setRequestId(requestId);
         mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
         mEnrollIgnoreList = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 3e70ee5..6366e19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -249,7 +249,7 @@
         }
 
         @Override // Binder call
-        public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
+        public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                 final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
@@ -257,15 +257,15 @@
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
-                return;
+                return -1;
             }
 
-            provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+            return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
                     receiver, opPackageName, enrollReason);
         }
 
         @Override // Binder call
-        public void cancelEnrollment(final IBinder token) {
+        public void cancelEnrollment(final IBinder token, long requestId) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -274,7 +274,7 @@
                 return;
             }
 
-            provider.second.cancelEnrollment(provider.first, token);
+            provider.second.cancelEnrollment(provider.first, token, requestId);
         }
 
         @SuppressWarnings("deprecation")
@@ -818,7 +818,7 @@
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                 } else {
                     fingerprint21 = Fingerprint21.newInstance(getContext(),
-                            mFingerprintStateCallback, hidlSensor,
+                            mFingerprintStateCallback, hidlSensor, mHandler,
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                 }
                 mServiceProviders.add(fingerprint21);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 1772f81..535705c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -88,11 +88,11 @@
     /**
      * Schedules fingerprint enrollment.
      */
-    void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+    long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
             int userId, @NonNull IFingerprintServiceReceiver receiver,
             @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
 
-    void cancelEnrollment(int sensorId, @NonNull IBinder token);
+    void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
 
     long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index ccb34aa..67507cc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -57,7 +57,7 @@
     private boolean mIsPointerDown;
 
     FingerprintEnrollClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@@ -69,6 +69,7 @@
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+        setRequestId(requestId);
         mSensorProps = sensorProps;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 734b173..eb16c76 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -347,15 +347,16 @@
     }
 
     @Override
-    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
             @FingerprintManager.EnrollReason int enrollReason) {
+        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
                     .maxEnrollmentsPerUser;
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token,
+                    mSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     mSensors.get(sensorId).getSensorProperties(),
@@ -378,11 +379,13 @@
                 }
             });
         });
+        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
-        mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+        mHandler.post(() ->
+                mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index e771923..1eb153c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -17,10 +17,12 @@
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
@@ -182,6 +184,34 @@
             public void onUiReady() {
                 Slog.w(TAG, "onUiReady");
             }
+
+            @Override
+            public ICancellationSignal authenticateWithContext(
+                    long operationId, OperationContext context) {
+                return authenticate(operationId);
+            }
+
+            @Override
+            public ICancellationSignal enrollWithContext(
+                    HardwareAuthToken hat, OperationContext context) {
+                return enroll(hat);
+            }
+
+            @Override
+            public ICancellationSignal detectInteractionWithContext(OperationContext context) {
+                return detectInteraction();
+            }
+
+            @Override
+            public void onPointerDownWithContext(PointerContext context) {
+                onPointerDown(
+                        context.pointerId, context.x, context.y, context.minor, context.major);
+            }
+
+            @Override
+            public void onPointerUpWithContext(PointerContext context) {
+                onPointerUp(context.pointerId);
+            }
         };
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 5f2f4cf..6feb5fa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -42,7 +42,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IHwBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -320,7 +319,8 @@
     Fingerprint21(@NonNull Context context,
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
+            @NonNull BiometricScheduler scheduler,
+            @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull HalResultController controller) {
         mContext = context;
@@ -356,16 +356,15 @@
     public static Fingerprint21 newInstance(@NonNull Context context,
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
+            @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        final Handler handler = new Handler(Looper.getMainLooper());
         final BiometricScheduler scheduler =
                 new BiometricScheduler(TAG,
                         BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
                         gestureAvailabilityDispatcher);
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
-                context, handler,
-                scheduler);
+                context, handler, scheduler);
         return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
                 lockoutResetDispatcher, controller);
     }
@@ -491,19 +490,25 @@
                 !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
         final FingerprintUpdateActiveUserClient client =
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
-                        mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
-                        hasEnrolled, mAuthenticatorIds, force);
+                        mContext.getOpPackageName(), mSensorProperties.sensorId,
+                        this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
                 if (success) {
                     mCurrentUserId = targetUserId;
+                } else {
+                    Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
                 }
             }
         });
     }
 
+    private int getCurrentUser() {
+        return mCurrentUserId;
+    }
+
     @Override
     public boolean containsSensor(int sensorId) {
         return mSensorProperties.sensorId == sensorId;
@@ -558,18 +563,20 @@
     }
 
     @Override
-    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
             @FingerprintManager.EnrollReason int enrollReason) {
+        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                    ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController,
-                    mSidefpsController, enrollReason);
+                    mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
+                    userId, hardwareAuthToken, opPackageName,
+                    FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
+                    mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
+                    enrollReason);
             mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -588,13 +595,12 @@
                 }
             });
         });
+        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
-        mHandler.post(() -> {
-            mScheduler.cancelEnrollment(token);
-        });
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+        mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index dd68b4d..273f8a5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,7 +26,6 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.FingerprintStateListener;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -135,43 +134,16 @@
     @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
 
     private static class TestableBiometricScheduler extends BiometricScheduler {
-        @NonNull private final TestableInternalCallback mInternalCallback;
         @NonNull private Fingerprint21UdfpsMock mFingerprint21;
 
-        TestableBiometricScheduler(@NonNull String tag,
+        TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
                 @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-            super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                    gestureAvailabilityDispatcher);
-            mInternalCallback = new TestableInternalCallback();
-        }
-
-        class TestableInternalCallback extends InternalCallback {
-            @Override
-            public void onClientStarted(BaseClientMonitor clientMonitor) {
-                super.onClientStarted(clientMonitor);
-                Slog.d(TAG, "Client started: " + clientMonitor);
-                mFingerprint21.setDebugMessage("Started: " + clientMonitor);
-            }
-
-            @Override
-            public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) {
-                super.onClientFinished(clientMonitor, success);
-                Slog.d(TAG, "Client finished: " + clientMonitor);
-                mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
-            }
+            super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
         }
 
         void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
             mFingerprint21 = fingerprint21;
         }
-
-        /**
-         * Expose the internal finish callback so it can be used for testing
-         */
-        @Override
-        @NonNull protected InternalCallback getInternalCallback() {
-            return mInternalCallback;
-        }
     }
 
     /**
@@ -280,7 +252,7 @@
 
         final Handler handler = new Handler(Looper.getMainLooper());
         final TestableBiometricScheduler scheduler =
-                new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
+                new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
         return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 1ebf44c..cc50bdf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -55,7 +55,7 @@
 
     FingerprintEnrollClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
+            long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -64,6 +64,7 @@
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
+        setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
         mEnrollReason = enrollReason;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index fd38bdd..a2c1892 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -31,6 +31,7 @@
 
 import java.io.File;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Sets the HAL's current active user, and updates the framework's authenticatorId cache.
@@ -40,7 +41,7 @@
     private static final String TAG = "FingerprintUpdateActiveUserClient";
     private static final String FP_DATA_DIR = "fpdata";
 
-    private final int mCurrentUserId;
+    private final Supplier<Integer> mCurrentUserId;
     private final boolean mForceUpdateAuthenticatorId;
     private final boolean mHasEnrolledBiometrics;
     private final Map<Integer, Long> mAuthenticatorIds;
@@ -48,8 +49,9 @@
 
     FingerprintUpdateActiveUserClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics,
-            @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) {
+            @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
+            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+            boolean forceUpdateAuthenticatorId) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
                 BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
@@ -63,7 +65,7 @@
     public void start(@NonNull Callback callback) {
         super.start(callback);
 
-        if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) {
+        if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
             callback.onClientFinished(this, true /* success */);
             return;
@@ -109,8 +111,10 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath());
-            mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+            final int targetId = getTargetUserId();
+            Slog.d(TAG, "Setting active user: " + targetId);
+            getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
+            mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
                     ? getFreshDaemon().getAuthenticatorId() : 0L);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
index 880dbf6..fe002ce 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -25,6 +25,7 @@
 import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
 import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
 
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 
 import android.annotation.NonNull;
@@ -157,17 +158,17 @@
         Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove =
                 new ArrayMap<>();
         for (String packageName : packageNames) {
-            Long versionCode = getVersionCodeOrNull(packageName);
-            if (versionCode == null) {
-                // Package isn't installed yet.
-                continue;
-            }
-
             Set<Long> changeIdsToSkip = packageToChangeIdsToSkip.getOrDefault(packageName,
                     emptySet());
-            Map<Long, PackageOverride> overridesToAdd = mOverridesParser.parsePackageOverrides(
-                    properties.getString(packageName, /* defaultValue= */ ""), packageName,
-                    versionCode, changeIdsToSkip);
+
+            Map<Long, PackageOverride> overridesToAdd = emptyMap();
+            Long versionCode = getVersionCodeOrNull(packageName);
+            if (versionCode != null) {
+                // Only if package installed add overrides, otherwise just remove.
+                overridesToAdd = mOverridesParser.parsePackageOverrides(
+                        properties.getString(packageName, /* defaultValue= */ ""), packageName,
+                        versionCode, changeIdsToSkip);
+            }
             if (!overridesToAdd.isEmpty()) {
                 packageNameToOverridesToAdd.put(packageName,
                         new CompatibilityOverrideConfig(overridesToAdd));
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index 7fe24ff..78d55b9 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -43,14 +43,14 @@
  */
 public final class DeviceState {
     /**
-     * Flag that indicates sticky requests should be cancelled when this device state becomes the
+     * Flag that indicates override requests should be cancelled when this device state becomes the
      * base device state.
      */
-    public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0;
+    public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0;
 
     /** @hide */
     @IntDef(prefix = {"FLAG_"}, flag = true, value = {
-            FLAG_CANCEL_STICKY_REQUESTS,
+            FLAG_CANCEL_OVERRIDE_REQUESTS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeviceStateFlags {}
@@ -114,4 +114,10 @@
     public int hashCode() {
         return Objects.hash(mIdentifier, mName, mFlags);
     }
+
+    /** Checks if a specific flag is set
+     */
+    public boolean hasFlag(int flagToCheckFor) {
+        return (mFlags & flagToCheckFor) == flagToCheckFor;
+    }
 }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 792feea..709af91 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -20,6 +20,7 @@
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
+import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
@@ -273,14 +274,14 @@
         synchronized (mLock) {
             final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
 
-            // Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS
+            // Whether or not at least one device state has the flag FLAG_CANCEL_OVERRIDE_REQUESTS
             // set. If set to true, the OverrideRequestController will be configured to allow sticky
             // requests.
             boolean hasTerminalDeviceState = false;
             mDeviceStates.clear();
             for (int i = 0; i < supportedDeviceStates.length; i++) {
                 DeviceState state = supportedDeviceStates[i];
-                if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
+                if (state.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
                     hasTerminalDeviceState = true;
                 }
                 mDeviceStates.put(state.getIdentifier(), state);
@@ -345,8 +346,8 @@
             }
             mBaseState = Optional.of(baseState);
 
-            if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
-                mOverrideRequestController.cancelStickyRequests();
+            if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
+                mOverrideRequestController.cancelOverrideRequests();
             }
             mOverrideRequestController.handleBaseStateChanged();
             updatePendingStateLocked();
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 05c9eb2..36cb416 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -153,6 +153,16 @@
     }
 
     /**
+     * Cancels all override requests, this could be due to the device being put
+     * into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS"
+     */
+    void cancelOverrideRequests() {
+        mTmpRequestsToCancel.clear();
+        mTmpRequestsToCancel.addAll(mRequests);
+        cancelRequestsLocked(mTmpRequestsToCancel);
+    }
+
+    /**
      * Returns {@code true} if this controller is current managing a request with the specified
      * {@code token}, {@code false} otherwise.
      */
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index c04032f..ffed68e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -54,6 +54,10 @@
 
     private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
 
+    public static final int AUTO_BRIGHTNESS_ENABLED = 1;
+    public static final int AUTO_BRIGHTNESS_DISABLED = 2;
+    public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3;
+
     // How long the current sensor reading is assumed to be valid beyond the current time.
     // This provides a bit of prediction, as well as ensures that the weight for the last sample is
     // non-zero, which in turn ensures that the total weight is non-zero.
@@ -214,6 +218,7 @@
     private IActivityTaskManager mActivityTaskManager;
     private PackageManager mPackageManager;
     private Context mContext;
+    private int mState = AUTO_BRIGHTNESS_DISABLED;
 
     private final Injector mInjector;
 
@@ -331,10 +336,11 @@
         return mCurrentBrightnessMapper.getAutoBrightnessAdjustment();
     }
 
-    public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
+    public void configure(int state, @Nullable BrightnessConfiguration configuration,
             float brightness, boolean userChangedBrightness, float adjustment,
             boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
-        mHbmController.setAutoBrightnessEnabled(enable);
+        mState = state;
+        mHbmController.setAutoBrightnessEnabled(mState);
         // While dozing, the application processor may be suspended which will prevent us from
         // receiving new information from the light sensor. On some devices, we may be able to
         // switch to a wake-up light sensor instead but for now we will simply disable the sensor
@@ -346,6 +352,7 @@
         if (userChangedAutoBrightnessAdjustment) {
             changed |= setAutoBrightnessAdjustment(adjustment);
         }
+        final boolean enable = mState == AUTO_BRIGHTNESS_ENABLED;
         if (userChangedBrightness && enable) {
             // Update the brightness curve with the new user control point. It's critical this
             // happens after we update the autobrightness adjustment since it may reset it.
@@ -459,6 +466,7 @@
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Automatic Brightness Controller Configuration:");
+        pw.println("  mState=" + configStateToString(mState));
         pw.println("  mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
         pw.println("  mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
         pw.println("  mDozeScaleFactor=" + mDozeScaleFactor);
@@ -520,6 +528,19 @@
         mScreenBrightnessThresholds.dump(pw);
     }
 
+    private String configStateToString(int state) {
+        switch (state) {
+        case AUTO_BRIGHTNESS_ENABLED:
+            return "AUTO_BRIGHTNESS_ENABLED";
+        case AUTO_BRIGHTNESS_DISABLED:
+            return "AUTO_BRIGHTNESS_DISABLED";
+        case AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE:
+            return "AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE";
+        default:
+            return String.valueOf(state);
+        }
+    }
+
     private boolean setLightSensorEnabled(boolean enable) {
         if (enable) {
             if (!mLightSensorEnabled) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c4d02c7..faf0038 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -62,6 +62,7 @@
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -127,6 +128,7 @@
     private static final int MSG_STOP = 9;
     private static final int MSG_UPDATE_BRIGHTNESS = 10;
     private static final int MSG_UPDATE_RBC = 11;
+    private static final int MSG_STATSD_HBM_BRIGHTNESS = 12;
 
     private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
@@ -136,6 +138,8 @@
     private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
     private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
 
+    private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
+
     // Trigger proximity if distance is less than 5 cm.
     private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
 
@@ -356,6 +360,9 @@
     private float mBrightnessRampRateSlowDecrease;
     private float mBrightnessRampRateSlowIncrease;
 
+    // Report HBM brightness change to StatsD
+    private int mDisplayStatsId;
+    private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
 
     // Whether or not to skip the initial brightness ramps into STATE_ON.
     private final boolean mSkipScreenOnBrightnessRamp;
@@ -466,6 +473,7 @@
         TAG = "DisplayPowerController[" + mDisplayId + "]";
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        mDisplayStatsId = mUniqueDisplayId.hashCode();
         mHandler = new DisplayControllerHandler(handler.getLooper());
 
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
@@ -763,6 +771,7 @@
             if (mDisplayDevice != device) {
                 mDisplayDevice = device;
                 mUniqueDisplayId = uniqueId;
+                mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
                 loadFromDisplayDeviceConfig(token, info);
                 updatePowerState();
@@ -816,7 +825,7 @@
         loadNitsRange(mContext.getResources());
         setUpAutoBrightness(mContext.getResources(), mHandler);
         reloadReduceBrightColours();
-        mHbmController.resetHbmData(info.width, info.height, token,
+        mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
                 mDisplayDeviceConfig.getHighBrightnessModeData());
     }
 
@@ -1004,7 +1013,15 @@
         }
     };
 
-    private final RampAnimator.Listener mRampAnimatorListener = this::sendUpdatePowerState;
+    private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
+        @Override
+        public void onAnimationEnd() {
+            sendUpdatePowerState();
+
+            final float brightness = mPowerState.getScreenBrightness();
+            reportStats(brightness);
+        }
+    };
 
     /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
     private void cleanupHandlerThreadAfterStop() {
@@ -1179,6 +1196,13 @@
                     && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                     && Float.isNaN(brightnessState)
                     && mAutomaticBrightnessController != null;
+        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+                    && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
+        final int autoBrightnessState = autoBrightnessEnabled
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : autoBrightnessDisabledDueToDisplayOff
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 
         final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
 
@@ -1229,7 +1253,7 @@
         // Configure auto-brightness.
         if (mAutomaticBrightnessController != null) {
             hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
-            mAutomaticBrightnessController.configure(autoBrightnessEnabled,
+            mAutomaticBrightnessController.configure(autoBrightnessState,
                     mBrightnessConfiguration,
                     mLastUserSetScreenBrightness,
                     userSetBrightnessChanged, autoBrightnessAdjustment,
@@ -1626,11 +1650,13 @@
         final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
         final IBinder displayToken =
                 mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
+        final String displayUniqueId =
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+                displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                 () -> {
                     sendUpdatePowerStateLocked();
                     postBrightnessChangeRunnable();
@@ -2462,6 +2488,42 @@
         }
     }
 
+    private void reportStats(float brightness) {
+        float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
+        synchronized(mCachedBrightnessInfo) {
+            if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
+                return;
+            }
+            hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
+        }
+
+        final boolean aboveTransition = brightness > hbmTransitionPoint;
+        final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
+
+        if (aboveTransition || oldAboveTransition) {
+            mLastStatsBrightness = brightness;
+            mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
+            if (aboveTransition != oldAboveTransition) {
+                // report immediately
+                logHbmBrightnessStats(brightness, mDisplayStatsId);
+            } else {
+                // delay for rate limiting
+                Message msg = mHandler.obtainMessage();
+                msg.what = MSG_STATSD_HBM_BRIGHTNESS;
+                msg.arg1 = Float.floatToIntBits(brightness);
+                msg.arg2 = mDisplayStatsId;
+                mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+            }
+        }
+    }
+
+    private final void logHbmBrightnessStats(float brightness, int displayStatsId) {
+        synchronized (mHandler) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
+        }
+    }
+
     private final class DisplayControllerHandler extends Handler {
         public DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -2526,6 +2588,10 @@
                     final int justActivated = msg.arg2;
                     handleRbcChanged(strengthChanged == 1, justActivated == 1);
                     break;
+
+                case MSG_STATSD_HBM_BRIGHTNESS:
+                    logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 1e1cfeb..16273ce 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -37,6 +37,7 @@
 import android.view.SurfaceControlHdrLayerInfoListener;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
 import com.android.server.display.DisplayManagerService.Clock;
 
@@ -80,6 +81,7 @@
     private boolean mIsInAllowedAmbientRange = false;
     private boolean mIsTimeAvailable = false;
     private boolean mIsAutoBrightnessEnabled = false;
+    private boolean mIsAutoBrightnessOffByState = false;
     private float mBrightness;
     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
     private boolean mIsHdrLayerPresent = false;
@@ -88,6 +90,7 @@
     private int mWidth;
     private int mHeight;
     private float mAmbientLux;
+    private int mDisplayStatsId;
 
     /**
      * If HBM is currently running, this is the start time for the current HBM session.
@@ -102,15 +105,15 @@
     private LinkedList<HbmEvent> mEvents = new LinkedList<>();
 
     HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
-            float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
-            Runnable hbmChangeCallback, Context context) {
-        this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax,
-                hbmData, hbmChangeCallback, context);
+            String displayUniqueId, float brightnessMin, float brightnessMax,
+            HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) {
+        this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
+            brightnessMax, hbmData, hbmChangeCallback, context);
     }
 
     @VisibleForTesting
     HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
-            IBinder displayToken, float brightnessMin, float brightnessMax,
+            IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
             HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
             Context context) {
         mInjector = injector;
@@ -126,10 +129,13 @@
         mRecalcRunnable = this::recalculateTimeAllowance;
         mHdrListener = new HdrListener();
 
-        resetHbmData(width, height, displayToken, hbmData);
+        resetHbmData(width, height, displayToken, displayUniqueId, hbmData);
     }
 
-    void setAutoBrightnessEnabled(boolean isEnabled) {
+    void setAutoBrightnessEnabled(int state) {
+        final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+        mIsAutoBrightnessOffByState =
+                state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
         if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) {
             return;
         }
@@ -231,10 +237,12 @@
         mSettingsObserver.stopObserving();
     }
 
-    void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData) {
+    void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
+            HighBrightnessModeData hbmData) {
         mWidth = width;
         mHeight = height;
         mHbmData = hbmData;
+        mDisplayStatsId = displayUniqueId.hashCode();
 
         unregisterHdrListener();
         mSkinThermalStatusObserver.stopObserving();
@@ -275,6 +283,7 @@
                 + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)"));
         pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
         pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
+        pw.println("  mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState);
         pw.println("  mIsHdrLayerPresent=" + mIsHdrLayerPresent);
         pw.println("  mBrightnessMin=" + mBrightnessMin);
         pw.println("  mBrightnessMax=" + mBrightnessMax);
@@ -436,11 +445,52 @@
     private void updateHbmMode() {
         int newHbmMode = calculateHighBrightnessMode();
         if (mHbmMode != newHbmMode) {
+            updateHbmStats(mHbmMode, newHbmMode);
             mHbmMode = newHbmMode;
             mHbmChangeCallback.run();
         }
     }
 
+    private void updateHbmStats(int mode, int newMode) {
+        int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
+        if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR) {
+            state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
+        } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+            state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
+        }
+
+        int reason =
+                FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
+        boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+        boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+        if (oldHbmSv && !newHbmSv) {
+            // If more than one conditions are flipped and turn off HBM sunlight
+            // visibility, only one condition will be reported to make it simple.
+            if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
+                reason = FrameworkStatsLog
+                                 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF;
+            } else if (!mIsAutoBrightnessEnabled) {
+                reason = FrameworkStatsLog
+                                 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF;
+            } else if (!mIsInAllowedAmbientRange) {
+                reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
+            } else if (!mIsTimeAvailable) {
+                reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
+            } else if (!mIsThermalStatusWithinLimit) {
+                reason = FrameworkStatsLog
+                                 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
+            } else if (mIsHdrLayerPresent) {
+                reason = FrameworkStatsLog
+                                 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING;
+            } else if (mIsBlockedByLowPowerMode) {
+                reason = FrameworkStatsLog
+                                 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
+            }
+        }
+
+        mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
+    }
+
     private int calculateHighBrightnessMode() {
         if (!deviceSupportsHbm()) {
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
@@ -642,5 +692,10 @@
             return IThermalService.Stub.asInterface(
                     ServiceManager.getService(Context.THERMAL_SERVICE));
         }
+
+        public void reportHbmStateChange(int display, int state, int reason) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
new file mode 100644
index 0000000..282e3c1
--- /dev/null
+++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
@@ -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.locales;
+
+import static android.os.Process.INVALID_UID;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Holds data used to report the ApplicationLocalesChanged atom.
+ */
+public final class AppLocaleChangedAtomRecord {
+    final int mCallingUid;
+    int mTargetUid = INVALID_UID;
+    String mNewLocales = "";
+    String mPrevLocales = "";
+    int mStatus = FrameworkStatsLog
+            .APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED;
+
+    AppLocaleChangedAtomRecord(int callingUid) {
+        this.mCallingUid = callingUid;
+    }
+
+    void setNewLocales(String newLocales) {
+        this.mNewLocales = newLocales;
+    }
+
+    void setTargetUid(int targetUid) {
+        this.mTargetUid = targetUid;
+    }
+
+    void setPrevLocales(String prevLocales) {
+        this.mPrevLocales = prevLocales;
+    }
+
+    void setStatus(int status) {
+        this.mStatus = status;
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 6aabdb5..1657b22 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -148,40 +149,52 @@
      */
     public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
             @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException {
-        requireNonNull(appPackageName);
-        requireNonNull(locales);
-
-        //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
-        userId = mActivityManagerInternal.handleIncomingUser(
-                Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
-                "setApplicationLocales", appPackageName);
-
-        // This function handles two types of set operations:
-        // 1.) A normal, non-privileged app setting its own locale.
-        // 2.) A privileged system service setting locales of another package.
-        // The least privileged case is a normal app performing a set, so check that first and
-        // set locales if the package name is owned by the app. Next, check if the caller has the
-        // necessary permission and set locales.
-        boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId);
-        if (!isCallerOwner) {
-            enforceChangeConfigurationPermission();
-        }
-
-        final long token = Binder.clearCallingIdentity();
+        AppLocaleChangedAtomRecord atomRecordForMetrics = new
+                AppLocaleChangedAtomRecord(Binder.getCallingUid());
         try {
-            setApplicationLocalesUnchecked(appPackageName, userId, locales);
+            requireNonNull(appPackageName);
+            requireNonNull(locales);
+            atomRecordForMetrics.setNewLocales(locales.toLanguageTags());
+            //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
+            userId = mActivityManagerInternal.handleIncomingUser(
+                    Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                    false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+                    "setApplicationLocales", appPackageName);
+
+            // This function handles two types of set operations:
+            // 1.) A normal, non-privileged app setting its own locale.
+            // 2.) A privileged system service setting locales of another package.
+            // The least privileged case is a normal app performing a set, so check that first and
+            // set locales if the package name is owned by the app. Next, check if the caller has
+            // the necessary permission and set locales.
+            boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId,
+                    atomRecordForMetrics);
+            if (!isCallerOwner) {
+                enforceChangeConfigurationPermission(atomRecordForMetrics);
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setApplicationLocalesUnchecked(appPackageName, userId, locales,
+                        atomRecordForMetrics);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         } finally {
-            Binder.restoreCallingIdentity(token);
+            logMetric(atomRecordForMetrics);
         }
     }
 
     private void setApplicationLocalesUnchecked(@NonNull String appPackageName,
-            @UserIdInt int userId, @NonNull LocaleList locales) {
+            @UserIdInt int userId, @NonNull LocaleList locales,
+            @NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) {
         if (DEBUG) {
             Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName
                     + " and user " + userId);
         }
+
+        atomRecordForMetrics.setPrevLocales(getApplicationLocalesUnchecked(appPackageName, userId)
+                .toLanguageTags());
         final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
                 mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
                         userId);
@@ -194,6 +207,11 @@
             notifyRegisteredReceivers(appPackageName, userId, locales);
 
             mBackupHelper.notifyBackupManager();
+            atomRecordForMetrics.setStatus(
+                    FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_COMMITTED);
+        } else {
+            atomRecordForMetrics.setStatus(FrameworkStatsLog
+                    .APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_UNCOMMITTED);
         }
     }
 
@@ -259,26 +277,49 @@
     }
 
     /**
+     * Same as {@link LocaleManagerService#isPackageOwnedByCaller(String, int,
+     * AppLocaleChangedAtomRecord)}, but for methods that do not log locale atom.
+     */
+    private boolean isPackageOwnedByCaller(String appPackageName, int userId) {
+        return isPackageOwnedByCaller(appPackageName, userId, /* atomRecordForMetrics= */null);
+    }
+
+    /**
      * Checks if the package is owned by the calling app or not for the given user id.
      *
      * @throws IllegalArgumentException if package not found for given userid
      */
-    private boolean isPackageOwnedByCaller(String appPackageName, int userId) {
+    private boolean isPackageOwnedByCaller(String appPackageName, int userId,
+            @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) {
         final int uid = mPackageManagerInternal
                 .getPackageUid(appPackageName, /* flags */ 0, userId);
         if (uid < 0) {
             Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId);
+            if (atomRecordForMetrics != null) {
+                atomRecordForMetrics.setStatus(FrameworkStatsLog
+                        .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE);
+            }
             throw new IllegalArgumentException("Unknown package: " + appPackageName
                     + " for user " + userId);
         }
+        if (atomRecordForMetrics != null) {
+            atomRecordForMetrics.setTargetUid(uid);
+        }
         //Once valid package found, ignore the userId part for validating package ownership
         //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user.
         return UserHandle.isSameApp(Binder.getCallingUid(), uid);
     }
 
-    private void enforceChangeConfigurationPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales");
+    private void enforceChangeConfigurationPermission(@NonNull AppLocaleChangedAtomRecord
+            atomRecordForMetrics) {
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales");
+        } catch (SecurityException e) {
+            atomRecordForMetrics.setStatus(FrameworkStatsLog
+                    .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_PERMISSION_ABSENT);
+            throw e;
+        }
     }
 
     /**
@@ -312,6 +353,7 @@
         }
     }
 
+    @NonNull
     private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName,
             @UserIdInt int userId) {
         if (DEBUG) {
@@ -345,4 +387,13 @@
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
         // TODO(b/201766221): Implement when there is state.
     }
+
+    private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) {
+        FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED,
+                atomRecordForMetrics.mCallingUid,
+                atomRecordForMetrics.mTargetUid,
+                atomRecordForMetrics.mNewLocales,
+                atomRecordForMetrics.mPrevLocales,
+                atomRecordForMetrics.mStatus);
+    }
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 03a63b9..8ef42ff 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -16,11 +16,8 @@
 
 package com.android.server.net;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.Network;
-import android.net.NetworkTemplate;
-import android.net.netstats.provider.NetworkStatsProvider;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.telephony.SubscriptionPlan;
 
@@ -56,11 +53,6 @@
      */
     public abstract SubscriptionPlan getSubscriptionPlan(Network network);
 
-    /**
-     * Return the active {@link SubscriptionPlan} for the given template.
-     */
-    public abstract SubscriptionPlan getSubscriptionPlan(NetworkTemplate template);
-
     public static final int QUOTA_TYPE_JOBS = 1;
     public static final int QUOTA_TYPE_MULTIPATH = 2;
 
@@ -99,13 +91,4 @@
      */
     public abstract void setMeteredRestrictedPackagesAsync(
             Set<String> packageNames, int userId);
-
-    /**
-     *  Notifies that the specified {@link NetworkStatsProvider} has reached its quota
-     *  which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
-     *  {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
-     *
-     * @param tag the human readable identifier of the custom network stats provider.
-     */
-    public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 5660951..9bc090f 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -151,6 +151,8 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -181,7 +183,6 @@
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
 import android.net.NetworkStateSnapshot;
-import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.TrafficStats;
@@ -441,7 +442,7 @@
 
     private final Context mContext;
     private final IActivityManager mActivityManager;
-    private NetworkStatsManagerInternal mNetworkStats;
+    private NetworkStatsManager mNetworkStats;
     private final INetworkManagementService mNetworkManager;
     private UsageStatsManagerInternal mUsageStats;
     private AppStandbyInternal mAppStandby;
@@ -453,6 +454,8 @@
     private ConnectivityManager mConnManager;
     private PowerManagerInternal mPowerManagerInternal;
     private PowerWhitelistManager mPowerWhitelistManager;
+    @NonNull
+    private final Dependencies mDeps;
 
     /** Current cached value of the current Battery Saver mode's setting for restrict background. */
     @GuardedBy("mUidRulesFirstLock")
@@ -704,7 +707,7 @@
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
             INetworkManagementService networkManagement) {
         this(context, activityManager, networkManagement, AppGlobals.getPackageManager(),
-                getDefaultClock(), getDefaultSystemDir(), false);
+                getDefaultClock(), getDefaultSystemDir(), false, new Dependencies(context));
     }
 
     private static @NonNull File getDefaultSystemDir() {
@@ -716,9 +719,59 @@
                 Clock.systemUTC());
     }
 
+    static class Dependencies {
+        final Context mContext;
+        final NetworkStatsManager mNetworkStatsManager;
+        Dependencies(Context context) {
+            mContext = context;
+            mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+            // Query stats from NetworkStatsService will trigger a poll by default.
+            // But since NPMS listens stats updated event, and will query stats
+            // after the event. A polling -> updated -> query -> polling loop will be introduced
+            // if polls on open. Hence, while NPMS manages it's poll requests explicitly, set
+            // flag to false to prevent a polling loop.
+            mNetworkStatsManager.setPollOnOpen(false);
+        }
+
+        long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+            Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes");
+            try {
+                final NetworkStats.Bucket ret = mNetworkStatsManager
+                        .querySummaryForDevice(template, start, end);
+                return ret.getRxBytes() + ret.getTxBytes();
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failed to read network stats: " + e);
+                return 0;
+            } finally {
+                Trace.traceEnd(TRACE_TAG_NETWORK);
+            }
+        }
+
+        @NonNull
+        List<NetworkStats.Bucket> getNetworkUidBytes(
+                @NonNull NetworkTemplate template, long start, long end) {
+            Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes");
+            final List<NetworkStats.Bucket> buckets = new ArrayList<>();
+            try {
+                final NetworkStats stats = mNetworkStatsManager.querySummary(template, start, end);
+                while (stats.hasNextBucket()) {
+                    final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                    stats.getNextBucket(bucket);
+                    buckets.add(bucket);
+                }
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failed to read network stats: " + e);
+            } finally {
+                Trace.traceEnd(TRACE_TAG_NETWORK);
+            }
+            return buckets;
+        }
+    }
+
+    @VisibleForTesting
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
             INetworkManagementService networkManagement, IPackageManager pm, Clock clock,
-            File systemDir, boolean suppressDefaultPolicy) {
+            File systemDir, boolean suppressDefaultPolicy, Dependencies deps) {
         mContext = Objects.requireNonNull(context, "missing context");
         mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
         mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
@@ -739,10 +792,12 @@
         mUidEventHandler = new Handler(mUidEventThread.getLooper(), mUidEventHandlerCallback);
 
         mSuppressDefaultPolicy = suppressDefaultPolicy;
+        mDeps = Objects.requireNonNull(deps, "missing Dependencies");
 
         mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"), "net-policy");
 
         mAppOps = context.getSystemService(AppOpsManager.class);
+        mNetworkStats = context.getSystemService(NetworkStatsManager.class);
         mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler);
         // Expose private service for system components to use.
         LocalServices.addService(NetworkPolicyManagerInternal.class,
@@ -842,7 +897,6 @@
 
             mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
             mAppStandby = LocalServices.getService(AppStandbyInternal.class);
-            mNetworkStats = LocalServices.getService(NetworkStatsManagerInternal.class);
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
@@ -1161,21 +1215,34 @@
     };
 
     /**
-     * Receiver that watches for {@link INetworkStatsService} updates, which we
+     * Receiver that watches for {@link NetworkStatsManager} updates, which we
      * use to check against {@link NetworkPolicy#warningBytes}.
      */
-    final private BroadcastReceiver mStatsReceiver = new BroadcastReceiver() {
+    private final NetworkStatsBroadcastReceiver mStatsReceiver =
+            new NetworkStatsBroadcastReceiver();
+    private class NetworkStatsBroadcastReceiver extends BroadcastReceiver {
+        private boolean mIsAnyIntentReceived = false;
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified
             // READ_NETWORK_USAGE_HISTORY permission above.
 
+            mIsAnyIntentReceived = true;
+
             synchronized (mNetworkPoliciesSecondLock) {
                 updateNetworkRulesNL();
                 updateNetworkEnabledNL();
                 updateNotificationsNL();
             }
         }
+
+        /**
+         * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received.
+         * Used to determine if NetworkStatsService is ready.
+         */
+        public boolean isAnyIntentReceived() {
+            return mIsAnyIntentReceived;
+        }
     };
 
     /**
@@ -1385,15 +1452,17 @@
         long maxBytes = 0;
         int maxUid = 0;
 
-        final NetworkStats stats = getNetworkUidBytes(template, start, end);
-        NetworkStats.Entry entry = null;
-        for (int i = 0; i < stats.size(); i++) {
-            entry = stats.getValues(i, entry);
-            final long bytes = entry.rxBytes + entry.txBytes;
+        // Skip if not ready. NetworkStatsService will block public API calls until it is
+        // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
+        if (!mStatsReceiver.isAnyIntentReceived()) return null;
+
+        final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end);
+        for (final NetworkStats.Bucket entry : stats) {
+            final long bytes = entry.getRxBytes() + entry.getTxBytes();
             totalBytes += bytes;
             if (bytes > maxBytes) {
                 maxBytes = bytes;
-                maxUid = entry.uid;
+                maxUid = entry.getUid();
             }
         }
 
@@ -3363,6 +3432,35 @@
         return result;
     }
 
+    /**
+     * Get subscription plan for the given networkTemplate.
+     *
+     * @param template the networkTemplate to get the subscription plan for.
+     */
+    @Override
+    public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) {
+        enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+        synchronized (mNetworkPoliciesSecondLock) {
+            final int subId = findRelevantSubIdNL(template);
+            return getPrimarySubscriptionPlanLocked(subId);
+        }
+    }
+
+    /**
+     * Notifies that the specified {@link NetworkStatsProvider} has reached its quota
+     * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+     * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
+     */
+    @Override
+    public void onStatsProviderWarningOrLimitReached() {
+        enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+        // This API may be called before the system is ready.
+        synchronized (mNetworkPoliciesSecondLock) {
+            if (!mSystemReady) return;
+        }
+        mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
+    }
+
     @Override
     public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) {
         enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
@@ -5349,25 +5447,10 @@
 
     @Deprecated
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
-        return getNetworkTotalBytes(template, start, end);
-    }
-
-    private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
-        try {
-            return mNetworkStats.getNetworkTotalBytes(template, start, end);
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Failed to read network stats: " + e);
-            return 0;
-        }
-    }
-
-    private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
-        try {
-            return mNetworkStats.getNetworkUidBytes(template, start, end);
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Failed to read network stats: " + e);
-            return new NetworkStats(SystemClock.elapsedRealtime(), 0);
-        }
+        // Skip if not ready. NetworkStatsService will block public API calls until it is
+        // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
+        if (!mStatsReceiver.isAnyIntentReceived()) return 0;
+        return mDeps.getNetworkTotalBytes(template, start, end);
     }
 
     private boolean isBandwidthControlEnabled() {
@@ -5583,14 +5666,6 @@
         }
 
         @Override
-        public SubscriptionPlan getSubscriptionPlan(NetworkTemplate template) {
-            synchronized (mNetworkPoliciesSecondLock) {
-                final int subId = findRelevantSubIdNL(template);
-                return getPrimarySubscriptionPlanLocked(subId);
-            }
-        }
-
-        @Override
         public long getSubscriptionOpportunisticQuota(Network network, int quotaType) {
             final long quotaBytes;
             synchronized (mNetworkPoliciesSecondLock) {
@@ -5632,12 +5707,6 @@
             mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
                     userId, 0, packageNames).sendToTarget();
         }
-
-        @Override
-        public void onStatsProviderWarningOrLimitReached(@NonNull String tag) {
-            Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag);
-            mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
-        }
     }
 
     private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a5edfed..86b385b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2657,16 +2657,20 @@
     }
 
     private void sendAppBlockStateChangedBroadcast(String pkg, int uid, boolean blocked) {
-        try {
-            getContext().sendBroadcastAsUser(
-                    new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
-                            .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked)
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                            .setPackage(pkg),
-                    UserHandle.of(UserHandle.getUserId(uid)), null);
-        } catch (SecurityException e) {
-            Slog.w(TAG, "Can't notify app about app block change", e);
-        }
+        // From Android T, revoking the notification permission will cause the app to be killed.
+        // delay this broadcast so it doesn't race with that process death
+        mHandler.postDelayed(() -> {
+            try {
+                getContext().sendBroadcastAsUser(
+                        new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+                                .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked)
+                                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                                .setPackage(pkg),
+                        UserHandle.of(UserHandle.getUserId(uid)), null);
+            } catch (SecurityException e) {
+                Slog.w(TAG, "Can't notify app about app block change", e);
+            }
+        }, 500);
     }
 
     @Override
@@ -3094,6 +3098,13 @@
                 if (mPreferencesHelper.setValidMessageSent(
                         r.getSbn().getPackageName(), r.getUid())) {
                     handleSavePolicyFile();
+                } else if (r.getNotification().getBubbleMetadata() != null) {
+                    // If bubble metadata is present it is valid (if invalid it's removed
+                    // via BubbleExtractor).
+                    if (mPreferencesHelper.setValidBubbleSent(
+                            r.getSbn().getPackageName(), r.getUid())) {
+                        handleSavePolicyFile();
+                    }
                 }
             } else {
                 if (mPreferencesHelper.setInvalidMessageSent(
@@ -3597,6 +3608,12 @@
         }
 
         @Override
+        public boolean hasSentValidBubble(String pkg, int uid) {
+            checkCallerIsSystem();
+            return mPreferencesHelper.hasSentValidBubble(pkg, uid);
+        }
+
+        @Override
         public void setNotificationDelegate(String callingPkg, String delegate) {
             checkCallerIsSameApp(callingPkg);
             final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 258ae8c..5e333da 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -808,6 +808,23 @@
         }
     }
 
+    /** Sets whether this package has sent a notification with valid bubble metadata. */
+    public boolean setValidBubbleSent(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            boolean valueChanged = !r.hasSentValidBubble;
+            r.hasSentValidBubble = true;
+            return valueChanged;
+        }
+    }
+
+    boolean hasSentValidBubble(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            return r.hasSentValidBubble;
+        }
+    }
+
     @Override
     public boolean isGroupBlocked(String packageName, int uid, String groupId) {
         if (groupId == null) {
@@ -848,6 +865,9 @@
             if (r == null) {
                 throw new IllegalArgumentException("Invalid package");
             }
+            if (fromTargetApp) {
+                group.setBlocked(false);
+            }
             final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
             if (oldGroup != null) {
                 group.setChannels(oldGroup.getChannels());
@@ -2813,8 +2833,9 @@
 
         boolean hasSentInvalidMessage = false;
         boolean hasSentValidMessage = false;
-        // notE: only valid while hasSentMessage is false and hasSentInvalidMessage is true
+        // note: only valid while hasSentMessage is false and hasSentInvalidMessage is true
         boolean userDemotedMsgApp = false;
+        boolean hasSentValidBubble = false;
 
         Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
similarity index 61%
rename from services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
rename to services/core/java/com/android/server/pm/InitAppsHelper.java
index dfa6c66..a5e6d6f 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -31,6 +31,7 @@
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Environment;
@@ -59,14 +60,25 @@
  * further cleanup and eventually all the installation/scanning related logic will go to another
  * class.
  */
-final class InitAndSystemPackageHelper {
+final class InitAppsHelper {
     private final PackageManagerService mPm;
-
     private final List<ScanPartition> mDirsToScanAsSystem;
     private final int mScanFlags;
     private final int mSystemParseFlags;
     private final int mSystemScanFlags;
     private final InstallPackageHelper mInstallPackageHelper;
+    private final ApexManager mApexManager;
+    private final PackageParser2 mPackageParser;
+    private final ExecutorService mExecutorService;
+    /* Tracks how long system scan took */
+    private long mSystemScanTime;
+    /* Track of the number of cached system apps */
+    private int mCachedSystemApps;
+    /* Track of the number of system apps */
+    private int mSystemPackagesCount;
+    private final boolean mIsDeviceUpgrading;
+    private final boolean mIsOnlyCoreApps;
+    private final List<ScanPartition> mSystemPartitions;
 
     /**
      * Tracks new system packages [received in an OTA] that we expect to
@@ -74,26 +86,39 @@
      * are package location.
      */
     private final ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
+    /* Tracks of any system packages that no longer exist that needs to be pruned. */
+    private final List<String> mPossiblyDeletedUpdatedSystemApps = new ArrayList<>();
+    // Tracks of stub packages that must either be replaced with full versions in the /data
+    // partition or be disabled.
+    private final List<String> mStubSystemApps = new ArrayList<>();
 
     // TODO(b/198166813): remove PMS dependency
-    InitAndSystemPackageHelper(PackageManagerService pm) {
+    InitAppsHelper(PackageManagerService pm, ApexManager apexManager,
+            InstallPackageHelper installPackageHelper, PackageParser2 packageParser,
+            List<ScanPartition> systemPartitions) {
         mPm = pm;
-        mInstallPackageHelper = new InstallPackageHelper(pm);
+        mApexManager = apexManager;
+        mInstallPackageHelper = installPackageHelper;
+        mPackageParser = packageParser;
+        mSystemPartitions = systemPartitions;
         mDirsToScanAsSystem = getSystemScanPartitions();
+        mIsDeviceUpgrading = mPm.isDeviceUpgrading();
+        mIsOnlyCoreApps = mPm.isOnlyCoreApps();
         // Set flag to monitor and not change apk file paths when scanning install directories.
         int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
-        if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()) {
+        if (mIsDeviceUpgrading || mPm.isFirstBoot()) {
             mScanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
         } else {
             mScanFlags = scanFlags;
         }
         mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+        mExecutorService = ParallelPackageParser.makeExecutorService();
     }
 
     private List<ScanPartition> getSystemScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
-        scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
+        scanPartitions.addAll(mSystemPartitions);
         scanPartitions.addAll(getApexScanPartitions());
         Slog.d(TAG, "Directories scanned as system partitions: " + scanPartitions);
         return scanPartitions;
@@ -101,8 +126,7 @@
 
     private List<ScanPartition> getApexScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
-        final List<ApexManager.ActiveApexInfo> activeApexInfos =
-                mPm.mApexManager.getActiveApexInfos();
+        final List<ApexManager.ActiveApexInfo> activeApexInfos = mApexManager.getActiveApexInfos();
         for (int i = 0; i < activeApexInfos.size(); i++) {
             final ScanPartition scanPartition = resolveApexToScanPartition(activeApexInfos.get(i));
             if (scanPartition != null) {
@@ -119,116 +143,133 @@
             if (apexInfo.preInstalledApexPath.getAbsolutePath().equals(
                     sp.getFolder().getAbsolutePath())
                     || apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
-                        sp.getFolder().getAbsolutePath() + File.separator)) {
+                    sp.getFolder().getAbsolutePath() + File.separator)) {
                 return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
             }
         }
         return null;
     }
 
-    public OverlayConfig initPackages(
-            WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
-            long startTime) {
-        PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
-
-        ExecutorService executorService = ParallelPackageParser.makeExecutorService();
+    /**
+     * Install apps from system dirs.
+     */
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    public OverlayConfig initSystemApps(WatchedArrayMap<String, PackageSetting> packageSettings,
+            int[] userIds, long startTime) {
         // Prepare apex package info before scanning APKs, this information is needed when
         // scanning apk in apex.
-        mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
+        mApexManager.scanApexPackagesTraced(mPackageParser, mExecutorService);
 
-        scanSystemDirs(packageParser, executorService);
+        scanSystemDirs(mPackageParser, mExecutorService);
         // Parse overlay configuration files to set default enable state, mutability, and
         // priority of system overlays.
         final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
-        for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) {
-            for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) {
+        for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
+            for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
                 apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
             }
         }
-        OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
+        final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
                 consumer -> mPm.forEachPackage(
                         pkg -> consumer.accept(pkg, pkg.isSystem(),
-                          apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
-        // Prune any system packages that no longer exist.
-        final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
-        // Stub packages must either be replaced with full versions in the /data
-        // partition or be disabled.
-        final List<String> stubSystemApps = new ArrayList<>();
+                                apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
 
-        if (!mPm.isOnlyCoreApps()) {
+        if (!mIsOnlyCoreApps) {
             // do this first before mucking with mPackages for the "expecting better" case
-            updateStubSystemAppsList(stubSystemApps);
+            updateStubSystemAppsList(mStubSystemApps);
             mInstallPackageHelper.prepareSystemPackageCleanUp(packageSettings,
-                    possiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);
+                    mPossiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);
         }
 
-        final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
+        logSystemAppsScanningTime(startTime);
+        return overlayConfig;
+    }
+
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private void logSystemAppsScanningTime(long startTime) {
+        mCachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
 
         // Remove any shared userIDs that have no associated packages
         mPm.mSettings.pruneSharedUsersLPw();
-        final long systemScanTime = SystemClock.uptimeMillis() - startTime;
-        final int systemPackagesCount = mPm.mPackages.size();
-        Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime
-                + " ms, packageCount: " + systemPackagesCount
+        mSystemScanTime = SystemClock.uptimeMillis() - startTime;
+        mSystemPackagesCount = mPm.mPackages.size();
+        Slog.i(TAG, "Finished scanning system apps. Time: " + mSystemScanTime
+                + " ms, packageCount: " + mSystemPackagesCount
                 + " , timePerPackage: "
-                + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
-                + " , cached: " + cachedSystemApps);
-        if (mPm.isDeviceUpgrading() && systemPackagesCount > 0) {
+                + (mSystemPackagesCount == 0 ? 0 : mSystemScanTime / mSystemPackagesCount)
+                + " , cached: " + mCachedSystemApps);
+        if (mIsDeviceUpgrading && mSystemPackagesCount > 0) {
             //CHECKSTYLE:OFF IndentationCheck
             FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
                     BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME,
-                    systemScanTime / systemPackagesCount);
+                    mSystemScanTime / mSystemPackagesCount);
             //CHECKSTYLE:ON IndentationCheck
         }
+    }
 
-        if (!mPm.isOnlyCoreApps()) {
+    /**
+     * Install apps/updates from data dir and fix system apps that are affected.
+     */
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    public void initNonSystemApps(@NonNull int[] userIds, long startTime) {
+        if (!mIsOnlyCoreApps) {
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                     SystemClock.uptimeMillis());
-            scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
-                    packageParser, executorService);
+            scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN,
+                    mPackageParser, mExecutorService);
 
         }
 
-        List<Runnable> unfinishedTasks = executorService.shutdownNow();
+        List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
         if (!unfinishedTasks.isEmpty()) {
             throw new IllegalStateException("Not all tasks finished before calling close: "
                     + unfinishedTasks);
         }
-
-        if (!mPm.isOnlyCoreApps()) {
-            mInstallPackageHelper.cleanupDisabledPackageSettings(possiblyDeletedUpdatedSystemApps,
-                    userIds, mScanFlags);
-            mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter,
-                    stubSystemApps, mSystemScanFlags, mSystemParseFlags);
-
-            // Uncompress and install any stubbed system applications.
-            // This must be done last to ensure all stubs are replaced or disabled.
-            mInstallPackageHelper.installSystemStubPackages(stubSystemApps, mScanFlags);
-
-            final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
-                    - cachedSystemApps;
-
-            final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
-            final int dataPackagesCount = mPm.mPackages.size() - systemPackagesCount;
-            Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
-                    + " ms, packageCount: " + dataPackagesCount
-                    + " , timePerPackage: "
-                    + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
-                    + " , cached: " + cachedNonSystemApps);
-            if (mPm.isDeviceUpgrading() && dataPackagesCount > 0) {
-                //CHECKSTYLE:OFF IndentationCheck
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
-                        BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
-                        dataScanTime / dataPackagesCount);
-                //CHECKSTYLE:OFF IndentationCheck
-            }
+        if (!mIsOnlyCoreApps) {
+            fixSystemPackages(userIds);
+            logNonSystemAppScanningTime(startTime);
         }
         mExpectingBetter.clear();
-
         mPm.mSettings.pruneRenamedPackagesLPw();
-        packageParser.close();
-        return overlayConfig;
+        mPackageParser.close();
+    }
+
+    /**
+     * Clean up system packages now that some system package updates have been installed from
+     * the data dir. Also install system stub packages as the last step.
+     */
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private void fixSystemPackages(@NonNull int[] userIds) {
+        mInstallPackageHelper.cleanupDisabledPackageSettings(mPossiblyDeletedUpdatedSystemApps,
+                userIds, mScanFlags);
+        mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter,
+                mStubSystemApps, mSystemScanFlags, mSystemParseFlags);
+
+        // Uncompress and install any stubbed system applications.
+        // This must be done last to ensure all stubs are replaced or disabled.
+        mInstallPackageHelper.installSystemStubPackages(mStubSystemApps, mScanFlags);
+    }
+
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private void logNonSystemAppScanningTime(long startTime) {
+        final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
+                - mCachedSystemApps;
+
+        final long dataScanTime = SystemClock.uptimeMillis() - mSystemScanTime - startTime;
+        final int dataPackagesCount = mPm.mPackages.size() - mSystemPackagesCount;
+        Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
+                + " ms, packageCount: " + dataPackagesCount
+                + " , timePerPackage: "
+                + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
+                + " , cached: " + cachedNonSystemApps);
+        if (mIsDeviceUpgrading && dataPackagesCount > 0) {
+            //CHECKSTYLE:OFF IndentationCheck
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
+                    BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
+                    dataScanTime / dataPackagesCount);
+            //CHECKSTYLE:OFF IndentationCheck
+        }
     }
 
     /**
@@ -248,12 +289,12 @@
                 continue;
             }
             scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
-                    mSystemScanFlags | partition.scanFlag, 0,
+                    mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
 
         scanDirTracedLI(frameworkDir, mSystemParseFlags,
-                mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
+                mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
                 packageParser, executorService);
         if (!mPm.mPackages.containsKey("android")) {
             throw new IllegalStateException(
@@ -264,11 +305,11 @@
             final ScanPartition partition = mDirsToScanAsSystem.get(i);
             if (partition.getPrivAppFolder() != null) {
                 scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
-                        mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
+                        mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
                         packageParser, executorService);
             }
             scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
-                    mSystemScanFlags | partition.scanFlag, 0,
+                    mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
     }
@@ -286,11 +327,11 @@
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
-            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+            PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
             mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
-                    currentTime, packageParser, executorService);
+                    packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 14c0761..d002c26 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -91,7 +91,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
 import android.app.backup.IBackupManager;
 import android.content.ContentResolver;
@@ -195,38 +194,32 @@
     private final AppDataHelper mAppDataHelper;
     private final BroadcastHelper mBroadcastHelper;
     private final RemovePackageHelper mRemovePackageHelper;
-    private final StorageManager mStorageManager;
-    private final RollbackManagerInternal mRollbackManager;
     private final IncrementalManager mIncrementalManager;
     private final ApexManager mApexManager;
     private final DexManager mDexManager;
     private final ArtManagerService mArtManagerService;
-    private final AppOpsManager mAppOpsManager;
     private final Context mContext;
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final PackageAbiHelper mPackageAbiHelper;
     private final ViewCompiler mViewCompiler;
-    private final IBackupManager mIBackupManager;
     private final SharedLibrariesImpl mSharedLibraries;
+    private final PackageManagerServiceInjector mInjector;
 
     // TODO(b/198166813): remove PMS dependency
     InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
         mPm = pm;
+        mInjector = pm.mInjector;
         mAppDataHelper = appDataHelper;
         mBroadcastHelper = new BroadcastHelper(pm.mInjector);
         mRemovePackageHelper = new RemovePackageHelper(pm);
-        mStorageManager = pm.mInjector.getSystemService(StorageManager.class);
-        mRollbackManager = pm.mInjector.getLocalService(RollbackManagerInternal.class);
         mIncrementalManager = pm.mInjector.getIncrementalManager();
         mApexManager = pm.mInjector.getApexManager();
         mDexManager = pm.mInjector.getDexManager();
         mArtManagerService = pm.mInjector.getArtManagerService();
-        mAppOpsManager = pm.mInjector.getSystemService(AppOpsManager.class);
         mContext = pm.mInjector.getContext();
         mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
         mPackageAbiHelper = pm.mInjector.getAbiHelper();
         mViewCompiler = pm.mInjector.getViewCompiler();
-        mIBackupManager = pm.mInjector.getIBackupManager();
         mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
     }
 
@@ -693,7 +686,8 @@
      * Returns whether the restore successfully completed.
      */
     private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
-        if (mIBackupManager != null) {
+        IBackupManager iBackupManager = mInjector.getIBackupManager();
+        if (iBackupManager != null) {
             // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
             // in the BackupManager. USER_ALL is used in compatibility tests.
             if (userId == UserHandle.USER_ALL) {
@@ -704,8 +698,8 @@
             }
             Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
             try {
-                if (mIBackupManager.isUserReadyForBackup(userId)) {
-                    mIBackupManager.restoreAtInstallForUser(
+                if (iBackupManager.isUserReadyForBackup(userId)) {
+                    iBackupManager.restoreAtInstallForUser(
                             userId, res.mPkg.getPackageName(), token);
                 } else {
                     Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
@@ -756,7 +750,9 @@
 
         if (ps != null && doSnapshotOrRestore) {
             final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
-            mRollbackManager.snapshotAndRestoreUserData(packageName,
+            final RollbackManagerInternal rollbackManager =
+                    mInjector.getLocalService(RollbackManagerInternal.class);
+            rollbackManager.snapshotAndRestoreUserData(packageName,
                     UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, token);
             return true;
         }
@@ -2788,8 +2784,10 @@
                 // Send broadcast package appeared if external for all users
                 if (res.mPkg.isExternalStorage()) {
                     if (!update) {
+                        final StorageManager storageManager =
+                                mInjector.getSystemService(StorageManager.class);
                         VolumeInfo volume =
-                                mStorageManager.findVolumeByUuid(
+                                storageManager.findVolumeByUuid(
                                         StorageManager.convert(
                                                 res.mPkg.getVolumeUuid()).toString());
                         int packageExternalStorageType =
@@ -3056,7 +3054,7 @@
         final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
         removePackageHelper.removePackageLI(stubPkg, true /*chatty*/);
         try {
-            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null);
+            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
                     e);
@@ -3189,7 +3187,7 @@
                         | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
         final AndroidPackage pkg = scanSystemPackageTracedLI(
-                        codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
+                codePath, parseFlags, scanFlags, null);
 
         PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
 
@@ -3364,7 +3362,7 @@
                 mRemovePackageHelper.removePackageLI(pkg, true);
                 try {
                     final File codePath = new File(pkg.getPath());
-                    scanSystemPackageTracedLI(codePath, 0, scanFlags, 0, null);
+                    scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Failed to parse updated, ex-system package: "
                             + e.getMessage());
@@ -3385,7 +3383,7 @@
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
-            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+            PackageParser2 packageParser, ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             Log.d(TAG, "No files in app dir " + scanDir);
@@ -3427,7 +3425,7 @@
                             parseResult.parsedPackage);
                 }
                 try {
-                    addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime,
+                    addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                             null);
                 } catch (PackageManagerException e) {
                     errorCode = e.error;
@@ -3490,7 +3488,7 @@
 
             try {
                 final AndroidPackage newPkg = scanSystemPackageTracedLI(
-                        scanFile, reparseFlags, rescanFlags, 0, null);
+                        scanFile, reparseFlags, rescanFlags, null);
                 // We rescanned a stub, add it to the list of stubbed system packages
                 if (newPkg.isStub()) {
                     stubSystemApps.add(packageName);
@@ -3504,14 +3502,14 @@
 
     /**
      *  Traces a package scan.
-     *  @see #scanSystemPackageLI(File, int, int, long, UserHandle)
+     *  @see #scanSystemPackageLI(File, int, int, UserHandle)
      */
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
-            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
+            int scanFlags, UserHandle user) throws PackageManagerException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
         try {
-            return scanSystemPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
+            return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -3523,7 +3521,7 @@
      */
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
-            long currentTime, UserHandle user) throws PackageManagerException {
+            UserHandle user) throws PackageManagerException {
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
@@ -3539,7 +3537,7 @@
             PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
         }
 
-        return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user);
+        return addForInitLI(parsedPackage, parseFlags, scanFlags, user);
     }
 
     /**
@@ -3558,11 +3556,11 @@
     @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
     private AndroidPackage addForInitLI(ParsedPackage parsedPackage,
             @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @PackageManagerService.ScanFlags int scanFlags,
             @Nullable UserHandle user) throws PackageManagerException {
 
         final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
-                parsedPackage, parseFlags, scanFlags, currentTime, user);
+                parsedPackage, parseFlags, scanFlags, user);
         final ScanResult scanResult = scanResultPair.first;
         boolean shouldHideSystemApp = scanResultPair.second;
         if (scanResult.mSuccess) {
@@ -3740,7 +3738,7 @@
 
     private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
             @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @PackageManagerService.ScanFlags int scanFlags,
             @Nullable UserHandle user) throws PackageManagerException {
         final boolean scanSystemPartition =
                 (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
@@ -3927,7 +3925,7 @@
         }
 
         final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
+                scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 248944e..548eb58 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -241,7 +241,6 @@
 import com.android.server.pm.pkg.SuspendParams;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
 import com.android.server.pm.pkg.mutate.PackageStateWrite;
-import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -965,7 +964,7 @@
     private final BroadcastHelper mBroadcastHelper;
     private final RemovePackageHelper mRemovePackageHelper;
     private final DeletePackageHelper mDeletePackageHelper;
-    private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
+    private final InitAppsHelper mInitAppsHelper;
     private final AppDataHelper mAppDataHelper;
     private final InstallPackageHelper mInstallPackageHelper;
     private final PreferredActivityHelper mPreferredActivityHelper;
@@ -1701,7 +1700,7 @@
         mAppDataHelper = testParams.appDataHelper;
         mInstallPackageHelper = testParams.installPackageHelper;
         mRemovePackageHelper = testParams.removePackageHelper;
-        mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper;
+        mInitAppsHelper = testParams.initAndSystemPackageHelper;
         mDeletePackageHelper = testParams.deletePackageHelper;
         mPreferredActivityHelper = testParams.preferredActivityHelper;
         mResolveIntentHelper = testParams.resolveIntentHelper;
@@ -1845,7 +1844,8 @@
         mAppDataHelper = new AppDataHelper(this);
         mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
         mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
-        mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this);
+        mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
+                mInjector.getScanningPackageParser(), mInjector.getSystemPartitions());
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
                 mAppDataHelper);
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
@@ -1977,8 +1977,8 @@
                     mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
 
             final int[] userIds = mUserManager.getUserIds();
-            mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
-                    userIds, startTime);
+            mOverlayConfig = mInitAppsHelper.initSystemApps(packageSettings, userIds, startTime);
+            mInitAppsHelper.initNonSystemApps(userIds, startTime);
 
             // Resolve the storage manager.
             mStorageManagerPackage = getStorageManagerPackageName();
@@ -9147,7 +9147,7 @@
     }
 
     boolean isExpectingBetter(String packageName) {
-        return mInitAndSystemPackageHelper.isExpectingBetter(packageName);
+        return mInitAppsHelper.isExpectingBetter(packageName);
     }
 
     int getDefParseFlags() {
@@ -9256,7 +9256,7 @@
 
     @ScanFlags int getSystemPackageScanFlags(File codePath) {
         List<ScanPartition> dirsToScanAsSystem =
-                mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+                mInitAppsHelper.getDirsToScanAsSystem();
         @PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM;
         for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) {
             ScanPartition partition = dirsToScanAsSystem.get(i);
@@ -9274,7 +9274,7 @@
     Pair<Integer, Integer> getSystemPackageRescanFlagsAndReparseFlags(File scanFile,
             int systemScanFlags, int systemParseFlags) {
         List<ScanPartition> dirsToScanAsSystem =
-                mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+                mInitAppsHelper.getDirsToScanAsSystem();
         @ParsingPackageUtils.ParseFlags int reparseFlags = 0;
         @PackageManagerService.ScanFlags int rescanFlags = 0;
         for (int i1 = dirsToScanAsSystem.size() - 1; i1 >= 0; i1--) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index a1acc38..168401a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -106,7 +106,7 @@
     public AppDataHelper appDataHelper;
     public InstallPackageHelper installPackageHelper;
     public RemovePackageHelper removePackageHelper;
-    public InitAndSystemPackageHelper initAndSystemPackageHelper;
+    public InitAppsHelper initAndSystemPackageHelper;
     public DeletePackageHelper deletePackageHelper;
     public PreferredActivityHelper preferredActivityHelper;
     public ResolveIntentHelper resolveIntentHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index be2bdaa..e27ad17 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2745,7 +2745,7 @@
         IUserManager um = IUserManager.Stub.asInterface(
                 ServiceManager.getService(Context.USER_SERVICE));
         if (setEphemeralIfInUse) {
-            return removeUserOrSetEphemeral(um, userId);
+            return removeUserWhenPossible(um, userId);
         } else {
             final boolean success = wait ? removeUserAndWait(um, userId) : removeUser(um, userId);
             if (success) {
@@ -2808,10 +2808,10 @@
         }
     }
 
-    private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId)
+    private int removeUserWhenPossible(IUserManager um, @UserIdInt int userId)
             throws RemoteException {
         Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use.");
-        int result = um.removeUserOrSetEphemeral(userId, /* evenWhenDisallowed= */ false);
+        int result = um.removeUserWhenPossible(userId, /* overrideDevicePolicy= */ false);
         switch (result) {
             case UserManager.REMOVE_RESULT_REMOVED:
                 getOutPrintWriter().printf("Success: user %d removed\n", userId);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 1433abd..de64405 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -150,7 +150,7 @@
                 final AndroidPackage pkg;
                 try {
                     pkg = installPackageHelper.scanSystemPackageTracedLI(
-                            ps.getPath(), parseFlags, SCAN_INITIAL, 0, null);
+                            ps.getPath(), parseFlags, SCAN_INITIAL, null);
                     loaded.add(pkg);
 
                 } catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fb3ca91..d29dbbc 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2487,20 +2487,25 @@
                 return false;
             }
 
-            // Limit the number of profiles that can be created
-            final int maxUsersOfType = getMaxUsersOfTypePerParent(type);
-            if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
-                final int userTypeCount = getProfileIds(userId, userType, false).length;
-                final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
-                if (userTypeCount - profilesRemovedCount >= maxUsersOfType) {
+            final int userTypeCount = getProfileIds(userId, userType, false).length;
+            final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
+            final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
+                    - profilesRemovedCount;
+
+            // Limit total number of users that can be created
+            if (usersCountAfterRemoving >= UserManager.getMaxSupportedUsers()) {
+                // Special case: Allow creating a managed profile anyway if there's only 1 user
+                // Otherwise, disallow.
+                if (!(isManagedProfile && usersCountAfterRemoving == 1)) {
                     return false;
                 }
-                // Allow creating a managed profile in the special case where there is only one user
-                if (isManagedProfile) {
-                    int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
-                            - profilesRemovedCount;
-                    return usersCountAfterRemoving == 1
-                            || usersCountAfterRemoving < UserManager.getMaxSupportedUsers();
+            }
+
+            // Limit the number of profiles of this type that can be created.
+            final int maxUsersOfType = getMaxUsersOfTypePerParent(type);
+            if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
+                if (userTypeCount - profilesRemovedCount >= maxUsersOfType) {
+                    return false;
                 }
             }
         }
@@ -3753,6 +3758,7 @@
         final boolean isGuest = UserManager.isUserTypeGuest(userType);
         final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
         final boolean isDemo = UserManager.isUserTypeDemo(userType);
+        final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
 
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
@@ -3776,6 +3782,14 @@
                                     + ". Maximum number of that type already exists.",
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
+                if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
+                    // If the user limit has been reached, we cannot add a user (except guest/demo).
+                    // Note that managed profiles can bypass it in certain circumstances (taken
+                    // into account in the profile check below).
+                    throwCheckedUserOperationException(
+                            "Cannot add user. Maximum user limit is reached.",
+                            UserManager.USER_OPERATION_ERROR_MAX_USERS);
+                }
                 // TODO(b/142482943): Perhaps let the following code apply to restricted users too.
                 if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
                     throwCheckedUserOperationException(
@@ -3783,13 +3797,6 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
-                if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
-                    // If we're not adding a guest/demo user or a profile and the 'user limit' has
-                    // been reached, cannot add a user.
-                    throwCheckedUserOperationException(
-                            "Cannot add user. Maximum user limit is reached.",
-                            UserManager.USER_OPERATION_ERROR_MAX_USERS);
-                }
                 // In legacy mode, restricted profile's parent can only be the owner user
                 if (isRestricted && !UserManager.isSplitSystemUser()
                         && (parentId != UserHandle.USER_SYSTEM)) {
@@ -4430,11 +4437,11 @@
     }
 
     @Override
-    public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
-            boolean evenWhenDisallowed) {
+    public @UserManager.RemoveResult int removeUserWhenPossible(@UserIdInt int userId,
+            boolean overrideDevicePolicy) {
         checkCreateUsersPermission("Only the system can remove users");
 
-        if (!evenWhenDisallowed) {
+        if (!overrideDevicePolicy) {
             final String restriction = getUserRemovalRestriction(userId);
             if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
                 Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 27a16e9..17a5fd0 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -94,6 +94,7 @@
     private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
     private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
     private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
+    private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
 
     /** Interface that allows reading the device state configuration. */
     interface ReadableConfig {
@@ -141,8 +142,8 @@
                         for (int i = 0; i < configFlagStrings.size(); i++) {
                             final String configFlagString = configFlagStrings.get(i);
                             switch (configFlagString) {
-                                case "FLAG_CANCEL_STICKY_REQUESTS":
-                                    flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS;
+                                case FLAG_CANCEL_OVERRIDE_REQUESTS:
+                                    flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
                                     break;
                                 default:
                                     Slog.w(TAG, "Parsed unknown flag with name: "
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 07a5849..16176f0 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3436,7 +3436,10 @@
                             metricsState.getLatestTelephonySuggestion()),
                     convertTimeZoneSuggestionToProtoBytes(
                             metricsState.getLatestGeolocationSuggestion()),
-                    metricsState.isTelephonyTimeZoneFallbackSupported()
+                    metricsState.isTelephonyTimeZoneFallbackSupported(),
+                    metricsState.getDeviceTimeZoneId(),
+                    metricsState.isEnhancedMetricsCollectionEnabled(),
+                    metricsState.getGeoDetectionRunInBackgroundEnabled()
             ));
         } catch (RuntimeException e) {
             Slog.e(TAG, "Getting time zone detection state failed: ", e);
@@ -3483,6 +3486,14 @@
                         android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS,
                         zoneIdOrdinal);
             }
+            String[] zoneIds = suggestion.getZoneIds();
+            if (zoneIds != null) {
+                for (String zoneId : zoneIds) {
+                    protoOutputStream.write(
+                            android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_IDS,
+                            zoneId);
+                }
+            }
         }
         protoOutputStream.flush();
         closeQuietly(byteArrayOutputStream);
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index b23f11a..36ab111d 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -281,15 +281,7 @@
                 LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider();
                 LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider();
                 LocationTimeZoneProviderController.MetricsLogger metricsLogger =
-                        new LocationTimeZoneProviderController.MetricsLogger() {
-                            @Override
-                            public void onStateChange(
-                                    @LocationTimeZoneProviderController.State String state) {
-                                // TODO b/200279201 - wire this up to metrics code
-                                // No-op.
-                            }
-                        };
-
+                        new RealControllerMetricsLogger();
                 boolean recordStateChanges = mServiceConfigAccessor.getRecordStateChangesForTests();
                 LocationTimeZoneProviderController controller =
                         new LocationTimeZoneProviderController(mThreadingDomain, metricsLogger,
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java
new file mode 100644
index 0000000..9cb36ef
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector.location;
+
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__CERTAIN;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__DESTROYED;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__FAILED;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__INITIALIZING;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__PROVIDERS_INITIALIZING;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNCERTAIN;
+import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNKNOWN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
+
+/**
+ * The real implementation of {@link LocationTimeZoneProviderController.MetricsLogger} which logs
+ * using {@link FrameworkStatsLog}.
+ */
+final class RealControllerMetricsLogger
+        implements LocationTimeZoneProviderController.MetricsLogger {
+
+    RealControllerMetricsLogger() {
+    }
+
+    @Override
+    public void onStateChange(@State String state) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED,
+                metricsState(state));
+    }
+
+    private static int metricsState(@State String state) {
+        switch (state) {
+            case STATE_PROVIDERS_INITIALIZING:
+                // Disable lint check (line length) for generated long constant name.
+                // CHECKSTYLE:OFF Generated code
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__PROVIDERS_INITIALIZING;
+                // CHECKSTYLE:ON Generated code
+            case STATE_STOPPED:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__STOPPED;
+            case STATE_INITIALIZING:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__INITIALIZING;
+            case STATE_CERTAIN:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__CERTAIN;
+            case STATE_UNCERTAIN:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNCERTAIN;
+            case STATE_DESTROYED:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__DESTROYED;
+            case STATE_FAILED:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__FAILED;
+            case STATE_UNKNOWN:
+            default:
+                return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNKNOWN;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java
index e19ec84..fe543ad 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java
@@ -41,12 +41,12 @@
  * The real implementation of {@link ProviderMetricsLogger} which logs using
  * {@link FrameworkStatsLog}.
  */
-public class RealProviderMetricsLogger implements ProviderMetricsLogger {
+final class RealProviderMetricsLogger implements ProviderMetricsLogger {
 
     @IntRange(from = 0, to = 1)
     private final int mProviderIndex;
 
-    public RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) {
+    RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) {
         mProviderIndex = providerIndex;
     }
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e066ca3..e786fa2 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1851,18 +1851,19 @@
         }
 
         @Override
-        public void setIAppNotificationEnabled(IBinder sessionToken, boolean enabled, int userId) {
+        public void setInteractiveAppNotificationEnabled(
+                IBinder sessionToken, boolean enabled, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
-                    userId, "setIAppNotificationEnabled");
+                    userId, "setInteractiveAppNotificationEnabled");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
-                                .setIAppNotificationEnabled(enabled);
+                                .setInteractiveAppNotificationEnabled(enabled);
                     } catch (RemoteException | SessionNotFoundException e) {
-                        Slog.e(TAG, "error in setIAppNotificationEnabled", e);
+                        Slog.e(TAG, "error in setInteractiveAppNotificationEnabled", e);
                     }
                 }
             } finally {
@@ -3538,6 +3539,23 @@
         }
 
         @Override
+        public void onSignalStrength(int strength) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onSignalStrength(" + strength + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onSignalStrength(strength, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onSignalStrength", e);
+                }
+            }
+        }
+
+        @Override
         public void onTuned(Uri channelUri) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index a4732c1..6058d88 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -34,15 +34,15 @@
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.ITvIAppClient;
 import android.media.tv.interactive.ITvIAppManager;
-import android.media.tv.interactive.ITvIAppManagerCallback;
-import android.media.tv.interactive.ITvIAppService;
-import android.media.tv.interactive.ITvIAppServiceCallback;
-import android.media.tv.interactive.ITvIAppSession;
-import android.media.tv.interactive.ITvIAppSessionCallback;
-import android.media.tv.interactive.TvIAppInfo;
+import android.media.tv.interactive.ITvInteractiveAppClient;
+import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
+import android.media.tv.interactive.ITvInteractiveAppService;
+import android.media.tv.interactive.ITvInteractiveAppServiceCallback;
+import android.media.tv.interactive.ITvInteractiveAppSession;
+import android.media.tv.interactive.ITvInteractiveAppSessionCallback;
 import android.media.tv.interactive.TvIAppService;
+import android.media.tv.interactive.TvInteractiveAppInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -53,6 +53,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InputChannel;
@@ -112,54 +113,55 @@
     }
 
     @GuardedBy("mLock")
-    private void buildTvIAppServiceListLocked(int userId, String[] updatedPackages) {
+    private void buildTvInteractiveAppServiceListLocked(int userId, String[] updatedPackages) {
         UserState userState = getOrCreateUserStateLocked(userId);
         userState.mPackageSet.clear();
 
         if (DEBUG) {
-            Slogf.d(TAG, "buildTvIAppServiceListLocked");
+            Slogf.d(TAG, "buildTvInteractiveAppServiceListLocked");
         }
         PackageManager pm = mContext.getPackageManager();
         List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                 new Intent(TvIAppService.SERVICE_INTERFACE),
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                 userId);
-        List<TvIAppInfo> iAppList = new ArrayList<>();
+        List<TvInteractiveAppInfo> iAppList = new ArrayList<>();
 
         for (ResolveInfo ri : services) {
             ServiceInfo si = ri.serviceInfo;
-            // TODO: add BIND_TV_IAPP permission and check it here
+            // TODO: add BIND_TV_INTERACTIVE_APP permission and check it here
 
             ComponentName component = new ComponentName(si.packageName, si.name);
             try {
-                TvIAppInfo info = new TvIAppInfo.Builder(mContext, component).build();
+                TvInteractiveAppInfo info =
+                        new TvInteractiveAppInfo(mContext, component);
                 iAppList.add(info);
             } catch (Exception e) {
-                Slogf.e(TAG, "failed to load TV IApp service " + si.name, e);
+                Slogf.e(TAG, "failed to load TV Interactive App service " + si.name, e);
                 continue;
             }
             userState.mPackageSet.add(si.packageName);
         }
 
         // sort the iApp list by iApp service id
-        Collections.sort(iAppList, Comparator.comparing(TvIAppInfo::getId));
-        Map<String, TvIAppState> iAppMap = new HashMap<>();
+        Collections.sort(iAppList, Comparator.comparing(TvInteractiveAppInfo::getId));
+        Map<String, TvInteractiveAppState> iAppMap = new HashMap<>();
         ArrayMap<String, Integer> tiasAppCount = new ArrayMap<>(iAppMap.size());
-        for (TvIAppInfo info : iAppList) {
+        for (TvInteractiveAppInfo info : iAppList) {
             String iAppServiceId = info.getId();
             if (DEBUG) {
                 Slogf.d(TAG, "add " + iAppServiceId);
             }
-            // Running count of IApp for each IApp service
+            // Running count of Interactive App for each Interactive App service
             Integer count = tiasAppCount.get(iAppServiceId);
             count = count == null ? 1 : count + 1;
             tiasAppCount.put(iAppServiceId, count);
-            TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId);
+            TvInteractiveAppState iAppState = userState.mIAppMap.get(iAppServiceId);
             if (iAppState == null) {
-                iAppState = new TvIAppState();
+                iAppState = new TvInteractiveAppState();
             }
             iAppState.mInfo = info;
-            iAppState.mUid = getIAppUid(info);
+            iAppState.mUid = getInteractiveAppUid(info);
             iAppState.mComponentName = info.getComponent();
             iAppMap.put(iAppServiceId, iAppState);
             iAppState.mIAppNumber = count;
@@ -167,14 +169,14 @@
 
         for (String iAppServiceId : iAppMap.keySet()) {
             if (!userState.mIAppMap.containsKey(iAppServiceId)) {
-                notifyIAppServiceAddedLocked(userState, iAppServiceId);
+                notifyInteractiveAppServiceAddedLocked(userState, iAppServiceId);
             } else if (updatedPackages != null) {
                 // Notify the package updates
                 ComponentName component = iAppMap.get(iAppServiceId).mInfo.getComponent();
                 for (String updatedPackage : updatedPackages) {
                     if (component.getPackageName().equals(updatedPackage)) {
                         updateServiceConnectionLocked(component, userId);
-                        notifyIAppServiceUpdatedLocked(userState, iAppServiceId);
+                        notifyInteractiveAppServiceUpdatedLocked(userState, iAppServiceId);
                         break;
                     }
                 }
@@ -183,12 +185,12 @@
 
         for (String iAppServiceId : userState.mIAppMap.keySet()) {
             if (!iAppMap.containsKey(iAppServiceId)) {
-                TvIAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo;
+                TvInteractiveAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo;
                 ServiceState serviceState = userState.mServiceStateMap.get(info.getComponent());
                 if (serviceState != null) {
                     abortPendingCreateSessionRequestsLocked(serviceState, iAppServiceId, userId);
                 }
-                notifyIAppServiceRemovedLocked(userState, iAppServiceId);
+                notifyInteractiveAppServiceRemovedLocked(userState, iAppServiceId);
             }
         }
 
@@ -197,48 +199,56 @@
     }
 
     @GuardedBy("mLock")
-    private void notifyIAppServiceAddedLocked(UserState userState, String iAppServiceId) {
+    private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyIAppServiceAddedLocked(iAppServiceId=" + iAppServiceId + ")");
+            Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
+                    + iAppServiceId + ")");
         }
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
             try {
-                userState.mCallbacks.getBroadcastItem(i).onIAppServiceAdded(iAppServiceId);
+                userState.mCallbacks.getBroadcastItem(i)
+                        .onInteractiveAppServiceAdded(iAppServiceId);
             } catch (RemoteException e) {
-                Slog.e(TAG, "failed to report added IApp service to callback", e);
+                Slog.e(TAG, "failed to report added Interactive App service to callback", e);
             }
         }
         userState.mCallbacks.finishBroadcast();
     }
 
     @GuardedBy("mLock")
-    private void notifyIAppServiceRemovedLocked(UserState userState, String iAppServiceId) {
+    private void notifyInteractiveAppServiceRemovedLocked(
+            UserState userState, String iAppServiceId) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyIAppServiceRemovedLocked(iAppServiceId=" + iAppServiceId + ")");
+            Slog.d(TAG, "notifyInteractiveAppServiceRemovedLocked(iAppServiceId="
+                    + iAppServiceId + ")");
         }
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
             try {
-                userState.mCallbacks.getBroadcastItem(i).onIAppServiceRemoved(iAppServiceId);
+                userState.mCallbacks.getBroadcastItem(i)
+                        .onInteractiveAppServiceRemoved(iAppServiceId);
             } catch (RemoteException e) {
-                Slog.e(TAG, "failed to report removed IApp service to callback", e);
+                Slog.e(TAG, "failed to report removed Interactive App service to callback", e);
             }
         }
         userState.mCallbacks.finishBroadcast();
     }
 
     @GuardedBy("mLock")
-    private void notifyIAppServiceUpdatedLocked(UserState userState, String iAppServiceId) {
+    private void notifyInteractiveAppServiceUpdatedLocked(
+            UserState userState, String iAppServiceId) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyIAppServiceUpdatedLocked(iAppServiceId=" + iAppServiceId + ")");
+            Slog.d(TAG, "notifyInteractiveAppServiceUpdatedLocked(iAppServiceId="
+                    + iAppServiceId + ")");
         }
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
             try {
-                userState.mCallbacks.getBroadcastItem(i).onIAppServiceUpdated(iAppServiceId);
+                userState.mCallbacks.getBroadcastItem(i)
+                        .onInteractiveAppServiceUpdated(iAppServiceId);
             } catch (RemoteException e) {
-                Slog.e(TAG, "failed to report updated IApp service to callback", e);
+                Slog.e(TAG, "failed to report updated Interactive App service to callback", e);
             }
         }
         userState.mCallbacks.finishBroadcast();
@@ -262,7 +272,7 @@
         userState.mCallbacks.finishBroadcast();
     }
 
-    private int getIAppUid(TvIAppInfo info) {
+    private int getInteractiveAppUid(TvInteractiveAppInfo info) {
         try {
             return getContext().getPackageManager().getApplicationInfo(
                     info.getServiceInfo().packageName, 0).uid;
@@ -286,18 +296,18 @@
             registerBroadcastReceivers();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mLock) {
-                buildTvIAppServiceListLocked(mCurrentUserId, null);
+                buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
             }
         }
     }
 
     private void registerBroadcastReceivers() {
         PackageMonitor monitor = new PackageMonitor() {
-            private void buildTvIAppServiceList(String[] packages) {
+            private void buildTvInteractiveAppServiceList(String[] packages) {
                 int userId = getChangingUserId();
                 synchronized (mLock) {
                     if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
-                        buildTvIAppServiceListLocked(userId, packages);
+                        buildTvInteractiveAppServiceListLocked(userId, packages);
                     }
                 }
             }
@@ -305,9 +315,9 @@
             @Override
             public void onPackageUpdateFinished(String packageName, int uid) {
                 if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
-                // This callback is invoked when the TV iApp service is reinstalled.
+                // This callback is invoked when the TV interactive App service is reinstalled.
                 // In this case, isReplacing() always returns true.
-                buildTvIAppServiceList(new String[] { packageName });
+                buildTvInteractiveAppServiceList(new String[] { packageName });
             }
 
             @Override
@@ -318,7 +328,7 @@
                 // This callback is invoked when the media on which some packages exist become
                 // available.
                 if (isReplacing()) {
-                    buildTvIAppServiceList(packages);
+                    buildTvInteractiveAppServiceList(packages);
                 }
             }
 
@@ -331,7 +341,7 @@
                             + ")");
                 }
                 if (isReplacing()) {
-                    buildTvIAppServiceList(packages);
+                    buildTvInteractiveAppServiceList(packages);
                 }
             }
 
@@ -339,17 +349,19 @@
             public void onSomePackagesChanged() {
                 if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()");
                 if (isReplacing()) {
-                    if (DEBUG) Slogf.d(TAG, "Skipped building TV iApp list due to replacing");
-                    // When the package is updated, buildTvIAppServiceListLocked is called in other
-                    // methods instead.
+                    if (DEBUG) {
+                        Slogf.d(TAG, "Skipped building TV interactive App list due to replacing");
+                    }
+                    // When the package is updated, buildTvInteractiveAppServiceListLocked is called
+                    // in other methods instead.
                     return;
                 }
-                buildTvIAppServiceList(null);
+                buildTvInteractiveAppServiceList(null);
             }
 
             @Override
             public boolean onPackageChanged(String packageName, int uid, String[] components) {
-                // The iApp list needs to be updated in any cases, regardless of whether
+                // The interactive App list needs to be updated in any cases, regardless of whether
                 // it happened to the whole package or a specific component. Returning true so that
                 // the update can be handled in {@link #onSomePackagesChanged}.
                 return true;
@@ -401,7 +413,7 @@
             unbindServiceOfUserLocked(mCurrentUserId);
 
             mCurrentUserId = userId;
-            buildTvIAppServiceListLocked(userId, null);
+            buildTvInteractiveAppServiceListLocked(userId, null);
         }
     }
 
@@ -486,7 +498,7 @@
     @GuardedBy("mLock")
     private void startProfileLocked(int userId) {
         mRunningProfiles.add(userId);
-        buildTvIAppServiceListLocked(userId, null);
+        buildTvInteractiveAppServiceListLocked(userId, null);
     }
 
     @GuardedBy("mLock")
@@ -601,13 +613,14 @@
     }
 
     @GuardedBy("mLock")
-    private ITvIAppSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+    private ITvInteractiveAppSession getSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
         return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
     }
 
     @GuardedBy("mLock")
-    private ITvIAppSession getSessionLocked(SessionState sessionState) {
-        ITvIAppSession session = sessionState.mSession;
+    private ITvInteractiveAppSession getSessionLocked(SessionState sessionState) {
+        ITvInteractiveAppSession session = sessionState.mSession;
         if (session == null) {
             throw new IllegalStateException("Session not yet created for token "
                     + sessionState.mSessionToken);
@@ -618,15 +631,15 @@
     private final class BinderService extends ITvIAppManager.Stub {
 
         @Override
-        public List<TvIAppInfo> getTvIAppServiceList(int userId) {
+        public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "getTvIAppServiceList");
+                    Binder.getCallingUid(), userId, "getTvInteractiveAppServiceList");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
-                    List<TvIAppInfo> iAppList = new ArrayList<>();
-                    for (TvIAppState state : userState.mIAppMap.values()) {
+                    List<TvInteractiveAppInfo> iAppList = new ArrayList<>();
+                    for (TvInteractiveAppState state : userState.mIAppMap.values()) {
                         iAppList.add(state.mInfo);
                     }
                     return iAppList;
@@ -645,7 +658,7 @@
             try {
                 synchronized (mLock) {
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
-                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
                     if (iAppState == null) {
                         Slogf.e(TAG, "failed to prepare TIAS - unknown TIAS id " + tiasId);
                         return;
@@ -673,16 +686,16 @@
         }
 
         @Override
-        public void notifyAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+        public void registerAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "notifyAppLinkInfo");
+                    Binder.getCallingUid(), userId, "registerAppLinkInfo");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
-                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
                     if (iAppState == null) {
-                        Slogf.e(TAG, "failed to notifyAppLinkInfo - unknown TIAS id "
+                        Slogf.e(TAG, "failed to registerAppLinkInfo - unknown TIAS id "
                                 + tiasId);
                         return;
                     }
@@ -691,18 +704,54 @@
                     if (serviceState == null) {
                         serviceState = new ServiceState(
                                 componentName, tiasId, resolvedUserId);
-                        serviceState.addPendingAppLink(appLinkInfo);
+                        serviceState.addPendingAppLink(appLinkInfo, true);
                         userState.mServiceStateMap.put(componentName, serviceState);
                         updateServiceConnectionLocked(componentName, resolvedUserId);
                     } else if (serviceState.mService != null) {
-                        serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+                        serviceState.mService.registerAppLinkInfo(appLinkInfo);
                     } else {
-                        serviceState.addPendingAppLink(appLinkInfo);
+                        serviceState.addPendingAppLink(appLinkInfo, true);
                         updateServiceConnectionLocked(componentName, resolvedUserId);
                     }
                 }
             } catch (RemoteException e) {
-                Slogf.e(TAG, "error in notifyAppLinkInfo", e);
+                Slogf.e(TAG, "error in registerAppLinkInfo", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void unregisterAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "unregisterAppLinkInfo");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
+                    if (iAppState == null) {
+                        Slogf.e(TAG, "failed to unregisterAppLinkInfo - unknown TIAS id "
+                                + tiasId);
+                        return;
+                    }
+                    ComponentName componentName = iAppState.mInfo.getComponent();
+                    ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+                    if (serviceState == null) {
+                        serviceState = new ServiceState(
+                                componentName, tiasId, resolvedUserId);
+                        serviceState.addPendingAppLink(appLinkInfo, false);
+                        userState.mServiceStateMap.put(componentName, serviceState);
+                        updateServiceConnectionLocked(componentName, resolvedUserId);
+                    } else if (serviceState.mService != null) {
+                        serviceState.mService.unregisterAppLinkInfo(appLinkInfo);
+                    } else {
+                        serviceState.addPendingAppLink(appLinkInfo, false);
+                        updateServiceConnectionLocked(componentName, resolvedUserId);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "error in unregisterAppLinkInfo", e);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -716,7 +765,7 @@
             try {
                 synchronized (mLock) {
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
-                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId);
                     if (iAppState == null) {
                         Slogf.e(TAG, "failed to sendAppLinkCommand - unknown TIAS id "
                                 + tiasId);
@@ -743,7 +792,8 @@
         }
 
         @Override
-        public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
+        public void createSession(
+                final ITvInteractiveAppClient client, final String iAppServiceId, int type,
                 int seq, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
@@ -760,7 +810,7 @@
                         return;
                     }
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
-                    TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId);
+                    TvInteractiveAppState iAppState = userState.mIAppMap.get(iAppServiceId);
                     if (iAppState == null) {
                         Slogf.w(TAG, "Failed to find state for iAppServiceId=" + iAppServiceId);
                         sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq);
@@ -994,13 +1044,35 @@
         }
 
         @Override
-        public void startIApp(IBinder sessionToken, int userId) {
+        public void notifySignalStrength(IBinder sessionToken, int strength, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifySignalStrength");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifySignalStrength(strength);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifySignalStrength", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void startInteractiveApp(IBinder sessionToken, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
             }
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
-                    userId, "startIApp");
+                    userId, "startInteractiveApp");
             SessionState sessionState = null;
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1008,7 +1080,7 @@
                     try {
                         sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 resolvedUserId);
-                        getSessionLocked(sessionState).startIApp();
+                        getSessionLocked(sessionState).startInteractiveApp();
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slogf.e(TAG, "error in start", e);
                     }
@@ -1019,13 +1091,13 @@
         }
 
         @Override
-        public void stopIApp(IBinder sessionToken, int userId) {
+        public void stopInteractiveApp(IBinder sessionToken, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "BinderService#stop(userId=%d)", userId);
             }
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
-                    userId, "stopIApp");
+                    userId, "stopInteractiveApp");
             SessionState sessionState = null;
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1033,7 +1105,7 @@
                     try {
                         sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 resolvedUserId);
-                        getSessionLocked(sessionState).stopIApp();
+                        getSessionLocked(sessionState).stopInteractiveApp();
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slogf.e(TAG, "error in stop", e);
                     }
@@ -1044,6 +1116,31 @@
         }
 
         @Override
+        public void resetInteractiveApp(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "BinderService#reset(userId=%d)", userId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "resetInteractiveApp");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).resetInteractiveApp();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in reset", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void createBiInteractiveApp(
                 IBinder sessionToken, Uri biIAppUri, Bundle params, int userId) {
             if (DEBUG) {
@@ -1096,6 +1193,31 @@
         }
 
         @Override
+        public void setTeletextAppEnabled(IBinder sessionToken, boolean enable, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "setTeletextAppEnabled(enable=%d)", enable);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setTeletextAppEnabled");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).setTeletextAppEnabled(enable);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in setTeletextAppEnabled", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
@@ -1196,6 +1318,31 @@
         }
 
         @Override
+        public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCurrentTvInputId");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendCurrentTvInputId(inputId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCurrentTvInputId", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void setSurface(IBinder sessionToken, Surface surface, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -1290,7 +1437,7 @@
         }
 
         @Override
-        public void registerCallback(final ITvIAppManagerCallback callback, int userId) {
+        public void registerCallback(final ITvInteractiveAppManagerCallback callback, int userId) {
             int callingPid = Binder.getCallingPid();
             int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
@@ -1309,7 +1456,7 @@
         }
 
         @Override
-        public void unregisterCallback(ITvIAppManagerCallback callback, int userId) {
+        public void unregisterCallback(ITvInteractiveAppManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "unregisterCallback");
             final long identity = Binder.clearCallingIdentity();
@@ -1335,7 +1482,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .createMediaView(windowToken, frame);
-                    } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in createMediaView", e);
                     }
                 }
@@ -1355,7 +1502,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .relayoutMediaView(frame);
-                    } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in relayoutMediaView", e);
                     }
                 }
@@ -1375,7 +1522,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .removeMediaView();
-                    } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in removeMediaView", e);
                     }
                 }
@@ -1386,8 +1533,9 @@
     }
 
     @GuardedBy("mLock")
-    private void sendSessionTokenToClientLocked(ITvIAppClient client, String iAppServiceId,
-            IBinder sessionToken, InputChannel channel, int seq) {
+    private void sendSessionTokenToClientLocked(
+            ITvInteractiveAppClient client, String iAppServiceId, IBinder sessionToken,
+            InputChannel channel, int seq) {
         try {
             client.onSessionCreated(iAppServiceId, sessionToken, channel, seq);
         } catch (RemoteException e) {
@@ -1396,8 +1544,8 @@
     }
 
     @GuardedBy("mLock")
-    private boolean createSessionInternalLocked(ITvIAppService service, IBinder sessionToken,
-            int userId) {
+    private boolean createSessionInternalLocked(
+            ITvInteractiveAppService service, IBinder sessionToken, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         SessionState sessionState = userState.mSessionStateMap.get(sessionToken);
         if (DEBUG) {
@@ -1407,7 +1555,7 @@
         InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
 
         // Set up a callback to send the session token.
-        ITvIAppSessionCallback callback = new SessionCallback(sessionState, channels);
+        ITvInteractiveAppSessionCallback callback = new SessionCallback(sessionState, channels);
 
         boolean created = true;
         // Create a session. When failed, send a null token immediately.
@@ -1514,7 +1662,8 @@
         }
 
         boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
-                || (serviceState.mPendingPrepare) || (!serviceState.mPendingAppLinkInfo.isEmpty());
+                || (serviceState.mPendingPrepare)
+                || (!serviceState.mPendingAppLinkInfo.isEmpty());
 
         if (serviceState.mService == null && shouldBind) {
             // This means that the service is not yet connected but its state indicates that we
@@ -1528,7 +1677,8 @@
                 Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
             }
 
-            Intent i = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+            Intent i =
+                    new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
             serviceState.mBound = mContext.bindServiceAsUser(
                     i, serviceState.mConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
@@ -1546,20 +1696,20 @@
 
     private static final class UserState {
         private final int mUserId;
-        // A mapping from the TV IApp ID to its TvIAppState.
-        private Map<String, TvIAppState> mIAppMap = new HashMap<>();
+        // A mapping from the TV Interactive App ID to its TvInteractiveAppState.
+        private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
-        // A mapping from the name of a TV IApp service to its state.
+        // A mapping from the name of a TV Interactive App service to its state.
         private final Map<ComponentName, ServiceState> mServiceStateMap = new HashMap<>();
-        // A mapping from the token of a TV IApp session to its state.
+        // A mapping from the token of a TV Interactive App session to its state.
         private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>();
 
-        // A set of all TV IApp service packages.
+        // A set of all TV Interactive App service packages.
         private final Set<String> mPackageSet = new HashSet<>();
 
         // A list of callbacks.
-        private final RemoteCallbackList<ITvIAppManagerCallback> mCallbacks =
+        private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
                 new RemoteCallbackList<>();
 
         private UserState(int userId) {
@@ -1567,20 +1717,20 @@
         }
     }
 
-    private static final class TvIAppState {
+    private static final class TvInteractiveAppState {
         private String mIAppServiceId;
         private ComponentName mComponentName;
-        private TvIAppInfo mInfo;
+        private TvInteractiveAppInfo mInfo;
         private int mUid;
         private int mIAppNumber;
     }
 
     private final class SessionState implements IBinder.DeathRecipient {
         private final IBinder mSessionToken;
-        private ITvIAppSession mSession;
+        private ITvInteractiveAppSession mSession;
         private final String mIAppServiceId;
         private final int mType;
-        private final ITvIAppClient mClient;
+        private final ITvInteractiveAppClient mClient;
         private final int mSeq;
         private final ComponentName mComponent;
 
@@ -1595,8 +1745,8 @@
         private final int mUserId;
 
         private SessionState(IBinder sessionToken, String iAppServiceId, int type,
-                ComponentName componentName, ITvIAppClient client, int seq, int callingUid,
-                int callingPid, int userId) {
+                ComponentName componentName, ITvInteractiveAppClient client, int seq,
+                int callingUid, int callingPid, int userId) {
             mSessionToken = sessionToken;
             mIAppServiceId = iAppServiceId;
             mComponent = componentName;
@@ -1660,12 +1810,12 @@
         private final ServiceConnection mConnection;
         private final ComponentName mComponent;
         private final String mIAppServiceId;
-        private final List<Bundle> mPendingAppLinkInfo = new ArrayList<>();
+        private final List<Pair<Bundle, Boolean>> mPendingAppLinkInfo = new ArrayList<>();
         private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
 
         private boolean mPendingPrepare = false;
         private Integer mPendingPrepareType = null;
-        private ITvIAppService mService;
+        private ITvInteractiveAppService mService;
         private ServiceCallback mCallback;
         private boolean mBound;
         private boolean mReconnecting;
@@ -1679,12 +1829,12 @@
             mComponent = component;
             mPendingPrepare = pendingPrepare;
             mPendingPrepareType = prepareType;
-            mConnection = new IAppServiceConnection(component, userId);
+            mConnection = new InteractiveAppServiceConnection(component, userId);
             mIAppServiceId = tias;
         }
 
-        private void addPendingAppLink(Bundle info) {
-            mPendingAppLinkInfo.add(info);
+        private void addPendingAppLink(Bundle info, boolean register) {
+            mPendingAppLinkInfo.add(Pair.create(info, register));
         }
 
         private void addPendingAppLinkCommand(Bundle command) {
@@ -1692,11 +1842,11 @@
         }
     }
 
-    private final class IAppServiceConnection implements ServiceConnection {
+    private final class InteractiveAppServiceConnection implements ServiceConnection {
         private final ComponentName mComponent;
         private final int mUserId;
 
-        private IAppServiceConnection(ComponentName component, int userId) {
+        private InteractiveAppServiceConnection(ComponentName component, int userId) {
             mComponent = component;
             mUserId = userId;
         }
@@ -1714,7 +1864,7 @@
                     return;
                 }
                 ServiceState serviceState = userState.mServiceStateMap.get(mComponent);
-                serviceState.mService = ITvIAppService.Stub.asInterface(service);
+                serviceState.mService = ITvInteractiveAppService.Stub.asInterface(service);
 
                 if (serviceState.mPendingPrepare) {
                     final long identity = Binder.clearCallingIdentity();
@@ -1730,15 +1880,20 @@
                 }
 
                 if (!serviceState.mPendingAppLinkInfo.isEmpty()) {
-                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkInfo.iterator();
+                    for (Iterator<Pair<Bundle, Boolean>> it =
+                            serviceState.mPendingAppLinkInfo.iterator();
                             it.hasNext(); ) {
-                        Bundle appLinkInfo = it.next();
+                        Pair<Bundle, Boolean> appLinkInfoPair = it.next();
                         final long identity = Binder.clearCallingIdentity();
                         try {
-                            serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+                            if (appLinkInfoPair.second) {
+                                serviceState.mService.registerAppLinkInfo(appLinkInfoPair.first);
+                            } else {
+                                serviceState.mService.unregisterAppLinkInfo(appLinkInfoPair.first);
+                            }
                             it.remove();
                         } catch (RemoteException e) {
-                            Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfo
+                            Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfoPair
                                     + ") when onServiceConnected", e);
                         } finally {
                             Binder.restoreCallingIdentity(identity);
@@ -1803,7 +1958,7 @@
         }
     }
 
-    private final class ServiceCallback extends ITvIAppServiceCallback.Stub {
+    private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
         private final ComponentName mComponent;
         private final int mUserId;
 
@@ -1828,7 +1983,7 @@
         }
     }
 
-    private final class SessionCallback extends ITvIAppSessionCallback.Stub {
+    private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
         private final SessionState mSessionState;
         private final InputChannel[] mInputChannels;
 
@@ -1838,7 +1993,7 @@
         }
 
         @Override
-        public void onSessionCreated(ITvIAppSession session) {
+        public void onSessionCreated(ITvInteractiveAppSession session) {
             if (DEBUG) {
                 Slogf.d(TAG, "onSessionCreated(iAppServiceId="
                         + mSessionState.mIAppServiceId + ")");
@@ -1916,7 +2071,8 @@
         }
 
         @Override
-        public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType,
+        public void onCommandRequest(
+                @TvIAppService.InteractiveAppServiceCommandType String cmdType,
                 Bundle parameters) {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -2020,6 +2176,23 @@
         }
 
         @Override
+        public void onRequestCurrentTvInputId() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestCurrentTvInputId");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestCurrentTvInputId", e);
+                }
+            }
+        }
+
+        @Override
         public void onAdRequest(AdRequest request) {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -2072,8 +2245,25 @@
             }
         }
 
+        @Override
+        public void onTeletextAppStateChanged(int state) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onTeletextAppStateChanged (state=" + state + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onTeletextAppStateChanged(state, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onTeletextAppStateChanged", e);
+                }
+            }
+        }
+
         @GuardedBy("mLock")
-        private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) {
+        private boolean addSessionTokenToClientStateLocked(ITvInteractiveAppSession session) {
             try {
                 session.asBinder().linkToDeath(mSessionState, 0);
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 6db25b7..c96c1ee 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -22,8 +22,6 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.server.VcnManagementService.LOCAL_LOG;
 
@@ -47,6 +45,7 @@
 import com.android.server.vcn.VcnContext;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /** @hide */
@@ -121,9 +120,10 @@
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundle carrierConfig) {
-        // TODO: Check Network Quality reported by metric monitors/probers.
-
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        final boolean isSelectedUnderlyingNetwork =
+                currentlySelected != null
+                        && Objects.equals(currentlySelected.network, networkRecord.network);
 
         final int meteredMatch = networkPriority.getMetered();
         final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
@@ -132,6 +132,23 @@
             return false;
         }
 
+        // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not
+        // selected, but less than entry threshold
+        if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
+                || (caps.getLinkUpstreamBandwidthKbps()
+                                < networkPriority.getMinEntryUpstreamBandwidthKbps()
+                        && !isSelectedUnderlyingNetwork)) {
+            return false;
+        }
+
+        if (caps.getLinkDownstreamBandwidthKbps()
+                        < networkPriority.getMinExitDownstreamBandwidthKbps()
+                || (caps.getLinkDownstreamBandwidthKbps()
+                                < networkPriority.getMinEntryDownstreamBandwidthKbps()
+                        && !isSelectedUnderlyingNetwork)) {
+            return false;
+        }
+
         if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
             return true;
         }
@@ -172,8 +189,7 @@
         }
 
         // TODO: Move the Network Quality check to the network metric monitor framework.
-        if (networkPriority.getNetworkQuality()
-                > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) {
+        if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) {
             return false;
         }
 
@@ -185,7 +201,7 @@
         return true;
     }
 
-    private static int getWifiQuality(
+    private static boolean isWifiRssiAcceptable(
             UnderlyingNetworkRecord networkRecord,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundle carrierConfig) {
@@ -196,14 +212,14 @@
 
         if (isSelectedNetwork
                 && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
-            return NETWORK_QUALITY_OK;
+            return true;
         }
 
         if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
-            return NETWORK_QUALITY_OK;
+            return true;
         }
 
-        return NETWORK_QUALITY_ANY;
+        return false;
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 8f703c5..0396a11 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -45,6 +45,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
+import static com.android.server.wm.utils.RegionUtils.forEachRect;
 
 import android.accessibilityservice.AccessibilityTrace;
 import android.animation.ObjectAnimator;
@@ -100,7 +101,6 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
 import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -133,22 +133,19 @@
     private static final Rect EMPTY_RECT = new Rect();
     private static final float[] sTempFloats = new float[9];
 
-    private final SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
-    private final SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
+    private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
+    private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
     private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
     private int mFocusedDisplay = -1;
     private boolean mIsImeVisible = false;
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
-    private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator;
 
     AccessibilityController(WindowManagerService service) {
         mService = service;
         mAccessibilityTracing =
                 AccessibilityController.getAccessibilityControllerInternal(service);
-
-        mAccessibilityWindowsPopulator = new AccessibilityWindowsPopulator(mService, this);
     }
 
     boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) {
@@ -212,9 +209,7 @@
                 }
                 mWindowsForAccessibilityObserver.remove(displayId);
             }
-            mAccessibilityWindowsPopulator.setWindowsNotification(true);
-            observer = new WindowsForAccessibilityObserver(mService, displayId, callback,
-                    mAccessibilityWindowsPopulator);
+            observer = new WindowsForAccessibilityObserver(mService, displayId, callback);
             mWindowsForAccessibilityObserver.put(displayId, observer);
             mAllObserversInitialized &= observer.mInitialized;
         } else {
@@ -229,10 +224,6 @@
                 }
             }
             mWindowsForAccessibilityObserver.remove(displayId);
-
-            if (mWindowsForAccessibilityObserver.size() <= 0) {
-                mAccessibilityWindowsPopulator.setWindowsNotification(false);
-            }
         }
     }
 
@@ -318,6 +309,11 @@
         if (displayMagnifier != null) {
             displayMagnifier.onDisplaySizeChanged(displayContent);
         }
+        final WindowsForAccessibilityObserver windowsForA11yObserver =
+                mWindowsForAccessibilityObserver.get(displayId);
+        if (windowsForA11yObserver != null) {
+            windowsForA11yObserver.scheduleComputeChangedWindows();
+        }
     }
 
     void onAppWindowTransition(int displayId, int transition) {
@@ -345,6 +341,11 @@
         if (displayMagnifier != null) {
             displayMagnifier.onWindowTransition(windowState, transition);
         }
+        final WindowsForAccessibilityObserver windowsForA11yObserver =
+                mWindowsForAccessibilityObserver.get(displayId);
+        if (windowsForA11yObserver != null) {
+            windowsForA11yObserver.scheduleComputeChangedWindows();
+        }
     }
 
     void onWindowFocusChangedNot(int displayId) {
@@ -454,19 +455,6 @@
         return null;
     }
 
-    boolean getMagnificationSpecForDisplay(int displayId, MagnificationSpec outSpec) {
-        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-            mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForDisplay",
-                    FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId);
-        }
-        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
-        if (displayMagnifier == null) {
-            return false;
-        }
-
-        return displayMagnifier.getMagnificationSpec(outSpec);
-    }
-
     boolean hasCallbacks() {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
                 | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -768,25 +756,6 @@
             return spec;
         }
 
-        boolean getMagnificationSpec(MagnificationSpec outSpec) {
-            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-                mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec",
-                        FLAGS_MAGNIFICATION_CALLBACK);
-            }
-            MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
-            if (spec == null) {
-                return false;
-            }
-
-            outSpec.setTo(spec);
-            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-                mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec",
-                        FLAGS_MAGNIFICATION_CALLBACK, "outSpec={" + outSpec + "}");
-            }
-
-            return true;
-        }
-
         void getMagnificationRegion(Region outMagnificationRegion) {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion",
@@ -1434,18 +1403,20 @@
 
         private static final boolean DEBUG = false;
 
-        private final List<AccessibilityWindow> mTempA11yWindows = new ArrayList<>();
+        private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>();
 
         private final Set<IBinder> mTempBinderSet = new ArraySet<>();
 
+        private final RectF mTempRectF = new RectF();
+
+        private final Matrix mTempMatrix = new Matrix();
+
         private final Point mTempPoint = new Point();
 
         private final Region mTempRegion = new Region();
 
         private final Region mTempRegion1 = new Region();
 
-        private final Region mTempRegion2 = new Region();
-
         private final WindowManagerService mService;
 
         private final Handler mHandler;
@@ -1460,11 +1431,10 @@
 
         // Set to true if initializing window population complete.
         private boolean mInitialized;
-        private final AccessibilityWindowsPopulator mA11yWindowsPopulator;
 
         WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
-                int displayId, WindowsForAccessibilityCallback callback,
-                AccessibilityWindowsPopulator accessibilityWindowsPopulator) {
+                int displayId,
+                WindowsForAccessibilityCallback callback) {
             mService = windowManagerService;
             mCallback = callback;
             mDisplayId = displayId;
@@ -1473,7 +1443,6 @@
                     AccessibilityController.getAccessibilityControllerInternal(mService);
             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
                     .getSendRecurringAccessibilityEventsInterval();
-            mA11yWindowsPopulator = accessibilityWindowsPopulator;
             computeChangedWindows(true);
         }
 
@@ -1497,6 +1466,52 @@
             }
         }
 
+        boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) {
+            int wsLayer = mService.mPolicy.getWindowLayerLw(windowState);
+            int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(),
+                    true);
+            return shellLayer >= wsLayer;
+        }
+
+        int addShellRootsIfAbove(WindowState windowState, ArrayList<ShellRoot> shellRoots,
+                int shellRootIndex, List<WindowInfo> windows, Set<IBinder> addedWindows,
+                Region unaccountedSpace, boolean focusedWindowAdded) {
+            while (shellRootIndex < shellRoots.size()
+                    && shellRootIsAbove(windowState, shellRoots.get(shellRootIndex))) {
+                ShellRoot shellRoot = shellRoots.get(shellRootIndex);
+                shellRootIndex++;
+                final WindowInfo info = shellRoot.getWindowInfo();
+                if (info == null) {
+                    continue;
+                }
+
+                info.layer = addedWindows.size();
+                windows.add(info);
+                addedWindows.add(info.token);
+                unaccountedSpace.op(info.regionInScreen, unaccountedSpace,
+                        Region.Op.REVERSE_DIFFERENCE);
+                if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
+                    break;
+                }
+            }
+            return shellRootIndex;
+        }
+
+        private ArrayList<ShellRoot> getSortedShellRoots(
+                SparseArray<ShellRoot> originalShellRoots) {
+            ArrayList<ShellRoot> sortedShellRoots = new ArrayList<>(originalShellRoots.size());
+            for (int i = originalShellRoots.size() - 1; i >= 0; --i) {
+                sortedShellRoots.add(originalShellRoots.valueAt(i));
+            }
+
+            sortedShellRoots.sort((left, right) ->
+                    mService.mPolicy.getWindowLayerFromTypeLw(right.getWindowType(), true)
+                            - mService.mPolicy.getWindowLayerFromTypeLw(left.getWindowType(),
+                            true));
+
+            return sortedShellRoots;
+        }
+
         /**
          * Check if windows have changed, and send them to the accessibility subsystem if they have.
          *
@@ -1546,29 +1561,44 @@
                 Region unaccountedSpace = mTempRegion;
                 unaccountedSpace.set(0, 0, screenWidth, screenHeight);
 
-                final List<AccessibilityWindow> visibleWindows = mTempA11yWindows;
-                mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
-                        mDisplayId, visibleWindows);
+                final SparseArray<WindowState> visibleWindows = mTempWindowStates;
+                populateVisibleWindowsOnScreen(visibleWindows);
                 Set<IBinder> addedWindows = mTempBinderSet;
                 addedWindows.clear();
 
                 boolean focusedWindowAdded = false;
 
                 final int visibleWindowCount = visibleWindows.size();
+                ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>();
+
+                ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots);
 
                 // Iterate until we figure out what is touchable for the entire screen.
-                for (int i = 0; i < visibleWindowCount; i++) {
-                    final AccessibilityWindow a11yWindow = visibleWindows.get(i);
-                    final Region regionInWindow = new Region();
-                    a11yWindow.getTouchableRegionInWindow(regionInWindow);
-                    if (windowMattersToAccessibility(a11yWindow, regionInWindow,
-                            unaccountedSpace)) {
-                        addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
-                        if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
-                            updateUnaccountedSpace(a11yWindow, unaccountedSpace);
+                int shellRootIndex = 0;
+                for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                    final WindowState windowState = visibleWindows.valueAt(i);
+                    int prevShellRootIndex = shellRootIndex;
+                    shellRootIndex = addShellRootsIfAbove(windowState, shellRoots, shellRootIndex,
+                            windows, addedWindows, unaccountedSpace, focusedWindowAdded);
+
+                    // If a Shell Root was added, it could have accounted for all the space already.
+                    if (shellRootIndex > prevShellRootIndex && unaccountedSpace.isEmpty()
+                            && focusedWindowAdded) {
+                        break;
+                    }
+
+                    final Region regionInScreen = new Region();
+                    computeWindowRegionInScreen(windowState, regionInScreen);
+                    if (windowMattersToAccessibility(windowState,
+                            regionInScreen, unaccountedSpace,
+                            skipRemainingWindowsForTaskFragments)) {
+                        addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows);
+                        if (windowMattersToUnaccountedSpaceComputation(windowState)) {
+                            updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace,
+                                    skipRemainingWindowsForTaskFragments);
                         }
-                        focusedWindowAdded |= a11yWindow.isFocused();
-                    } else if (a11yWindow.isUntouchableNavigationBar()) {
+                        focusedWindowAdded |= windowState.isFocused();
+                    } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) {
                         // If this widow is navigation bar without touchable region, accounting the
                         // region of navigation bar inset because all touch events from this region
                         // would be received by launcher, i.e. this region is a un-touchable one
@@ -1617,39 +1647,47 @@
 
         // Some windows should be excluded from unaccounted space computation, though they still
         // should be reported
-        private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
+        private boolean windowMattersToUnaccountedSpaceComputation(WindowState windowState) {
             // Do not account space of trusted non-touchable windows, except the split-screen
             // divider.
             // If it's not trusted, touch events are not sent to the windows behind it.
-            if (((a11yWindow.getFlags() & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
-                    && (a11yWindow.getType() != TYPE_DOCK_DIVIDER)
-                    && a11yWindow.isTrustedOverlay()) {
+            if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+                    && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)
+                    && windowState.isTrustedOverlay()) {
                 return false;
             }
 
-            if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+            if (windowState.mAttrs.type
+                    == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
                 return false;
             }
             return true;
         }
 
-        private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
-                Region regionInScreen, Region unaccountedSpace) {
-            if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
+        private boolean windowMattersToAccessibility(WindowState windowState,
+                Region regionInScreen, Region unaccountedSpace,
+                ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
+            final RecentsAnimationController controller = mService.getRecentsAnimationController();
+            if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
                 return false;
             }
 
-            if (a11yWindow.isFocused()) {
+            if (windowState.isFocused()) {
                 return true;
             }
 
+            // If the window is part of a task that we're finished with - ignore.
+            final TaskFragment taskFragment = windowState.getTaskFragment();
+            if (taskFragment != null
+                    && skipRemainingWindowsForTaskFragments.contains(taskFragment)) {
+                return false;
+            }
+
             // Ignore non-touchable windows, except the split-screen divider, which is
             // occasionally non-touchable but still useful for identifying split-screen
-            // mode and the PIP menu.
-            if (((a11yWindow.getFlags()
-                    & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
-                    && (a11yWindow.getType() != TYPE_DOCK_DIVIDER
-                    && !a11yWindow.isPIPMenu())) {
+            // mode.
+            if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+                    && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
                 return false;
             }
 
@@ -1659,36 +1697,88 @@
             }
 
             // Add windows of certain types not covered by modal windows.
-            if (isReportedWindowType(a11yWindow.getType())) {
+            if (isReportedWindowType(windowState.mAttrs.type)) {
                 return true;
             }
 
             return false;
         }
 
-        private void updateUnaccountedSpace(AccessibilityWindow a11yWindow,
-                Region unaccountedSpace) {
-            if (a11yWindow.getType()
-                    != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
-                // Account for the space this window takes if the window
-                // is not an accessibility overlay which does not change
-                // the reported windows.
-                final Region touchableRegion = mTempRegion2;
-                a11yWindow.getTouchableRegionInScreen(touchableRegion);
-                unaccountedSpace.op(touchableRegion, unaccountedSpace,
-                        Region.Op.REVERSE_DIFFERENCE);
-                // Account for the space of letterbox.
-                final Region letterboxBounds = mTempRegion1;
-                if (a11yWindow.setLetterBoxBoundsIfNeeded(letterboxBounds)) {
-                    unaccountedSpace.op(letterboxBounds,
-                            unaccountedSpace, Region.Op.REVERSE_DIFFERENCE);
+        private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen,
+                Region unaccountedSpace,
+                ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
+            // Account for the space this window takes if the window
+            // is not an accessibility overlay which does not change
+            // the reported windows.
+            unaccountedSpace.op(regionInScreen, unaccountedSpace,
+                    Region.Op.REVERSE_DIFFERENCE);
+
+            // If a window is modal it prevents other windows from being touched
+            if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
+                if (!windowState.hasTapExcludeRegion()) {
+                    // Account for all space in the task, whether the windows in it are
+                    // touchable or not. The modal window blocks all touches from the task's
+                    // area.
+                    unaccountedSpace.op(windowState.getDisplayFrame(), unaccountedSpace,
+                            Region.Op.REVERSE_DIFFERENCE);
+                } else {
+                    // If a window has tap exclude region, we need to account it.
+                    final Region displayRegion = new Region(windowState.getDisplayFrame());
+                    final Region tapExcludeRegion = new Region();
+                    windowState.getTapExcludeRegion(tapExcludeRegion);
+                    displayRegion.op(tapExcludeRegion, displayRegion,
+                            Region.Op.REVERSE_DIFFERENCE);
+                    unaccountedSpace.op(displayRegion, unaccountedSpace,
+                            Region.Op.REVERSE_DIFFERENCE);
                 }
+
+                final TaskFragment taskFragment = windowState.getTaskFragment();
+                if (taskFragment != null) {
+                    // If the window is associated with a particular task, we can skip the
+                    // rest of the windows for that task.
+                    skipRemainingWindowsForTaskFragments.add(taskFragment);
+                } else if (!windowState.hasTapExcludeRegion()) {
+                    // If the window is not associated with a particular task, then it is
+                    // globally modal. In this case we can skip all remaining windows when
+                    // it doesn't has tap exclude region.
+                    unaccountedSpace.setEmpty();
+                }
+            }
+
+            // Account for the space of letterbox.
+            if (windowState.areAppWindowBoundsLetterboxed()) {
+                unaccountedSpace.op(getLetterboxBounds(windowState), unaccountedSpace,
+                        Region.Op.REVERSE_DIFFERENCE);
             }
         }
 
-        private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow,
-                Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) {
-            final WindowInfo window = a11yWindow.getWindowInfo();
+        private void computeWindowRegionInScreen(WindowState windowState, Region outRegion) {
+            // Get the touchable frame.
+            Region touchableRegion = mTempRegion1;
+            windowState.getTouchableRegion(touchableRegion);
+
+            // Map the frame to get what appears on the screen.
+            Matrix matrix = mTempMatrix;
+            populateTransformationMatrix(windowState, matrix);
+
+            forEachRect(touchableRegion, rect -> {
+                // Move to origin as all transforms are captured by the matrix.
+                RectF windowFrame = mTempRectF;
+                windowFrame.set(rect);
+                windowFrame.offset(-windowState.getFrame().left, -windowState.getFrame().top);
+
+                matrix.mapRect(windowFrame);
+
+                // Union all rects.
+                outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
+                        (int) windowFrame.right, (int) windowFrame.bottom));
+            });
+        }
+
+        private static void addPopulatedWindowInfo(WindowState windowState, Region regionInScreen,
+                List<WindowInfo> out, Set<IBinder> tokenOut) {
+            final WindowInfo window = windowState.getWindowInfo();
             window.regionInScreen.set(regionInScreen);
             window.layer = tokenOut.size();
             out.add(window);
@@ -1715,6 +1805,23 @@
                     && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
         }
 
+        private void populateVisibleWindowsOnScreen(SparseArray<WindowState> outWindows) {
+            final List<WindowState> tempWindowStatesList = new ArrayList<>();
+            final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
+            if (dc == null) {
+                return;
+            }
+
+            dc.forAllWindows(w -> {
+                if (w.isVisible()) {
+                    tempWindowStatesList.add(w);
+                }
+            }, false /* traverseTopToBottom */);
+            for (int i = 0; i < tempWindowStatesList.size(); i++) {
+                outWindows.put(i, tempWindowStatesList.get(i));
+            }
+        }
+
         private WindowState getTopFocusWindow() {
             return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
         }
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
deleted file mode 100644
index f31ae06..0000000
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-
-import static com.android.server.wm.utils.RegionUtils.forEachRect;
-
-import android.annotation.NonNull;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.IWindow;
-import android.view.InputWindowHandle;
-import android.view.MagnificationSpec;
-import android.view.WindowInfo;
-import android.view.WindowManager;
-import android.window.WindowInfosListener;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class is the accessibility windows population adapter.
- */
-public final class AccessibilityWindowsPopulator extends WindowInfosListener {
-
-    private static final String TAG = AccessibilityWindowsPopulator.class.getSimpleName();
-    // If the surface flinger callback is not coming within in 2 frames time, i.e. about
-    // 35ms, then assuming the windows become stable.
-    private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35;
-    // To avoid the surface flinger callbacks always comes within in 2 frames, then no windows
-    // are reported to the A11y framework, and the animation duration time is 500ms, so setting
-    // this value as the max timeout value to force computing changed windows.
-    private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500;
-
-    private static final float[] sTempFloats = new float[9];
-
-    private final WindowManagerService mService;
-    private final AccessibilityController mAccessibilityController;
-    @GuardedBy("mLock")
-    private final SparseArray<List<InputWindowHandle>> mInputWindowHandlesOnDisplays =
-            new SparseArray<>();
-    @GuardedBy("mLock")
-    private final SparseArray<Matrix> mMagnificationSpecInverseMatrix = new SparseArray<>();
-    @GuardedBy("mLock")
-    private final SparseArray<DisplayInfo> mDisplayInfos = new SparseArray<>();
-    @GuardedBy("mLock")
-    private final List<InputWindowHandle> mVisibleWindows = new ArrayList<>();
-    @GuardedBy("mLock")
-    private boolean mWindowsNotificationEnabled = false;
-    private final Object mLock = new Object();
-    private final Handler mHandler;
-
-    AccessibilityWindowsPopulator(WindowManagerService service,
-            AccessibilityController accessibilityController) {
-        mService = service;
-        mAccessibilityController = accessibilityController;
-        mHandler = new MyHandler(mService.mH.getLooper());
-
-        register();
-    }
-
-    /**
-     * Gets the visible windows list with the window layer on the specified display.
-     *
-     * @param displayId The display.
-     * @param outWindows The visible windows list. The z-order of each window in the list
-     *                   is from the top to bottom.
-     */
-    public void populateVisibleWindowsOnScreenLocked(int displayId,
-            List<AccessibilityWindow> outWindows) {
-        List<InputWindowHandle> inputWindowHandles;
-        final Matrix inverseMatrix = new Matrix();
-        final Matrix displayMatrix = new Matrix();
-
-        synchronized (mLock) {
-            inputWindowHandles = mInputWindowHandlesOnDisplays.get(displayId);
-            if (inputWindowHandles == null) {
-                outWindows.clear();
-
-                return;
-            }
-            inverseMatrix.set(mMagnificationSpecInverseMatrix.get(displayId));
-
-            final DisplayInfo displayInfo = mDisplayInfos.get(displayId);
-            if (displayInfo != null) {
-                displayMatrix.set(displayInfo.mTransform);
-            } else {
-                Slog.w(TAG, "The displayInfo of this displayId (" + displayId + ") called "
-                        + "back from the surface fligner is null");
-            }
-        }
-
-        final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-        final ShellRoot shellroot = dc.mShellRoots.get(WindowManager.SHELL_ROOT_LAYER_PIP);
-        final IBinder pipMenuIBinder =
-                shellroot != null ? shellroot.getAccessibilityWindowToken() : null;
-
-        for (final InputWindowHandle windowHandle : inputWindowHandles) {
-            final AccessibilityWindow accessibilityWindow =
-                    AccessibilityWindow.initializeData(mService, windowHandle, inverseMatrix,
-                            pipMenuIBinder, displayMatrix);
-
-            outWindows.add(accessibilityWindow);
-        }
-    }
-
-    @Override
-    public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
-            DisplayInfo[] displayInfos) {
-        synchronized (mLock) {
-            mVisibleWindows.clear();
-            for (InputWindowHandle window : windowHandles) {
-                if (window.visible && window.getWindow() != null) {
-                    mVisibleWindows.add(window);
-                }
-            }
-
-            mDisplayInfos.clear();
-            for (final DisplayInfo displayInfo : displayInfos) {
-                mDisplayInfos.put(displayInfo.mDisplayId, displayInfo);
-            }
-
-            if (mWindowsNotificationEnabled) {
-                if (!mHandler.hasMessages(
-                        MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT)) {
-                    mHandler.sendEmptyMessageDelayed(
-                            MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT,
-                            WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS);
-                }
-                populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked();
-            }
-        }
-    }
-
-    /**
-     * Sets to notify the accessibilityController to compute changed windows on
-     * the display after populating the visible windows if the windows reported
-     * from the surface flinger changes.
-     *
-     * @param register {@code true} means starting windows population.
-     */
-    public void setWindowsNotification(boolean register) {
-        synchronized (mLock) {
-            if (mWindowsNotificationEnabled == register) {
-                return;
-            }
-            mWindowsNotificationEnabled = register;
-            if (mWindowsNotificationEnabled) {
-                populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked();
-            } else {
-                releaseResources();
-            }
-        }
-    }
-
-    private void populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked() {
-        final SparseArray<List<InputWindowHandle>> tempWindowHandleList = new SparseArray<>();
-
-        for (final InputWindowHandle windowHandle : mVisibleWindows) {
-            List<InputWindowHandle> inputWindowHandles = tempWindowHandleList.get(
-                    windowHandle.displayId);
-
-            if (inputWindowHandles == null) {
-                inputWindowHandles = new ArrayList<>();
-                tempWindowHandleList.put(windowHandle.displayId, inputWindowHandles);
-                generateMagnificationSpecInverseMatrixLocked(windowHandle.displayId);
-            }
-            inputWindowHandles.add(windowHandle);
-        }
-
-        final List<Integer> displayIdsForWindowsChanged = new ArrayList<>();
-
-        getDisplaysForWindowsChangedLocked(displayIdsForWindowsChanged, tempWindowHandleList,
-                mInputWindowHandlesOnDisplays);
-        // Clones all windows from the callback of the surface flinger.
-        mInputWindowHandlesOnDisplays.clear();
-        for (int i = 0; i < tempWindowHandleList.size(); i++) {
-            final int displayId = tempWindowHandleList.keyAt(i);
-            mInputWindowHandlesOnDisplays.put(displayId, tempWindowHandleList.get(displayId));
-        }
-
-        if (displayIdsForWindowsChanged.size() > 0) {
-            if (!mHandler.hasMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED)) {
-                mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED,
-                        displayIdsForWindowsChanged).sendToTarget();
-            }
-
-            return;
-        }
-        mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE);
-        mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE,
-                SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS);
-    }
-
-    private void getDisplaysForWindowsChangedLocked(List<Integer> outDisplayIdsForWindowsChanged,
-            SparseArray<List<InputWindowHandle>> newWindowsList,
-            SparseArray<List<InputWindowHandle>> oldWindowsList) {
-        for (int i = 0; i < newWindowsList.size(); i++) {
-            final int displayId = newWindowsList.keyAt(i);
-            final List<InputWindowHandle> newWindows = newWindowsList.get(displayId);
-            final List<InputWindowHandle> oldWindows = oldWindowsList.get(displayId);
-
-            if (hasWindowsChangedLocked(newWindows, oldWindows)) {
-                outDisplayIdsForWindowsChanged.add(displayId);
-            }
-        }
-    }
-
-    private boolean hasWindowsChangedLocked(List<InputWindowHandle> newWindows,
-            List<InputWindowHandle> oldWindows) {
-        if (oldWindows == null || oldWindows.size() != newWindows.size()) {
-            return true;
-        }
-
-        final int windowsCount = newWindows.size();
-        // Since we always traverse windows from high to low layer,
-        // the old and new windows at the same index should be the
-        // same, otherwise something changed.
-        for (int i = 0; i < windowsCount; i++) {
-            final InputWindowHandle newWindow = newWindows.get(i);
-            final InputWindowHandle oldWindow = oldWindows.get(i);
-
-            if (!newWindow.getWindow().asBinder().equals(oldWindow.getWindow().asBinder())) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private void generateMagnificationSpecInverseMatrixLocked(int displayId) {
-        MagnificationSpec spec = new MagnificationSpec();
-        if (!mAccessibilityController.getMagnificationSpecForDisplay(displayId, spec)) {
-            return;
-        }
-        sTempFloats[Matrix.MSCALE_X] = spec.scale;
-        sTempFloats[Matrix.MSKEW_Y] = 0;
-        sTempFloats[Matrix.MSKEW_X] = 0;
-        sTempFloats[Matrix.MSCALE_Y] = spec.scale;
-        sTempFloats[Matrix.MTRANS_X] = spec.offsetX;
-        sTempFloats[Matrix.MTRANS_Y] = spec.offsetY;
-        sTempFloats[Matrix.MPERSP_0] = 0;
-        sTempFloats[Matrix.MPERSP_1] = 0;
-        sTempFloats[Matrix.MPERSP_2] = 1;
-
-        final Matrix tempMatrix = new Matrix();
-        tempMatrix.setValues(sTempFloats);
-
-        final Matrix inverseMatrix = new Matrix();
-        final boolean result = tempMatrix.invert(inverseMatrix);
-
-        if (!result) {
-            Slog.e(TAG, "Can't inverse the magnification spec matrix with the "
-                    + "magnification spec = " + spec + " on the displayId = " + displayId);
-            return;
-        }
-        mMagnificationSpecInverseMatrix.set(displayId, inverseMatrix);
-    }
-
-    private void notifyWindowsChanged(@NonNull List<Integer> displayIdsForWindowsChanged) {
-        mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT);
-
-        for (int i = 0; i < displayIdsForWindowsChanged.size(); i++) {
-            mAccessibilityController.performComputeChangedWindowsNot(
-                    displayIdsForWindowsChanged.get(i), false);
-        }
-    }
-
-    private void forceUpdateWindows() {
-        final List<Integer> displayIdsForWindowsChanged = new ArrayList<>();
-
-        synchronized (mLock) {
-            for (int i = 0; i < mInputWindowHandlesOnDisplays.size(); i++) {
-                final int displayId = mInputWindowHandlesOnDisplays.keyAt(i);
-                displayIdsForWindowsChanged.add(displayId);
-            }
-        }
-        notifyWindowsChanged(displayIdsForWindowsChanged);
-    }
-
-    @GuardedBy("mLock")
-    private void releaseResources() {
-        mInputWindowHandlesOnDisplays.clear();
-        mMagnificationSpecInverseMatrix.clear();
-        mVisibleWindows.clear();
-        mDisplayInfos.clear();
-        mWindowsNotificationEnabled = false;
-        mHandler.removeCallbacksAndMessages(null);
-    }
-
-    private class MyHandler extends Handler {
-        public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1;
-        public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE = 2;
-        public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT = 3;
-
-        MyHandler(Looper looper) {
-            super(looper, null, false);
-        }
-
-        @Override
-        public void handleMessage(Message message) {
-            switch (message.what) {
-                case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
-                    final List<Integer> displayIdsForWindowsChanged = (List<Integer>) message.obj;
-                    notifyWindowsChanged(displayIdsForWindowsChanged);
-                } break;
-
-                case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE: {
-                    forceUpdateWindows();
-                } break;
-
-                case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT: {
-                    Slog.w(TAG, "Windows change within in 2 frames continuously over 500 ms "
-                            + "and notify windows changed immediately");
-                    mHandler.removeMessages(
-                            MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE);
-
-                    forceUpdateWindows();
-                } break;
-            }
-        }
-    }
-
-    /**
-     * This class represents information about a window from the
-     * surface flinger to the accessibility framework.
-     */
-    public static class AccessibilityWindow {
-        private static final Region TEMP_REGION = new Region();
-        private static final RectF TEMP_RECTF = new RectF();
-        // Data
-        private IWindow mWindow;
-        private int mDisplayId;
-        private int mFlags;
-        private int mType;
-        private int mPrivateFlags;
-        private boolean mIsPIPMenu;
-        private boolean mIsFocused;
-        private boolean mShouldMagnify;
-        private boolean mIgnoreDuetoRecentsAnimation;
-        private boolean mIsTrustedOverlay;
-        private final Region mTouchableRegionInScreen = new Region();
-        private final Region mTouchableRegionInWindow = new Region();
-        private final Region mLetterBoxBounds = new Region();
-        private WindowInfo mWindowInfo;
-
-        /**
-         * Returns the instance after initializing the internal data.
-         * @param service The window manager service.
-         * @param inputWindowHandle The window from the surface flinger.
-         * @param inverseMatrix The magnification spec inverse matrix.
-         */
-        public static AccessibilityWindow initializeData(WindowManagerService service,
-                InputWindowHandle inputWindowHandle, Matrix inverseMatrix, IBinder pipIBinder,
-                Matrix displayMatrix) {
-            final IWindow window = inputWindowHandle.getWindow();
-            final WindowState windowState = window != null ? service.mWindowMap.get(
-                    window.asBinder()) : null;
-
-            final AccessibilityWindow instance = new AccessibilityWindow();
-
-            instance.mWindow = inputWindowHandle.getWindow();
-            instance.mDisplayId = inputWindowHandle.displayId;
-            instance.mFlags = inputWindowHandle.layoutParamsFlags;
-            instance.mType = inputWindowHandle.layoutParamsType;
-            instance.mIsPIPMenu = inputWindowHandle.getWindow().asBinder().equals(pipIBinder);
-
-            // TODO (b/199357848): gets the private flag of the window from other way.
-            instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0;
-            // TODO (b/199358208) : using new way to implement the focused window.
-            instance.mIsFocused = windowState != null && windowState.isFocused();
-            instance.mShouldMagnify = windowState == null || windowState.shouldMagnify();
-
-            final RecentsAnimationController controller = service.getRecentsAnimationController();
-            instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null
-                    && controller.shouldIgnoreForAccessibility(windowState);
-            instance.mIsTrustedOverlay = inputWindowHandle.trustedOverlay;
-
-            // TODO (b/199358388) : gets the letterbox bounds of the window from other way.
-            if (windowState != null && windowState.areAppWindowBoundsLetterboxed()) {
-                getLetterBoxBounds(windowState, instance.mLetterBoxBounds);
-            }
-
-            final Rect windowFrame = new Rect(inputWindowHandle.frameLeft,
-                    inputWindowHandle.frameTop, inputWindowHandle.frameRight,
-                    inputWindowHandle.frameBottom);
-            getTouchableRegionInWindow(instance.mShouldMagnify, inputWindowHandle.touchableRegion,
-                    instance.mTouchableRegionInWindow, windowFrame, inverseMatrix, displayMatrix);
-            getUnMagnifiedTouchableRegion(instance.mShouldMagnify,
-                    inputWindowHandle.touchableRegion, instance.mTouchableRegionInScreen,
-                    inverseMatrix, displayMatrix);
-            instance.mWindowInfo = windowState != null
-                    ? windowState.getWindowInfo() : getWindowInfoForWindowlessWindows(instance);
-
-            return instance;
-        }
-
-        /**
-         * Returns the touchable region in the screen.
-         * @param outRegion The touchable region.
-         */
-        public void getTouchableRegionInScreen(Region outRegion) {
-            outRegion.set(mTouchableRegionInScreen);
-        }
-
-        /**
-         * Returns the touchable region in the window.
-         * @param outRegion The touchable region.
-         */
-        public void getTouchableRegionInWindow(Region outRegion) {
-            outRegion.set(mTouchableRegionInWindow);
-        }
-
-        /**
-         * @return the layout parameter flag {@link android.view.WindowManager.LayoutParams#flags}.
-         */
-        public int getFlags() {
-            return mFlags;
-        }
-
-        /**
-         * @return the layout parameter type {@link android.view.WindowManager.LayoutParams#type}.
-         */
-        public int getType() {
-            return mType;
-        }
-
-        /**
-         * @return the layout parameter private flag
-         * {@link android.view.WindowManager.LayoutParams#privateFlags}.
-         */
-        public int getPrivateFlag() {
-            return mPrivateFlags;
-        }
-
-        /**
-         * @return the windowInfo {@link WindowInfo}.
-         */
-        public WindowInfo getWindowInfo() {
-            return mWindowInfo;
-        }
-
-        /**
-         * Gets the letter box bounds if activity bounds are letterboxed
-         * or letterboxed for display cutout.
-         *
-         * @return {@code true} there's a letter box bounds.
-         */
-        public Boolean setLetterBoxBoundsIfNeeded(Region outBounds) {
-            if (mLetterBoxBounds.isEmpty()) {
-                return false;
-            }
-
-            outBounds.set(mLetterBoxBounds);
-            return true;
-        }
-
-        /**
-         * @return true if this window should be magnified.
-         */
-        public boolean shouldMagnify() {
-            return mShouldMagnify;
-        }
-
-        /**
-         * @return true if this window is focused.
-         */
-        public boolean isFocused() {
-            return mIsFocused;
-        }
-
-        /**
-         * @return true if it's running the recent animation but not the target app.
-         */
-        public boolean ignoreRecentsAnimationForAccessibility() {
-            return mIgnoreDuetoRecentsAnimation;
-        }
-
-        /**
-         * @return true if this window is the trusted overlay.
-         */
-        public boolean isTrustedOverlay() {
-            return mIsTrustedOverlay;
-        }
-
-        /**
-         * @return true if this window is the navigation bar with the gesture mode.
-         */
-        public boolean isUntouchableNavigationBar() {
-            if (mType != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) {
-                return false;
-            }
-
-            return mTouchableRegionInScreen.isEmpty();
-        }
-
-        /**
-         * @return true if this window is PIP menu.
-         */
-        public boolean isPIPMenu() {
-            return mIsPIPMenu;
-        }
-
-        private static void getTouchableRegionInWindow(boolean shouldMagnify, Region inRegion,
-                Region outRegion, Rect frame, Matrix inverseMatrix, Matrix displayMatrix) {
-            // Some modal windows, like the activity with Theme.dialog, has the full screen
-            // as its touchable region, but its window frame is smaller than the touchable
-            // region. The region we report should be the touchable area in the window frame
-            // for the consistency and match developers expectation.
-            // So we need to make the intersection between the frame and touchable region to
-            // obtain the real touch region in the screen.
-            Region touchRegion = TEMP_REGION;
-            touchRegion.set(inRegion);
-            touchRegion.op(frame, Region.Op.INTERSECT);
-
-            getUnMagnifiedTouchableRegion(shouldMagnify, touchRegion, outRegion, inverseMatrix,
-                    displayMatrix);
-        }
-
-        /**
-         * Gets the un-magnified touchable region. If this window can be magnified and magnifying,
-         * we will transform the input touchable region by applying the inverse matrix of the
-         * magnification spec to get the un-magnified touchable region.
-         * @param shouldMagnify The window can be magnified.
-         * @param inRegion The touchable region of this window.
-         * @param outRegion The un-magnified touchable region of this window.
-         * @param inverseMatrix The inverse matrix of the magnification spec.
-         * @param displayMatrix The display transform matrix which takes display coordinates to
-         *                      logical display coordinates.
-         */
-        private static void getUnMagnifiedTouchableRegion(boolean shouldMagnify, Region inRegion,
-                Region outRegion, Matrix inverseMatrix, Matrix displayMatrix) {
-            if ((!shouldMagnify || inverseMatrix.isIdentity()) && displayMatrix.isIdentity()) {
-                outRegion.set(inRegion);
-                return;
-            }
-
-            forEachRect(inRegion, rect -> {
-                // Move to origin as all transforms are captured by the matrix.
-                RectF windowFrame = TEMP_RECTF;
-                windowFrame.set(rect);
-
-                inverseMatrix.mapRect(windowFrame);
-                displayMatrix.mapRect(windowFrame);
-                // Union all rects.
-                outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
-                        (int) windowFrame.right, (int) windowFrame.bottom));
-            });
-        }
-
-        private static WindowInfo getWindowInfoForWindowlessWindows(AccessibilityWindow window) {
-            WindowInfo windowInfo = WindowInfo.obtain();
-            windowInfo.displayId = window.mDisplayId;
-            windowInfo.type = window.mType;
-            windowInfo.token = window.mWindow.asBinder();
-            windowInfo.hasFlagWatchOutsideTouch = (window.mFlags
-                    & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0;
-            windowInfo.inPictureInPicture = false;
-
-            // There only are two windowless windows now, one is split window, and the other
-            // one is PIP.
-            if (windowInfo.type == TYPE_DOCK_DIVIDER) {
-                windowInfo.title = "Splitscreen Divider";
-            } else if (window.mIsPIPMenu) {
-                windowInfo.title = "Picture-in-Picture menu";
-            }
-            return windowInfo;
-        }
-
-        private static void getLetterBoxBounds(WindowState windowState, Region outRegion) {
-            final Rect letterboxInsets = windowState.mActivityRecord.getLetterboxInsets();
-            final Rect nonLetterboxRect = windowState.getBounds();
-
-            nonLetterboxRect.inset(letterboxInsets);
-            outRegion.set(windowState.getBounds());
-            outRegion.op(nonLetterboxRect, Region.Op.DIFFERENCE);
-        }
-
-        @Override
-        public String toString() {
-            String builder = "A11yWindow=[" + mWindow.asBinder()
-                    + ", displayId=" + mDisplayId
-                    + ", flag=0x" + Integer.toHexString(mFlags)
-                    + ", type=" + mType
-                    + ", privateFlag=0x" + Integer.toHexString(mPrivateFlags)
-                    + ", focused=" + mIsFocused
-                    + ", shouldMagnify=" + mShouldMagnify
-                    + ", ignoreDuetoRecentsAnimation=" + mIgnoreDuetoRecentsAnimation
-                    + ", isTrustedOverlay=" + mIsTrustedOverlay
-                    + ", regionInScreen=" + mTouchableRegionInScreen
-                    + ", touchableRegion=" + mTouchableRegionInWindow
-                    + ", letterBoxBounds=" + mLetterBoxBounds
-                    + ", isPIPMenu=" + mIsPIPMenu
-                    + ", windowInfo=" + mWindowInfo
-                    + "]";
-
-            return builder;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 1bb9ca7..48dd2f4 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -55,6 +55,7 @@
             FIRST_ORDERED_ID,
             COMMUNAL_MODE_ORDERED_ID,
             PERMISSION_POLICY_ORDERED_ID,
+            VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
             LAST_ORDERED_ID // Update this when adding new ids
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -76,10 +77,16 @@
     public static final int PERMISSION_POLICY_ORDERED_ID = 2;
 
     /**
+     * The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService}
+     * interceptor.
+     */
+    public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3;
+
+    /**
      * The final id, used by the framework to determine the valid range of ids. Update this when
      * adding new ids.
      */
-    static final int LAST_ORDERED_ID = PERMISSION_POLICY_ORDERED_ID;
+    static final int LAST_ORDERED_ID = VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
 
     /**
      * Data class for storing the various arguments needed for activity interception.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c929cbb..c2765db 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1546,10 +1546,17 @@
     void onDisplayChanged(DisplayContent dc) {
         DisplayContent prevDc = mDisplayContent;
         super.onDisplayChanged(dc);
-        if (prevDc == null || prevDc == mDisplayContent) {
+        if (prevDc == mDisplayContent) {
             return;
         }
 
+        mDisplayContent.onRunningActivityChanged();
+
+        if (prevDc == null) {
+            return;
+        }
+        prevDc.onRunningActivityChanged();
+
         // TODO(b/169035022): move to a more-appropriate place.
         mTransitionController.collect(this);
         if (prevDc.mOpeningApps.remove(this)) {
@@ -1623,6 +1630,11 @@
         }
         // Trigger TaskInfoChanged to update the camera compat UI.
         getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+        // TaskOrganizerController#onTaskInfoChanged adds pending task events to the queue waiting
+        // for the surface placement to be ready. So need to trigger surface placement to dispatch
+        // events to avoid stale state for the camera compat control.
+        getDisplayContent().setLayoutNeeded();
+        mWmService.mWindowPlacerLocked.performSurfacePlacement();
     }
 
     void updateCameraCompatStateFromUser(@CameraCompatControlState int state) {
@@ -3900,6 +3912,7 @@
 
         // Reset the last saved PiP snap fraction on removal.
         mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
+        mDisplayContent.onRunningActivityChanged();
         mWmService.mEmbeddedWindowController.onActivityRemoved(this);
         mRemovingFromDisplay = false;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 9353f6d..1681348 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -26,6 +26,7 @@
 import android.os.IBinder;
 import android.os.InputConstants;
 import android.os.Looper;
+import android.os.Process;
 import android.util.Slog;
 import android.view.InputChannel;
 import android.view.InputEvent;
@@ -113,14 +114,13 @@
     }
 
     private InputWindowHandle createInputWindowHandle() {
-        InputWindowHandle inputWindowHandle = new InputWindowHandle(
-                mActivityRecord.getInputApplicationHandle(false),
+        InputWindowHandle inputWindowHandle = new InputWindowHandle(null,
                 mActivityRecord.getDisplayId());
         inputWindowHandle.replaceTouchableRegionWithCrop(
                 mActivityRecord.getParentSurfaceControl());
         inputWindowHandle.name = mName;
-        inputWindowHandle.ownerUid = mActivityRecord.getUid();
-        inputWindowHandle.ownerPid = mActivityRecord.getPid();
+        inputWindowHandle.ownerUid = Process.myUid();
+        inputWindowHandle.ownerPid = Process.myPid();
         inputWindowHandle.layoutParamsFlags =
                 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3cecce2..9d53e5a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -364,6 +364,17 @@
     /** Check if placing task or activity on specified display is allowed. */
     boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid,
             ActivityInfo activityInfo) {
+        return canPlaceEntityOnDisplay(displayId, callingPid, callingUid, null /* task */,
+                activityInfo);
+    }
+
+    boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid, Task task) {
+        return canPlaceEntityOnDisplay(displayId, callingPid, callingUid, task,
+                null /* activityInfo */);
+    }
+
+    private boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid,
+            Task task, ActivityInfo activityInfo) {
         if (displayId == DEFAULT_DISPLAY) {
             // No restrictions for the default display.
             return true;
@@ -372,12 +383,31 @@
             // Can't launch on secondary displays if feature is not supported.
             return false;
         }
+
         if (!isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, displayId, activityInfo)) {
             // Can't place activities to a display that has restricted launch rules.
             // In this case the request should be made by explicitly adding target display id and
             // by caller with corresponding permissions. See #isCallerAllowedToLaunchOnDisplay().
             return false;
         }
+
+        final DisplayContent displayContent =
+                mRootWindowContainer.getDisplayContentOrCreate(displayId);
+        if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+            final ArrayList<ActivityInfo> activities = new ArrayList<>();
+            if (activityInfo != null) {
+                activities.add(activityInfo);
+            }
+            if (task != null) {
+                task.forAllActivities((r) -> {
+                    activities.add(r.info);
+                });
+            }
+            if (!displayContent.mDwpcHelper.canContainActivities(activities)) {
+                return false;
+            }
+        }
+
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4fc591d..d91f48e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -222,7 +222,6 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
-import android.window.DisplayWindowPolicyController;
 import android.window.IDisplayAreaOrganizer;
 import android.window.TransitionRequestInfo;
 
@@ -698,12 +697,11 @@
     boolean mDontMoveToTop;
 
     /**
-     * The policy controller of the windows that can be displayed on the virtual display.
+     * The helper of policy controller.
      *
-     * @see DisplayWindowPolicyController
+     * @see DisplayWindowPolicyControllerHelper
      */
-    @Nullable
-    DisplayWindowPolicyController mDisplayWindowPolicyController;
+    DisplayWindowPolicyControllerHelper mDwpcHelper;
 
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
@@ -2739,8 +2737,7 @@
                 mDisplayInfo.copyFrom(newDisplayInfo);
             }
 
-            mDisplayWindowPolicyController =
-                    displayManagerInternal.getDisplayWindowPolicyController(mDisplayId);
+            mDwpcHelper = new DisplayWindowPolicyControllerHelper(this);
         }
 
         updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
@@ -3453,10 +3450,7 @@
         mInputMonitor.dump(pw, "  ");
         pw.println();
         mInsetsStateController.dump(prefix, pw);
-        if (mDisplayWindowPolicyController != null) {
-            pw.println();
-            mDisplayWindowPolicyController.dump(prefix, pw);
-        }
+        mDwpcHelper.dump(prefix, pw);
     }
 
     @Override
@@ -3684,6 +3678,11 @@
         return true;
     }
 
+    /** Update the top activity and the uids of non-finishing activity */
+    void onRunningActivityChanged() {
+        mDwpcHelper.onRunningActivityChanged();
+    }
+
     /** Called when the focused {@link TaskDisplayArea} on this display may have changed. */
     void onLastFocusedTaskDisplayAreaChanged(@Nullable TaskDisplayArea taskDisplayArea) {
         // Only record the TaskDisplayArea that handles orientation request.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
new file mode 100644
index 0000000..60d2a5d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.window.DisplayWindowPolicyController;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+class DisplayWindowPolicyControllerHelper {
+
+    private final DisplayContent mDisplayContent;
+
+    /**
+     * The policy controller of the windows that can be displayed on the virtual display.
+     *
+     * @see DisplayWindowPolicyController
+     */
+    @Nullable
+    private DisplayWindowPolicyController mDisplayWindowPolicyController;
+
+    /**
+     * The top non-finishing activity of this display.
+     */
+    private ActivityRecord mTopRunningActivity = null;
+
+    /**
+     * All the uids of non-finishing activity on this display.
+     * @see DisplayWindowPolicyController#onRunningAppsChanged(ArraySet)
+     */
+    private ArraySet<Integer> mRunningUid = new ArraySet<>();
+
+    DisplayWindowPolicyControllerHelper(DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+        mDisplayWindowPolicyController = mDisplayContent.mWmService.mDisplayManagerInternal
+                .getDisplayWindowPolicyController(mDisplayContent.mDisplayId);
+    }
+
+    /**
+     * Return {@code true} if there is DisplayWindowPolicyController.
+     */
+    public boolean hasController() {
+        return mDisplayWindowPolicyController != null;
+    }
+
+    /**
+     * @see DisplayWindowPolicyController#canContainActivities(List)
+     */
+    public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
+        if (mDisplayWindowPolicyController == null) {
+            return true;
+        }
+        return mDisplayWindowPolicyController.canContainActivities(activities);
+    }
+
+    /**
+     * @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int)
+     */
+    boolean keepActivityOnWindowFlagsChanged(ActivityInfo aInfo, int flagChanges,
+            int privateFlagChanges) {
+        if (mDisplayWindowPolicyController == null) {
+            return true;
+        }
+
+        if (!mDisplayWindowPolicyController.isInterestedWindowFlags(
+                flagChanges, privateFlagChanges)) {
+            return true;
+        }
+
+        return mDisplayWindowPolicyController.keepActivityOnWindowFlagsChanged(
+                aInfo, flagChanges, privateFlagChanges);
+    }
+
+    /** Update the top activity and the uids of non-finishing activity */
+    void onRunningActivityChanged() {
+        if (mDisplayWindowPolicyController == null) {
+            return;
+        }
+
+        // Update top activity.
+        ActivityRecord topActivity = mDisplayContent.getTopActivity(false /* includeFinishing */,
+                true /* includeOverlays */);
+        if (topActivity != mTopRunningActivity) {
+            mTopRunningActivity = topActivity;
+            mDisplayWindowPolicyController.onTopActivityChanged(
+                    topActivity == null ? null : topActivity.info.getComponentName(),
+                    topActivity == null
+                            ? UserHandle.USER_NULL : topActivity.info.applicationInfo.uid);
+        }
+
+        // Update running uid.
+        final boolean[] notifyChanged = {false};
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        mDisplayContent.forAllActivities((r) -> {
+            if (!r.finishing) {
+                notifyChanged[0] |= runningUids.add(r.getUid());
+            }
+        });
+
+        // We need to compare the size because if it is the following case, we can't know the
+        // existence of 3 in the forAllActivities() loop.
+        // Old set: 1,2,3
+        // New set: 1,2
+        if (notifyChanged[0] || (mRunningUid.size() != runningUids.size())) {
+            mRunningUid = runningUids;
+            mDisplayWindowPolicyController.onRunningAppsChanged(runningUids);
+        }
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        if (mDisplayWindowPolicyController != null) {
+            pw.println();
+            mDisplayWindowPolicyController.dump(prefix, pw);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/OverlayHost.java b/services/core/java/com/android/server/wm/OverlayHost.java
new file mode 100644
index 0000000..14f8983
--- /dev/null
+++ b/services/core/java/com/android/server/wm/OverlayHost.java
@@ -0,0 +1,129 @@
+/*
+ * 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.wm;
+
+import android.content.res.Configuration;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to assist WindowContainer in the hosting of
+ * SurfacePackage based overlays. Manages overlays inside
+ * one parent control, and manages the lifetime of that parent control
+ * in order to obscure details from WindowContainer.
+ *
+ * Also handles multiplexing of event dispatch and tracking of overlays
+ * to make things easier for WindowContainer.
+ */
+class OverlayHost {
+    // Lazily initialized when required
+    SurfaceControl mSurfaceControl;
+    final ArrayList<SurfaceControlViewHost.SurfacePackage> mOverlays = new ArrayList<>();
+    final WindowManagerService mWmService;
+
+    OverlayHost(WindowManagerService wms) {
+        mWmService = wms;
+    }
+
+    void requireOverlaySurfaceControl() {
+        if (mSurfaceControl == null) {
+            final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null)
+                .setContainerLayer()
+                .setHidden(true)
+                .setName("Overlay Host Leash");
+
+            mSurfaceControl = b.build();
+        }
+    }
+
+    void setParent(SurfaceControl.Transaction t, SurfaceControl newParent) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        t.reparent(mSurfaceControl, newParent);
+        if (newParent != null) {
+            t.show(mSurfaceControl);
+        } else {
+            t.hide(mSurfaceControl);
+        }
+    }
+
+    void setLayer(SurfaceControl.Transaction t, int layer) {
+        if (mSurfaceControl != null) {
+            t.setLayer(mSurfaceControl, layer);
+        }
+    }
+
+    void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
+        requireOverlaySurfaceControl();
+        mOverlays.add(p);
+
+        SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
+        t.reparent(p.getSurfaceControl(), mSurfaceControl)
+            .show(p.getSurfaceControl());
+        setParent(t,currentParent);
+        t.apply();
+    }
+
+    boolean removeOverlay(SurfaceControlViewHost.SurfacePackage p) {
+        final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
+
+        for (int i = mOverlays.size() - 1; i >= 0; i--) {
+           SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+           if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+               mOverlays.remove(i);
+               t.reparent(l.getSurfaceControl(), null);
+               l.release();
+           }
+        }
+        t.apply();
+        return mOverlays.size() > 0;
+    }
+
+    void dispatchConfigurationChanged(Configuration c) {
+        for (int i = mOverlays.size() - 1; i >= 0; i--) {
+           SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+           try {
+               l.getRemoteInterface().onConfigurationChanged(c);
+           } catch (Exception e) {
+               removeOverlay(l);
+           }
+        }
+    }
+
+    private void dispatchDetachedFromWindow() {
+        for (int i = mOverlays.size() - 1; i >= 0; i--) {
+            SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+            try {
+                l.getRemoteInterface().onDispatchDetachedFromWindow();
+            } catch (Exception e) {
+                // Oh well we are tearing down anyway.
+            }
+            l.release();
+        }
+    }
+
+    void release() {
+        dispatchDetachedFromWindow();
+        mOverlays.clear();
+        final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
+        t.remove(mSurfaceControl).apply();
+        mSurfaceControl = null;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index f9d7b53..6ed59e9 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -25,14 +25,15 @@
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.SurfaceControl;
+import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.animation.Animation;
 
@@ -135,12 +136,47 @@
                 ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
-    @Nullable
-    IBinder getAccessibilityWindowToken() {
-        if (mAccessibilityWindow != null) {
-            return mAccessibilityWindow.asBinder();
+    WindowInfo getWindowInfo() {
+        if (mShellRootLayer != SHELL_ROOT_LAYER_DIVIDER
+                && mShellRootLayer != SHELL_ROOT_LAYER_PIP) {
+            return null;
         }
-        return null;
+        if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER
+                && !mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+            return null;
+        }
+        if (mShellRootLayer == SHELL_ROOT_LAYER_PIP
+                && mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask() == null) {
+            return null;
+        }
+        if (mAccessibilityWindow == null) {
+            return null;
+        }
+        WindowInfo windowInfo = WindowInfo.obtain();
+        windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId;
+        windowInfo.type = mToken.windowType;
+        windowInfo.layer = mToken.getWindowLayerFromType();
+        windowInfo.token = mAccessibilityWindow.asBinder();
+        windowInfo.focused = false;
+        windowInfo.hasFlagWatchOutsideTouch = false;
+        final Rect regionRect = new Rect();
+
+
+        // DividerView
+        if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER) {
+            windowInfo.inPictureInPicture = false;
+            mDisplayContent.getDockedDividerController().getTouchRegion(regionRect);
+            windowInfo.regionInScreen.set(regionRect);
+            windowInfo.title = "Splitscreen Divider";
+        }
+        // PipMenuView
+        if (mShellRootLayer == SHELL_ROOT_LAYER_PIP) {
+            windowInfo.inPictureInPicture = true;
+            mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask().getBounds(regionRect);
+            windowInfo.regionInScreen.set(regionRect);
+            windowInfo.title = "Picture-in-Picture menu";
+        }
+        return windowInfo;
     }
 
     void setAccessibilityWindow(IWindow window) {
@@ -161,5 +197,9 @@
                 mAccessibilityWindow = null;
             }
         }
+        if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
+            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
+                    mDisplayContent.getDisplayId());
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 43038ce..cfd1f6e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -125,6 +125,8 @@
 import static com.android.server.wm.TaskProto.SURFACE_WIDTH;
 import static com.android.server.wm.TaskProto.TASK_FRAGMENT;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
+import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.TASK;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
@@ -609,6 +611,8 @@
      */
     ActivityRecord mChildPipActivity;
 
+    boolean mLastSurfaceShowing = true;
+
     private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -1749,7 +1753,7 @@
      */
     boolean canBeLaunchedOnDisplay(int displayId) {
         return mTaskSupervisor.canPlaceEntityOnDisplay(displayId,
-                -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */);
+                -1 /* don't check PID */, -1 /* don't check UID */, this);
     }
 
     /**
@@ -3308,6 +3312,17 @@
         if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
             scheduleAnimation();
         }
+
+        // We intend to let organizer manage task visibility but it doesn't
+        // have enough information until we finish shell transitions.
+        // In the mean time we do an easy fix here.
+        final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS);
+        if (mSurfaceControl != null) {
+            if (show != mLastSurfaceShowing) {
+                getSyncTransaction().setVisibility(mSurfaceControl, show);
+            }
+        }
+        mLastSurfaceShowing = show;
     }
 
     @Override
@@ -5971,7 +5986,19 @@
     }
 
     void reparent(TaskDisplayArea newParent, boolean onTop) {
-        reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
+        if (newParent == null) {
+            throw new IllegalArgumentException("Task can't reparent to null " + this);
+        }
+
+        if (getParent() == newParent) {
+            throw new IllegalArgumentException("Task=" + this + " already child of " + newParent);
+        }
+
+        if (canBeLaunchedOnDisplay(newParent.getDisplayId())) {
+            reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
+        } else {
+            Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent);
+        }
     }
 
     void setLastRecentsAnimationTransaction(@NonNull PictureInPictureSurfaceTransaction transaction,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d133ca9..b681a96 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -398,8 +398,16 @@
             Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
                     + mResumedActivity + " to:" + r + " reason:" + reason);
         }
+        final ActivityRecord prevR = mResumedActivity;
         mResumedActivity = r;
         mTaskSupervisor.updateTopResumedActivityIfNeeded();
+        if (r == null && prevR.mDisplayContent != null
+                && prevR.mDisplayContent.getFocusedRootTask() == null) {
+            // Only need to notify DWPC when no activity will resume.
+            prevR.mDisplayContent.onRunningActivityChanged();
+        } else if (r != null) {
+            r.mDisplayContent.onRunningActivityChanged();
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c7fdefc..123ca88 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -22,6 +22,7 @@
 import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -497,6 +498,23 @@
         return null;
     }
 
+    private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
+            @NonNull PendingTaskFragmentEvent event) {
+        final TaskFragmentOrganizerState state =
+                mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
+        final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
+        final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo();
+        // Send an info changed callback if this event is for the last activities to finish in a
+        // Task so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. Otherwise,
+        // the Task may be removed before it becomes visible again to send this event because it no
+        // longer has activities. As a result, the organizer will never get this info changed event
+        // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
+        // has running activities.
+        return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
+                && task.topRunningActivity() == null && lastInfo != null
+                && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
+    }
+
     void dispatchPendingEvents() {
         if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
                 || mPendingTaskFragmentEvents.isEmpty()) {
@@ -510,7 +528,8 @@
             final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
             final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
             if (task != null && (task.lastActiveTime <= event.mDeferTime
-                    || !isTaskVisible(task, visibleTasks, invisibleTasks))) {
+                    || !(isTaskVisible(task, visibleTasks, invisibleTasks)
+                    || shouldSendEventWhenTaskInvisible(task, event)))) {
                 // Defer sending events to the TaskFragment until the host task is active again.
                 event.mDeferTime = task.lastActiveTime;
                 continue;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 61acb97..4006848 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -85,6 +85,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceSession;
 import android.view.TaskTransitionSpec;
@@ -312,6 +313,8 @@
 
     private final List<WindowContainerListener> mListeners = new ArrayList<>();
 
+    private OverlayHost mOverlayHost;
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mTransitionController = mWmService.mAtmService.getTransitionController();
@@ -341,6 +344,9 @@
         super.onConfigurationChanged(newParentConfig);
         updateSurfacePositionNonOrganized();
         scheduleAnimation();
+        if (mOverlayHost != null) {
+            mOverlayHost.dispatchConfigurationChanged(getConfiguration());
+        }
     }
 
     void reparent(WindowContainer newParent, int position) {
@@ -387,6 +393,8 @@
 
         if (mParent != null) {
             mParent.onChildAdded(this);
+        } else if (mSurfaceAnimator.hasLeash()) {
+            mSurfaceAnimator.cancelAnimation();
         }
         if (!mReparenting) {
             onSyncReparent(oldParent, mParent);
@@ -487,6 +495,11 @@
                 t.reparent(sc, mSurfaceControl);
             }
         }
+
+        if (mOverlayHost != null) {
+            mOverlayHost.setParent(t, mSurfaceControl);
+        }
+
         scheduleAnimation();
     }
 
@@ -632,6 +645,10 @@
             mLastSurfacePosition.set(0, 0);
             scheduleAnimation();
         }
+        if (mOverlayHost != null) {
+            mOverlayHost.release();
+            mOverlayHost = null;
+        }
 
         // This must happen after updating the surface so that sync transactions can be handled
         // properly.
@@ -2308,6 +2325,9 @@
                 wc.assignLayer(t, layer++);
             }
         }
+        if (mOverlayHost != null) {
+            mOverlayHost.setLayer(t, layer++);
+        }
     }
 
     void assignChildLayers() {
@@ -3570,4 +3590,18 @@
         void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
                 @AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
     }
+
+    void addOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
+        if (mOverlayHost == null) {
+            mOverlayHost = new OverlayHost(mWmService);
+        }
+        mOverlayHost.addOverlay(overlay, mSurfaceControl);
+    }
+
+    void removeOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
+        if (mOverlayHost != null && !mOverlayHost.removeOverlay(overlay)) {
+            mOverlayHost.release();
+            mOverlayHost = null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 62c674b..1ab191b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -32,6 +32,7 @@
 import android.view.InputChannel;
 import android.view.MagnificationSpec;
 import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControlViewHost;
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
 
@@ -767,4 +768,16 @@
      *         {@code false} otherwise.
      */
     public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken);
+
+    /**
+     * Internal methods for other parts of SystemServer to manage
+     * SurfacePackage based overlays on tasks.
+     *
+     * Callers prepare a view hierarchy with SurfaceControlViewHost
+     * and send the package to WM here. The remote view hierarchy will receive
+     * configuration change, lifecycle events, etc, forwarded over the
+     * ISurfaceControlViewHost interface inside the SurfacePackage.
+     */
+    public abstract void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay);
+    public abstract void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c239e68..e4216bf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -267,6 +267,7 @@
 import android.view.ScrollCaptureResponse;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.SurfaceSession;
 import android.view.TaskTransitionSpec;
 import android.view.View;
@@ -2245,6 +2246,15 @@
                     winAnimator.setColorSpaceAgnosticLocked((win.mAttrs.privateFlags
                             & WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
                 }
+                if (win.mActivityRecord != null
+                        && !displayContent.mDwpcHelper.keepActivityOnWindowFlagsChanged(
+                                win.mActivityRecord.info, flagChanges, privateFlagChanges)) {
+                    mH.sendMessage(mH.obtainMessage(H.REPARENT_TASK_TO_DEFAULT_DISPLAY,
+                            win.mActivityRecord.getTask()));
+                    Slog.w(TAG_WM, "Activity " + win.mActivityRecord + " window flag changed,"
+                            + " can't remain on display " + displayContent.getDisplayId());
+                    return 0;
+                }
             }
 
             if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility
@@ -5064,6 +5074,7 @@
         public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
         public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63;
         public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64;
+        public static final int REPARENT_TASK_TO_DEFAULT_DISPLAY = 65;
 
         /**
          * Used to denote that an integer field in a message will not be used.
@@ -5381,6 +5392,15 @@
                     }
                     break;
                 }
+                case REPARENT_TASK_TO_DEFAULT_DISPLAY: {
+                    synchronized (mGlobalLock) {
+                        Task task = (Task) msg.obj;
+                        task.reparent(mRoot.getDefaultTaskDisplayArea(), true /* onTop */);
+                        // Resume focusable root task after reparenting to another display area.
+                        task.resumeNextFocusAfterReparent();
+                    }
+                    break;
+                }
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG_WM, "handleMessage: exit");
@@ -7891,6 +7911,28 @@
         public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
             return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken);
         }
+
+        @Override
+        public void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) {
+            synchronized (mGlobalLock) {
+                final Task task = mRoot.getRootTask(taskId);
+                if (task == null) {
+                    throw new IllegalArgumentException("no task with taskId" + taskId);
+                }
+                task.addOverlay(overlay);
+            }
+        }
+
+        @Override
+        public void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) {
+            synchronized (mGlobalLock) {
+                final Task task = mRoot.getRootTask(taskId);
+                if (task == null) {
+                    throw new IllegalArgumentException("no task with taskId" + taskId);
+                }
+                task.removeOverlay(overlay);
+            }
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5bbe2cd..94d4a77 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4809,6 +4809,9 @@
         if (isAnimating()) {
             return;
         }
+        if (mWmService.mAccessibilityController.hasCallbacks()) {
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
+        }
 
         if (!isSelfOrAncestorWindowAnimatingExit()) {
             return;
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index 34ae469..5a7cee9 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -27,12 +27,16 @@
 using hardware::gnss::GnssData;
 using hardware::gnss::GnssMeasurement;
 using hardware::gnss::SatellitePvt;
+using GnssAgc = hardware::gnss::GnssData::GnssAgc;
 
 namespace {
 jclass class_arrayList;
 jclass class_clockInfo;
 jclass class_correlationVectorBuilder;
+jclass class_gnssAgc;
+jclass class_gnssAgcBuilder;
 jclass class_gnssMeasurementsEvent;
+jclass class_gnssMeasurementsEventBuilder;
 jclass class_gnssMeasurement;
 jclass class_gnssClock;
 jclass class_positionEcef;
@@ -47,7 +51,16 @@
 jmethodID method_correlationVectorBuilderSetMagnitude;
 jmethodID method_correlationVectorBuilderSetSamplingStartMeters;
 jmethodID method_correlationVectorBuilderSetSamplingWidthMeters;
-jmethodID method_gnssMeasurementsEventCtor;
+jmethodID method_gnssAgcBuilderCtor;
+jmethodID method_gnssAgcBuilderSetLevelDb;
+jmethodID method_gnssAgcBuilderSetConstellationType;
+jmethodID method_gnssAgcBuilderSetCarrierFrequencyHz;
+jmethodID method_gnssAgcBuilderBuild;
+jmethodID method_gnssMeasurementsEventBuilderCtor;
+jmethodID method_gnssMeasurementsEventBuilderSetClock;
+jmethodID method_gnssMeasurementsEventBuilderSetMeasurements;
+jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls;
+jmethodID method_gnssMeasurementsEventBuilderBuild;
 jmethodID method_gnssMeasurementsSetCorrelationVectors;
 jmethodID method_gnssMeasurementsSetSatellitePvt;
 jmethodID method_gnssClockCtor;
@@ -69,12 +82,55 @@
 void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz) {
     method_reportMeasurementData = env->GetMethodID(clazz, "reportMeasurementData",
                                                     "(Landroid/location/GnssMeasurementsEvent;)V");
+
+    // Initialize GnssMeasurement related classes and methods
     jclass gnssMeasurementsEventClass = env->FindClass("android/location/GnssMeasurementsEvent");
     class_gnssMeasurementsEvent = (jclass)env->NewGlobalRef(gnssMeasurementsEventClass);
-    method_gnssMeasurementsEventCtor =
-            env->GetMethodID(class_gnssMeasurementsEvent, "<init>",
-                             "(Landroid/location/GnssClock;[Landroid/location/GnssMeasurement;)V");
+    jclass gnssMeasurementsEventBuilderClass =
+            env->FindClass("android/location/GnssMeasurementsEvent$Builder");
+    class_gnssMeasurementsEventBuilder =
+            (jclass)env->NewGlobalRef(gnssMeasurementsEventBuilderClass);
+    method_gnssMeasurementsEventBuilderCtor =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "<init>", "()V");
+    method_gnssMeasurementsEventBuilderSetClock =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setClock",
+                             "(Landroid/location/GnssClock;)"
+                             "Landroid/location/GnssMeasurementsEvent$Builder;");
+    method_gnssMeasurementsEventBuilderSetMeasurements =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setMeasurements",
+                             "([Landroid/location/GnssMeasurement;)"
+                             "Landroid/location/GnssMeasurementsEvent$Builder;");
+    method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls",
+                             "([Landroid/location/GnssAutomaticGainControl;)"
+                             "Landroid/location/GnssMeasurementsEvent$Builder;");
+    method_gnssMeasurementsEventBuilderBuild =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "build",
+                             "()Landroid/location/GnssMeasurementsEvent;");
 
+    // Initialize GnssAgc related classes and methods
+    jclass gnssAgcClass = env->FindClass("android/location/GnssAutomaticGainControl");
+    class_gnssAgc = (jclass)env->NewGlobalRef(gnssAgcClass);
+    jclass gnssAgcBuilderClass =
+            env->FindClass("android/location/GnssAutomaticGainControl$Builder");
+    class_gnssAgcBuilder = (jclass)env->NewGlobalRef(gnssAgcBuilderClass);
+    method_gnssAgcBuilderCtor = env->GetMethodID(class_gnssAgcBuilder, "<init>", "()V");
+    method_gnssAgcBuilderSetLevelDb =
+            env->GetMethodID(class_gnssAgcBuilder, "setLevelDb",
+                             "(D)"
+                             "Landroid/location/GnssAutomaticGainControl$Builder;");
+    method_gnssAgcBuilderSetConstellationType =
+            env->GetMethodID(class_gnssAgcBuilder, "setConstellationType",
+                             "(I)"
+                             "Landroid/location/GnssAutomaticGainControl$Builder;");
+    method_gnssAgcBuilderSetCarrierFrequencyHz =
+            env->GetMethodID(class_gnssAgcBuilder, "setCarrierFrequencyHz",
+                             "(J)"
+                             "Landroid/location/GnssAutomaticGainControl$Builder;");
+    method_gnssAgcBuilderBuild = env->GetMethodID(class_gnssAgcBuilder, "build",
+                                                  "()Landroid/location/GnssAutomaticGainControl;");
+
+    // Initialize GnssMeasurement related classes and methods
     jclass gnssMeasurementClass = env->FindClass("android/location/GnssMeasurement");
     class_gnssMeasurement = (jclass)env->NewGlobalRef(gnssMeasurementClass);
     method_gnssMeasurementCtor = env->GetMethodID(class_gnssMeasurement, "<init>", "()V");
@@ -152,14 +208,25 @@
 }
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
-                        jobjectArray measurementArray) {
-    jobject gnssMeasurementsEvent =
-            env->NewObject(class_gnssMeasurementsEvent, method_gnssMeasurementsEventCtor, clock,
-                           measurementArray);
+                        jobjectArray measurementArray, jobjectArray gnssAgcArray) {
+    jobject gnssMeasurementsEventBuilderObject =
+            env->NewObject(class_gnssMeasurementsEventBuilder,
+                           method_gnssMeasurementsEventBuilderCtor);
+    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+                          method_gnssMeasurementsEventBuilderSetClock, clock);
+    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+                          method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray);
+    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+                          method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
+                          gnssAgcArray);
+    jobject gnssMeasurementsEventObject =
+            env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
+                                  method_gnssMeasurementsEventBuilderBuild);
 
-    env->CallVoidMethod(callbacksObj, method_reportMeasurementData, gnssMeasurementsEvent);
+    env->CallVoidMethod(callbacksObj, method_reportMeasurementData, gnssMeasurementsEventObject);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    env->DeleteLocalRef(gnssMeasurementsEvent);
+    env->DeleteLocalRef(gnssMeasurementsEventBuilderObject);
+    env->DeleteLocalRef(gnssMeasurementsEventObject);
 }
 
 template <class T_Measurement, class T_Flags>
@@ -289,9 +356,13 @@
     JavaObject gnssClockJavaObject(env, class_gnssClock, method_gnssClockCtor);
     translateGnssClock(env, data, gnssClockJavaObject);
     jobject clock = gnssClockJavaObject.get();
-
     jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
-    setMeasurementData(env, mCallbacksObj, clock, measurementArray);
+
+    jobjectArray gnssAgcArray = nullptr;
+    if (data.gnssAgcs.has_value()) {
+        gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
+    }
+    setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
 
     env->DeleteLocalRef(clock);
     env->DeleteLocalRef(measurementArray);
@@ -436,6 +507,38 @@
     return gnssMeasurementArray;
 }
 
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
+        JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+    if (agcs.size() == 0) {
+        return nullptr;
+    }
+
+    jobjectArray gnssAgcArray =
+            env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
+
+    for (uint16_t i = 0; i < agcs.size(); ++i) {
+        if (!agcs[i].has_value()) {
+            continue;
+        }
+        const GnssAgc& gnssAgc = agcs[i].value();
+
+        jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
+        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
+                              gnssAgc.agcLevelDb);
+        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType,
+                              (int)gnssAgc.constellation);
+        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz,
+                              gnssAgc.carrierFrequencyHz);
+        jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild);
+
+        env->SetObjectArrayElement(gnssAgcArray, i, agcObject);
+        env->DeleteLocalRef(agcBuilderObject);
+        env->DeleteLocalRef(agcObject);
+    }
+
+    return gnssAgcArray;
+}
+
 void GnssMeasurementCallbackAidl::translateGnssClock(JNIEnv* env, const GnssData& data,
                                                      JavaObject& object) {
     setElapsedRealtimeFields<ElapsedRealtime, ElapsedRealtime>(data.elapsedRealtime, object);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 32200fd..9b346312 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -48,7 +48,7 @@
 void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz);
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
-                        jobjectArray measurementArray);
+                        jobjectArray measurementArray, jobjectArray gnssAgcArray);
 
 class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
 public:
@@ -62,6 +62,8 @@
 
     jobjectArray translateAllGnssMeasurements(
             JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
+    jobjectArray translateAllGnssAgcs(
+            JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
 
     void translateAndSetGnssData(const hardware::gnss::GnssData& data);
 
@@ -139,7 +141,7 @@
     size_t count = getMeasurementCount(data);
     jobjectArray measurementArray =
             translateAllGnssMeasurements(env, data.measurements.data(), count);
-    setMeasurementData(env, mCallbacksObj, clock, measurementArray);
+    setMeasurementData(env, mCallbacksObj, clock, measurementArray, nullptr);
 
     env->DeleteLocalRef(clock);
     env->DeleteLocalRef(measurementArray);
diff --git a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
index 5a6275d..cc97b8f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
@@ -24,7 +24,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
@@ -226,7 +225,7 @@
     }
 
     private void expectWipeNonSystemUser() {
-        when(mUserManager.removeUserOrSetEphemeral(anyInt(), anyBoolean()))
+        when(mUserManager.removeUserWhenPossible(any(), anyBoolean()))
                 .thenReturn(UserManager.REMOVE_RESULT_REMOVED);
     }
 
@@ -266,7 +265,7 @@
     }
 
     private void verifyWipeNonSystemUser() {
-        verify(mUserManager).removeUserOrSetEphemeral(anyInt(), anyBoolean());
+        verify(mUserManager).removeUserWhenPossible(any(), anyBoolean());
     }
 
     private void setPendingResultForUser(int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
index edf6816..1a5888e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
@@ -183,7 +183,8 @@
                 mOverridesToRemoveByPackageConfigCaptor.getValue().packageNameToOverridesToRemove;
         Map<Long, PackageOverride> addedOverrides;
         assertThat(packageNameToAddedOverrides.keySet()).containsExactly(PACKAGE_1, PACKAGE_3);
-        assertThat(packageNameToRemovedOverrides.keySet()).containsExactly(PACKAGE_3, PACKAGE_4);
+        assertThat(packageNameToRemovedOverrides.keySet()).containsExactly(PACKAGE_2, PACKAGE_3,
+                PACKAGE_4);
         // Package 1
         addedOverrides = packageNameToAddedOverrides.get(PACKAGE_1).overrides;
         assertThat(addedOverrides).hasSize(3);
@@ -193,6 +194,9 @@
                 new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build());
         assertThat(addedOverrides.get(789L)).isEqualTo(
                 new PackageOverride.Builder().setEnabled(false).build());
+        // Package 2
+        assertThat(packageNameToRemovedOverrides.get(PACKAGE_2).changeIds).containsExactly(123L,
+                456L, 789L);
         // Package 3
         addedOverrides = packageNameToAddedOverrides.get(PACKAGE_3).overrides;
         assertThat(addedOverrides).hasSize(1);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
new file mode 100644
index 0000000..52d0494
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2018 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.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppGlobals;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.os.BatteryManagerInternal;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryControllerTest {
+    private static final int CALLING_UID = 1000;
+    private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+    private static final int SOURCE_USER_ID = 0;
+
+    private BatteryController mBatteryController;
+    private BroadcastReceiver mPowerReceiver;
+    private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
+    private int mSourceUid;
+
+    private MockitoSession mMockingSession;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BatteryManagerInternal mBatteryManagerInternal;
+    @Mock
+    private JobSchedulerService mJobSchedulerService;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(LocalServices.class)
+                .startMocking();
+
+        // Called in StateController constructor.
+        when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+        when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+        when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
+        // Called in BatteryController constructor.
+        doReturn(mBatteryManagerInternal)
+                .when(() -> LocalServices.getService(BatteryManagerInternal.class));
+        // Used in JobStatus.
+        doReturn(mPackageManagerInternal)
+                .when(() -> LocalServices.getService(PackageManagerInternal.class));
+
+        // Initialize real objects.
+        // Capture the listeners.
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        mBatteryController = new BatteryController(mJobSchedulerService);
+
+        verify(mContext).registerReceiver(receiverCaptor.capture(),
+                ArgumentMatchers.argThat(filter ->
+                        filter.hasAction(Intent.ACTION_POWER_CONNECTED)
+                                && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
+        mPowerReceiver = receiverCaptor.getValue();
+        try {
+            mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
+            // Need to do this since we're using a mock JS and not a real object.
+            doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE}))
+                    .when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
+        } catch (RemoteException e) {
+            fail(e.getMessage());
+        }
+        setPowerConnected(false);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void setBatteryNotLow(boolean notLow) {
+        doReturn(notLow).when(mJobSchedulerService).isBatteryNotLow();
+        synchronized (mBatteryController.mLock) {
+            mBatteryController.onBatteryStateChangedLocked();
+        }
+        waitForNonDelayedMessagesProcessed();
+    }
+
+    private void setCharging() {
+        doReturn(true).when(mJobSchedulerService).isBatteryCharging();
+        synchronized (mBatteryController.mLock) {
+            mBatteryController.onBatteryStateChangedLocked();
+        }
+        waitForNonDelayedMessagesProcessed();
+    }
+
+    private void setDischarging() {
+        doReturn(false).when(mJobSchedulerService).isBatteryCharging();
+        synchronized (mBatteryController.mLock) {
+            mBatteryController.onBatteryStateChangedLocked();
+        }
+        waitForNonDelayedMessagesProcessed();
+    }
+
+    private void setPowerConnected(boolean connected) {
+        Intent intent = new Intent(
+                connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED);
+        mPowerReceiver.onReceive(mContext, intent);
+    }
+
+    private void setUidBias(int uid, int bias) {
+        int prevBias = mJobSchedulerService.getUidBias(uid);
+        doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
+        synchronized (mBatteryController.mLock) {
+            mBatteryController.onUidBiasChangedLocked(uid, prevBias, bias);
+        }
+    }
+
+    private void trackJobs(JobStatus... jobs) {
+        for (JobStatus job : jobs) {
+            synchronized (mBatteryController.mLock) {
+                mBatteryController.maybeStartTrackingJobLocked(job, null);
+            }
+        }
+    }
+
+    private void waitForNonDelayedMessagesProcessed() {
+        JobSchedulerBackgroundThread.getHandler().runWithScissors(() -> {}, 15_000);
+    }
+
+    private JobInfo.Builder createBaseJobInfoBuilder(int jobId) {
+        return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestBatteryJobService"));
+    }
+
+    private JobInfo.Builder createBaseJobInfoBuilder(int jobId, String pkgName) {
+        return new JobInfo.Builder(jobId, new ComponentName(pkgName, "TestBatteryJobService"));
+    }
+
+    private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
+            JobInfo jobInfo) {
+        JobStatus js = JobStatus.createFromJobInfo(
+                jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+        js.serviceInfo = mock(ServiceInfo.class);
+        // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+        js.setStandbyBucket(FREQUENT_INDEX);
+        return js;
+    }
+
+    @Test
+    public void testBatteryNotLow() {
+        JobStatus job1 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(1).setRequiresBatteryNotLow(true).build());
+        JobStatus job2 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(2).setRequiresBatteryNotLow(true).build());
+
+        setBatteryNotLow(false);
+        trackJobs(job1);
+        assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+
+        setBatteryNotLow(true);
+        assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+
+        trackJobs(job2);
+        assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+    }
+
+    @Test
+    public void testCharging_BatteryNotLow() {
+        JobStatus job1 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(1)
+                        .setRequiresCharging(true)
+                        .setRequiresBatteryNotLow(true).build());
+        JobStatus job2 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(2)
+                        .setRequiresCharging(true)
+                        .setRequiresBatteryNotLow(false).build());
+
+        setBatteryNotLow(true);
+        setDischarging();
+        trackJobs(job1, job2);
+        assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+        setCharging();
+        assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+    }
+
+    @Test
+    public void testTopPowerConnectedExemption() {
+        final int uid1 = mSourceUid;
+        final int uid2 = mSourceUid + 1;
+        final int uid3 = mSourceUid + 2;
+        JobStatus jobFg = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1,
+                createBaseJobInfoBuilder(1).setRequiresCharging(true).build());
+        JobStatus jobFgRunner = createJobStatus("testTopPowerConnectedExemption",
+                SOURCE_PACKAGE, uid1,
+                createBaseJobInfoBuilder(2).setRequiresCharging(true).build());
+        JobStatus jobFgLow = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1,
+                createBaseJobInfoBuilder(3)
+                        .setRequiresCharging(true)
+                        .setPriority(JobInfo.PRIORITY_LOW)
+                        .build());
+        JobStatus jobBg = createJobStatus("testTopPowerConnectedExemption",
+                "some.background.app", uid2,
+                createBaseJobInfoBuilder(4, "some.background.app")
+                        .setRequiresCharging(true)
+                        .build());
+        JobStatus jobLateFg = createJobStatus("testTopPowerConnectedExemption",
+                "switch.to.fg", uid3,
+                createBaseJobInfoBuilder(5, "switch.to.fg").setRequiresCharging(true).build());
+        JobStatus jobLateFgLow = createJobStatus("testTopPowerConnectedExemption",
+                "switch.to.fg", uid3,
+                createBaseJobInfoBuilder(6, "switch.to.fg")
+                        .setRequiresCharging(true)
+                        .setPriority(JobInfo.PRIORITY_MIN)
+                        .build());
+
+        setBatteryNotLow(false);
+        setDischarging();
+        setUidBias(uid1, JobInfo.BIAS_TOP_APP);
+        setUidBias(uid2, JobInfo.BIAS_DEFAULT);
+        setUidBias(uid3, JobInfo.BIAS_DEFAULT);
+
+        // Jobs are scheduled when power isn't connected.
+        setPowerConnected(false);
+        trackJobs(jobFg, jobFgLow, jobBg, jobLateFg, jobLateFgLow);
+        assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+        // Power is connected. TOP app should be allowed to start job DEFAULT+ jobs.
+        setPowerConnected(true);
+        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+        // Test that newly scheduled job of TOP app is correctly allowed to run.
+        trackJobs(jobFgRunner);
+        assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+        // Switch top app. New TOP app should be allowed to run job and the running job of
+        // previously TOP app should be allowed to continue to run.
+        synchronized (mBatteryController.mLock) {
+            mBatteryController.prepareForExecutionLocked(jobFgRunner);
+        }
+        setUidBias(uid1, JobInfo.BIAS_DEFAULT);
+        setUidBias(uid2, JobInfo.BIAS_DEFAULT);
+        setUidBias(uid3, JobInfo.BIAS_TOP_APP);
+        assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertTrue(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+        setPowerConnected(false);
+        assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+        assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 95912b2..d741459 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -200,8 +200,10 @@
     }
 
     private void setUidBias(int uid, int bias) {
+        int prevBias = mJobSchedulerService.getUidBias(uid);
+        doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
         synchronized (mPrefetchController.mLock) {
-            mPrefetchController.onUidBiasChangedLocked(uid, bias);
+            mPrefetchController.onUidBiasChangedLocked(uid, prevBias, bias);
         }
     }
 
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 202a54d..587447a 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -95,6 +95,7 @@
     <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
     <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
     <uses-permission android:name="android.permission.KILL_UID"/>
+    <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
     <uses-permission
         android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
new file mode 100644
index 0000000..d4bac2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.biometrics.sensors;
+
+import static android.testing.TestableLooper.RunWithLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class BiometricSchedulerOperationTest {
+
+    public interface FakeHal {}
+    public abstract static class InterruptableMonitor<T>
+            extends HalClientMonitor<T> implements  Interruptable {
+        public InterruptableMonitor() {
+            super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
+        }
+    }
+
+    @Mock
+    private InterruptableMonitor<FakeHal> mClientMonitor;
+    @Mock
+    private BaseClientMonitor.Callback mClientCallback;
+    @Mock
+    private FakeHal mHal;
+    @Captor
+    ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback;
+
+    private Handler mHandler;
+    private BiometricSchedulerOperation mOperation;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new Handler(TestableLooper.get(this).getLooper());
+        mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback);
+    }
+
+    @Test
+    public void testStartWithCookie() {
+        final int cookie = 200;
+        when(mClientMonitor.getCookie()).thenReturn(cookie);
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        assertThat(mOperation.isReadyToStart()).isEqualTo(cookie);
+        assertThat(mOperation.isStarted()).isFalse();
+        assertThat(mOperation.isCanceling()).isFalse();
+        assertThat(mOperation.isFinished()).isFalse();
+
+        final boolean started = mOperation.startWithCookie(
+                mock(BaseClientMonitor.Callback.class), cookie);
+
+        assertThat(started).isTrue();
+        verify(mClientMonitor).start(mStartCallback.capture());
+        mStartCallback.getValue().onClientStarted(mClientMonitor);
+        assertThat(mOperation.isStarted()).isTrue();
+    }
+
+    @Test
+    public void testNoStartWithoutCookie() {
+        final int goodCookie = 20;
+        final int badCookie = 22;
+        when(mClientMonitor.getCookie()).thenReturn(goodCookie);
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie);
+        final boolean started = mOperation.startWithCookie(
+                mock(BaseClientMonitor.Callback.class), badCookie);
+
+        assertThat(started).isFalse();
+        assertThat(mOperation.isStarted()).isFalse();
+        assertThat(mOperation.isCanceling()).isFalse();
+        assertThat(mOperation.isFinished()).isFalse();
+    }
+
+    @Test
+    public void startsWhenReadyAndHalAvailable() {
+        when(mClientMonitor.getCookie()).thenReturn(0);
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+        mOperation.start(cb);
+        verify(mClientMonitor).start(mStartCallback.capture());
+        mStartCallback.getValue().onClientStarted(mClientMonitor);
+
+        assertThat(mOperation.isStarted()).isTrue();
+        assertThat(mOperation.isCanceling()).isFalse();
+        assertThat(mOperation.isFinished()).isFalse();
+
+        verify(mClientCallback).onClientStarted(eq(mClientMonitor));
+        verify(cb).onClientStarted(eq(mClientMonitor));
+        verify(mClientCallback, never()).onClientFinished(any(), anyBoolean());
+        verify(cb, never()).onClientFinished(any(), anyBoolean());
+
+        mStartCallback.getValue().onClientFinished(mClientMonitor, true);
+
+        assertThat(mOperation.isFinished()).isTrue();
+        assertThat(mOperation.isCanceling()).isFalse();
+        verify(mClientMonitor).destroy();
+        verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
+    }
+
+    @Test
+    public void startFailsWhenReadyButHalNotAvailable() {
+        when(mClientMonitor.getCookie()).thenReturn(0);
+        when(mClientMonitor.getFreshDaemon()).thenReturn(null);
+
+        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+        mOperation.start(cb);
+        verify(mClientMonitor, never()).start(any());
+
+        assertThat(mOperation.isStarted()).isFalse();
+        assertThat(mOperation.isCanceling()).isFalse();
+        assertThat(mOperation.isFinished()).isTrue();
+
+        verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor));
+        verify(cb, never()).onClientStarted(eq(mClientMonitor));
+        verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false));
+        verify(cb).onClientFinished(eq(mClientMonitor), eq(false));
+    }
+
+    @Test
+    public void doesNotStartWithCookie() {
+        when(mClientMonitor.getCookie()).thenReturn(9);
+        assertThrows(IllegalStateException.class,
+                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+    }
+
+    @Test
+    public void cannotRestart() {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        mOperation.start(mock(BaseClientMonitor.Callback.class));
+
+        assertThrows(IllegalStateException.class,
+                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+    }
+
+    @Test
+    public void abortsNotRunning() {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        mOperation.abort();
+
+        assertThat(mOperation.isFinished()).isTrue();
+        verify(mClientMonitor).unableToStart();
+        verify(mClientMonitor).destroy();
+        assertThrows(IllegalStateException.class,
+                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+    }
+
+    @Test
+    public void cannotAbortRunning() {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        mOperation.start(mock(BaseClientMonitor.Callback.class));
+
+        assertThrows(IllegalStateException.class, () -> mOperation.abort());
+    }
+
+    @Test
+    public void cancel() {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class);
+        final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
+        mOperation.start(startCb);
+        verify(mClientMonitor).start(mStartCallback.capture());
+        mStartCallback.getValue().onClientStarted(mClientMonitor);
+        mOperation.cancel(mHandler, cancelCb);
+
+        assertThat(mOperation.isCanceling()).isTrue();
+        verify(mClientMonitor).cancel();
+        verify(mClientMonitor, never()).cancelWithoutStarting(any());
+        verify(mClientMonitor, never()).destroy();
+
+        mStartCallback.getValue().onClientFinished(mClientMonitor, true);
+
+        assertThat(mOperation.isFinished()).isTrue();
+        assertThat(mOperation.isCanceling()).isFalse();
+        verify(mClientMonitor).destroy();
+
+        // should be unused since the operation was started
+        verify(cancelCb, never()).onClientStarted(any());
+        verify(cancelCb, never()).onClientFinished(any(), anyBoolean());
+    }
+
+    @Test
+    public void cancelWithoutStarting() {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
+        mOperation.cancel(mHandler, cancelCb);
+
+        assertThat(mOperation.isCanceling()).isTrue();
+        ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor =
+                ArgumentCaptor.forClass(BaseClientMonitor.Callback.class);
+        verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture());
+
+        cbCaptor.getValue().onClientFinished(mClientMonitor, true);
+        verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true));
+        verify(mClientMonitor, never()).start(any());
+        verify(mClientMonitor, never()).cancel();
+        verify(mClientMonitor).destroy();
+    }
+
+    @Test
+    public void markCanceling() {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        mOperation.markCanceling();
+
+        assertThat(mOperation.isMarkedCanceling()).isTrue();
+        assertThat(mOperation.isCanceling()).isFalse();
+        assertThat(mOperation.isFinished()).isFalse();
+        verify(mClientMonitor, never()).start(any());
+        verify(mClientMonitor, never()).cancel();
+        verify(mClientMonitor, never()).cancelWithoutStarting(any());
+        verify(mClientMonitor, never()).unableToStart();
+        verify(mClientMonitor, never()).destroy();
+    }
+
+    @Test
+    public void cancelPendingWithCookie() {
+        markCancellingAndStart(2);
+    }
+
+    @Test
+    public void cancelPendingWithoutCookie() {
+        markCancellingAndStart(null);
+    }
+
+    private void markCancellingAndStart(Integer withCookie) {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+        if (withCookie != null) {
+            when(mClientMonitor.getCookie()).thenReturn(withCookie);
+        }
+
+        mOperation.markCanceling();
+        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+        if (withCookie != null) {
+            mOperation.startWithCookie(cb, withCookie);
+        } else {
+            mOperation.start(cb);
+        }
+
+        assertThat(mOperation.isFinished()).isTrue();
+        verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
+        verify(mClientMonitor, never()).start(any());
+        verify(mClientMonitor, never()).cancel();
+        verify(mClientMonitor, never()).cancelWithoutStarting(any());
+        verify(mClientMonitor, never()).unableToStart();
+        verify(mClientMonitor).destroy();
+    }
+
+    @Test
+    public void cancelWatchdogWhenStarted() {
+        cancelWatchdog(true);
+    }
+
+    @Test
+    public void cancelWatchdogWithoutStarting() {
+        cancelWatchdog(false);
+    }
+
+    private void cancelWatchdog(boolean start) {
+        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+        mOperation.start(mock(BaseClientMonitor.Callback.class));
+        if (start) {
+            verify(mClientMonitor).start(mStartCallback.capture());
+            mStartCallback.getValue().onClientStarted(mClientMonitor);
+        }
+        mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class));
+
+        assertThat(mOperation.isCanceling()).isTrue();
+
+        // omit call to onClientFinished and trigger watchdog
+        mOperation.mCancelWatchdog.run();
+
+        assertThat(mOperation.isFinished()).isTrue();
+        assertThat(mOperation.isCanceling()).isFalse();
+        verify(mClientMonitor).destroy();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index d192697..ac08319 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -16,10 +16,14 @@
 
 package com.android.server.biometrics.sensors;
 
+import static android.testing.TestableLooper.RunWithLooper;
+
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,10 +38,13 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.IBiometricService;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
+import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -46,16 +53,18 @@
 
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
 import com.android.server.biometrics.nano.BiometricsProto;
-import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @Presubmit
 @SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
 public class BiometricSchedulerTest {
 
     private static final String TAG = "BiometricSchedulerTest";
@@ -76,8 +85,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mToken = new Binder();
-        mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN,
-                null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS,
+        mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
+                BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
+                mBiometricService, LOG_NUM_RECENT_OPERATIONS,
                 CoexCoordinator.getInstance());
     }
 
@@ -86,9 +96,9 @@
         final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
 
         final HalClientMonitor<Object> client1 =
-                new TestClientMonitor(mContext, mToken, nonNullDaemon);
+                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
         final HalClientMonitor<Object> client2 =
-                new TestClientMonitor(mContext, mToken, nonNullDaemon);
+                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
         mScheduler.scheduleClientMonitor(client1);
         mScheduler.scheduleClientMonitor(client2);
 
@@ -99,20 +109,17 @@
     @Test
     public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
         // Even if second client has a non-null daemon, it needs to be canceled.
-        Object daemon2 = mock(Object.class);
-
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
-
-        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1);
-        final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(
+                mContext, mToken, () -> null);
+        final TestHalClientMonitor client2 = new TestHalClientMonitor(
+                mContext, mToken, () -> mock(Object.class));
 
         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
         final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
 
         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
         // to pretend like there are two operations in the queue before kicking things off
-        mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
+        mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
                 mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -122,11 +129,11 @@
         mScheduler.scheduleClientMonitor(client2, callback2);
         waitForIdle();
 
-        assertTrue(client1.wasUnableToStart());
+        assertTrue(client1.mUnableToStart);
         verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
         verify(callback1, never()).onClientStarted(any());
 
-        assertTrue(client2.wasUnableToStart());
+        assertTrue(client2.mUnableToStart);
         verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
         verify(callback2, never()).onClientStarted(any());
 
@@ -138,21 +145,19 @@
         // Second non-BiometricPrompt client has a valid daemon
         final Object daemon2 = mock(Object.class);
 
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
-
         final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
 
         final TestAuthenticationClient client1 =
-                new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1);
-        final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
+                new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
+        final TestHalClientMonitor client2 =
+                new TestHalClientMonitor(mContext, mToken, () -> daemon2);
 
         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
         final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
 
         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
         // to pretend like there are two operations in the queue before kicking things off
-        mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
+        mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
                 mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -172,8 +177,8 @@
         verify(callback1, never()).onClientStarted(any());
 
         // Client 2 was able to start
-        assertFalse(client2.wasUnableToStart());
-        assertTrue(client2.hasStarted());
+        assertFalse(client2.mUnableToStart);
+        assertTrue(client2.mStarted);
         verify(callback2).onClientStarted(eq(client2));
     }
 
@@ -187,16 +192,18 @@
         // Schedule a BiometricPrompt authentication request
         mScheduler.scheduleClientMonitor(client1, callback1);
 
-        assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState);
-        assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor);
+        assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
+        assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
         assertEquals(0, mScheduler.mPendingOperations.size());
 
         // Request it to be canceled. The operation can be canceled immediately, and the scheduler
         // should go back to idle, since in this case the framework has not even requested the HAL
         // to authenticate yet.
         mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
+        waitForIdle();
         assertTrue(client1.isAlreadyDone());
         assertTrue(client1.mDestroyed);
+        assertFalse(client1.mStartedHal);
         assertNull(mScheduler.mCurrentOperation);
     }
 
@@ -210,8 +217,8 @@
         // assertEquals(0, bsp.recentOperations.length);
 
         // Pretend the scheduler is busy enrolling, and check the proto dump again.
-        final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
-                () -> mock(Object.class), BiometricsProto.CM_ENROLL);
+        final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
+                () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
         mScheduler.scheduleClientMonitor(client);
         waitForIdle();
         bsp = getDump(true /* clearSchedulerBuffer */);
@@ -230,8 +237,8 @@
     @Test
     public void testProtoDump_fifo() throws Exception {
         // Add the first operation
-        final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
-                () -> mock(Object.class), BiometricsProto.CM_ENROLL);
+        final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
+                () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
         mScheduler.scheduleClientMonitor(client);
         waitForIdle();
         BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
@@ -244,8 +251,8 @@
         client.getCallback().onClientFinished(client, true);
 
         // Add another operation
-        final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken,
-                () -> mock(Object.class), BiometricsProto.CM_REMOVE);
+        final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
+                () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE);
         mScheduler.scheduleClientMonitor(client2);
         waitForIdle();
         bsp = getDump(false /* clearSchedulerBuffer */);
@@ -256,8 +263,8 @@
         client2.getCallback().onClientFinished(client2, true);
 
         // And another operation
-        final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken,
-                () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE);
+        final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken,
+                () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE);
         mScheduler.scheduleClientMonitor(client3);
         waitForIdle();
         bsp = getDump(false /* clearSchedulerBuffer */);
@@ -290,8 +297,7 @@
     @Test
     public void testCancelPendingAuth() throws RemoteException {
         final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
-
-        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
                 mToken, callback);
@@ -302,14 +308,12 @@
         waitForIdle();
 
         assertEquals(mScheduler.getCurrentClient(), client1);
-        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
-                mScheduler.mPendingOperations.getFirst().mState);
+        assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
 
         // Request cancel before the authentication client has started
         mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
         waitForIdle();
-        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
-                mScheduler.mPendingOperations.getFirst().mState);
+        assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
 
         // Finish the blocking client. The authentication client should send ERROR_CANCELED
         client1.getCallback().onClientFinished(client1, true /* success */);
@@ -326,67 +330,109 @@
 
     @Test
     public void testCancels_whenAuthRequestIdNotSet() {
-        testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
+        testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */);
     }
 
     @Test
     public void testCancels_whenAuthRequestIdNotSet_notStarted() {
-        testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
+        testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */);
     }
 
     @Test
     public void testCancels_whenAuthRequestIdMatches() {
-        testCancelsWhenRequestId(200L, 200, true /* started */);
+        testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */);
     }
 
     @Test
     public void testCancels_whenAuthRequestIdMatches_noStarted() {
-        testCancelsWhenRequestId(200L, 200, false /* started */);
+        testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */);
     }
 
     @Test
     public void testDoesNotCancel_whenAuthRequestIdMismatched() {
-        testCancelsWhenRequestId(10L, 20, true /* started */);
+        testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */);
     }
 
     @Test
     public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
-        testCancelsWhenRequestId(10L, 20, false /* started */);
+        testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */);
+    }
+
+    private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+            boolean started) {
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        testCancelsWhenRequestId(requestId, cancelRequestId, started,
+                new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
+    }
+
+    @Test
+    public void testCancels_whenEnrollRequestIdNotSet() {
+        testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */);
+    }
+
+    @Test
+    public void testCancels_whenEnrollRequestIdMatches() {
+        testCancelsEnrollWhenRequestId(200L, 200, false /* started */);
+    }
+
+    @Test
+    public void testDoesNotCancel_whenEnrollRequestIdMismatched() {
+        testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
+    }
+
+    private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+            boolean started) {
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        testCancelsWhenRequestId(requestId, cancelRequestId, started,
+                new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
     }
 
     private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
-            boolean started) {
+            boolean started, HalClientMonitor<?> client) {
         final boolean matches = requestId == null || requestId == cancelRequestId;
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
-        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
-        final TestAuthenticationClient client = new TestAuthenticationClient(
-                mContext, lazyDaemon, mToken, callback);
         if (requestId != null) {
             client.setRequestId(requestId);
         }
 
+        final boolean isAuth = client instanceof TestAuthenticationClient;
+        final boolean isEnroll = client instanceof TestEnrollClient;
+
         mScheduler.scheduleClientMonitor(client);
         if (started) {
             mScheduler.startPreparedClient(client.getCookie());
         }
         waitForIdle();
-        mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+        if (isAuth) {
+            mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+        } else if (isEnroll) {
+            mScheduler.cancelEnrollment(mToken, cancelRequestId);
+        } else {
+            fail("unexpected operation type");
+        }
         waitForIdle();
 
-        assertEquals(matches && started ? 1 : 0, client.mNumCancels);
+        if (isAuth) {
+            // auth clients that were waiting for cookie when canceled should never invoke the hal
+            final TestAuthenticationClient authClient = (TestAuthenticationClient) client;
+            assertEquals(matches && started ? 1 : 0, authClient.mNumCancels);
+            assertEquals(started, authClient.mStartedHal);
+        } else if (isEnroll) {
+            final TestEnrollClient enrollClient = (TestEnrollClient) client;
+            assertEquals(matches ? 1 : 0, enrollClient.mNumCancels);
+            assertTrue(enrollClient.mStartedHal);
+        }
 
         if (matches) {
-            if (started) {
-                assertEquals(Operation.STATE_STARTED_CANCELING,
-                        mScheduler.mCurrentOperation.mState);
+            if (started || isEnroll) { // prep'd auth clients and enroll clients
+                assertTrue(mScheduler.mCurrentOperation.isCanceling());
             }
         } else {
-            if (started) {
-                assertEquals(Operation.STATE_STARTED,
-                        mScheduler.mCurrentOperation.mState);
+            if (started || isEnroll) { // prep'd auth clients and enroll clients
+                assertTrue(mScheduler.mCurrentOperation.isStarted());
             } else {
-                assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
-                        mScheduler.mCurrentOperation.mState);
+                assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
             }
         }
     }
@@ -411,18 +457,14 @@
         mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
         waitForIdle();
 
-        assertEquals(Operation.STATE_STARTED,
-                mScheduler.mCurrentOperation.mState);
-        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
-                mScheduler.mPendingOperations.getFirst().mState);
+        assertTrue(mScheduler.mCurrentOperation.isStarted());
+        assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
 
         mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
         waitForIdle();
 
-        assertEquals(Operation.STATE_STARTED,
-                mScheduler.mCurrentOperation.mState);
-        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
-                mScheduler.mPendingOperations.getFirst().mState);
+        assertTrue(mScheduler.mCurrentOperation.isStarted());
+        assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
     }
 
     @Test
@@ -459,12 +501,12 @@
     @Test
     public void testClientDestroyed_afterFinish() {
         final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
-        final TestClientMonitor client =
-                new TestClientMonitor(mContext, mToken, nonNullDaemon);
+        final TestHalClientMonitor client =
+                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
         mScheduler.scheduleClientMonitor(client);
         client.mCallback.onClientFinished(client, true /* success */);
         waitForIdle();
-        assertTrue(client.wasDestroyed());
+        assertTrue(client.mDestroyed);
     }
 
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
@@ -472,8 +514,10 @@
     }
 
     private static class TestAuthenticationClient extends AuthenticationClient<Object> {
-        int mNumCancels = 0;
+        boolean mStartedHal = false;
+        boolean mStoppedHal = false;
         boolean mDestroyed = false;
+        int mNumCancels = 0;
 
         public TestAuthenticationClient(@NonNull Context context,
                 @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -488,18 +532,16 @@
 
         @Override
         protected void stopHalOperation() {
-
+            mStoppedHal = true;
         }
 
         @Override
         protected void startHalOperation() {
-
+            mStartedHal = true;
         }
 
         @Override
-        protected void handleLifecycleAfterAuth(boolean authenticated) {
-
-        }
+        protected void handleLifecycleAfterAuth(boolean authenticated) {}
 
         @Override
         public boolean wasUserDetected() {
@@ -519,36 +561,59 @@
         }
     }
 
-    private static class TestClientMonitor2 extends TestClientMonitor {
-        private final int mProtoEnum;
+    private static class TestEnrollClient extends EnrollClient<Object> {
+        boolean mStartedHal = false;
+        boolean mStoppedHal = false;
+        int mNumCancels = 0;
 
-        public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token,
-                @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) {
-            super(context, token, lazyDaemon);
-            mProtoEnum = protoEnum;
+        TestEnrollClient(@NonNull Context context,
+                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+                @NonNull ClientMonitorCallbackConverter listener) {
+            super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
+                    "test" /* owner */, mock(BiometricUtils.class),
+                    5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
+                    true /* shouldVibrate */);
         }
 
         @Override
-        public int getProtoEnum() {
-            return mProtoEnum;
+        protected void stopHalOperation() {
+            mStoppedHal = true;
+        }
+
+        @Override
+        protected void startHalOperation() {
+            mStartedHal = true;
+        }
+
+        @Override
+        protected boolean hasReachedEnrollmentLimit() {
+            return false;
+        }
+
+        @Override
+        public void cancel() {
+            mNumCancels++;
+            super.cancel();
         }
     }
 
-    private static class TestClientMonitor extends HalClientMonitor<Object> {
+    private static class TestHalClientMonitor extends HalClientMonitor<Object> {
+        private final int mProtoEnum;
         private boolean mUnableToStart;
         private boolean mStarted;
         private boolean mDestroyed;
 
-        public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
+        TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
                 @NonNull LazyDaemon<Object> lazyDaemon) {
-            this(context, token, lazyDaemon, 0 /* cookie */);
+            this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
         }
 
-        public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
-                @NonNull LazyDaemon<Object> lazyDaemon, int cookie) {
+        TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
+                @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
             super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
                     TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
                     0 /* statsAction */, 0 /* statsClient */);
+            mProtoEnum = protoEnum;
         }
 
         @Override
@@ -559,9 +624,7 @@
 
         @Override
         public int getProtoEnum() {
-            // Anything other than CM_NONE, which is used to represent "idle". Tests that need
-            // real proto enums should use TestClientMonitor2
-            return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+            return mProtoEnum;
         }
 
         @Override
@@ -573,7 +636,7 @@
 
         @Override
         protected void startHalOperation() {
-
+            mStarted = true;
         }
 
         @Override
@@ -581,22 +644,9 @@
             super.destroy();
             mDestroyed = true;
         }
-
-        public boolean wasUnableToStart() {
-            return mUnableToStart;
-        }
-
-        public boolean hasStarted() {
-            return mStarted;
-        }
-
-        public boolean wasDestroyed() {
-            return mDestroyed;
-        }
-
     }
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    private void waitForIdle() {
+        TestableLooper.get(this).processAllMessages();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 7fccd49..407f5fb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors;
 
+import static android.testing.TestableLooper.RunWithLooper;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -28,52 +30,53 @@
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @Presubmit
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
 @SmallTest
 public class UserAwareBiometricSchedulerTest {
 
-    private static final String TAG = "BiometricSchedulerTest";
+    private static final String TAG = "UserAwareBiometricSchedulerTest";
     private static final int TEST_SENSOR_ID = 0;
 
+    private Handler mHandler;
     private UserAwareBiometricScheduler mScheduler;
-    private IBinder mToken;
+    private IBinder mToken = new Binder();
 
     @Mock
     private Context mContext;
     @Mock
     private IBiometricService mBiometricService;
 
-    private TestUserStartedCallback mUserStartedCallback;
-    private TestUserStoppedCallback mUserStoppedCallback;
+    private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
+    private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
     private int mCurrentUserId = UserHandle.USER_NULL;
-    private boolean mStartOperationsFinish;
-    private int mStartUserClientCount;
+    private boolean mStartOperationsFinish = true;
+    private int mStartUserClientCount = 0;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        mToken = new Binder();
-        mStartOperationsFinish = true;
-        mStartUserClientCount = 0;
-        mUserStartedCallback = new TestUserStartedCallback();
-        mUserStoppedCallback = new TestUserStoppedCallback();
-
+        mHandler = new Handler(TestableLooper.get(this).getLooper());
         mScheduler = new UserAwareBiometricScheduler(TAG,
+                mHandler,
                 BiometricScheduler.SENSOR_TYPE_UNKNOWN,
                 null /* gestureAvailabilityDispatcher */,
                 mBiometricService,
@@ -117,7 +120,7 @@
         mCurrentUserId = UserHandle.USER_NULL;
         mStartOperationsFinish = false;
 
-        final BaseClientMonitor[] nextClients = new BaseClientMonitor[] {
+        final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
                 mock(BaseClientMonitor.class),
                 mock(BaseClientMonitor.class),
                 mock(BaseClientMonitor.class)
@@ -147,11 +150,11 @@
         waitForIdle();
 
         final TestStartUserClient startUserClient =
-                (TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor;
+                (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
         mScheduler.reset();
         assertNull(mScheduler.mCurrentOperation);
 
-        final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation(
+        final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
                 mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
         mScheduler.mCurrentOperation = fakeOperation;
         startUserClient.mCallback.onClientFinished(startUserClient, true);
@@ -194,8 +197,8 @@
         verify(nextClient).start(any());
     }
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    private void waitForIdle() {
+        TestableLooper.get(this).processAllMessages();
     }
 
     private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index a13dff2..2718bf9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -33,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -79,10 +80,13 @@
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
 
         mScheduler = new UserAwareBiometricScheduler(TAG,
+                new Handler(mLooper.getLooper()),
                 BiometricScheduler.SENSOR_TYPE_FACE,
                 null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
                 () -> USER_ID,
-                mUserSwitchCallback);
+                mUserSwitchCallback,
+                CoexCoordinator.getInstance());
         mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 39c51d5..21a7a8a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -32,7 +32,9 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
@@ -69,6 +71,7 @@
     @Mock
     private BiometricScheduler mScheduler;
 
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
     private IBinder mBinder;
@@ -97,7 +100,7 @@
                 resetLockoutRequiresChallenge);
 
         Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
-        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler);
+        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
         mBinder = new Binder();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 0d520ca..d4609b5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -33,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -79,10 +80,13 @@
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
 
         mScheduler = new UserAwareBiometricScheduler(TAG,
+                new Handler(mLooper.getLooper()),
                 BiometricScheduler.SENSOR_TYPE_FP_OTHER,
                 null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
                 () -> USER_ID,
-                mUserSwitchCallback);
+                mUserSwitchCallback,
+                CoexCoordinator.getInstance());
         mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index d79a833..0a0f7d7 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -71,6 +71,8 @@
     private InputController.NativeWrapper mNativeWrapperMock;
     @Mock
     private DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock
+    private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
 
     @Before
     public void setUp() {
@@ -85,7 +87,8 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock);
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 /* association info */ null, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {}, new VirtualDeviceParams.Builder().build());
+                (int associationId) -> {}, mPendingTrampolineCallback,
+                new VirtualDeviceParams.Builder().build());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
index e286cb2..d54524e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -41,10 +41,10 @@
     @Test
     public void testConstruct() {
         final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
-                "CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */);
+                "TEST_CLOSED" /* name */, DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS /* flags */);
         assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
-        assertEquals(state.getName(), "CLOSED");
-        assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS);
+        assertEquals(state.getName(), "TEST_CLOSED");
+        assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index c9cf2f0..b94fc43 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -213,6 +213,25 @@
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
+    @Test
+    public void cancelOverrideRequestsTest() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */, 0 /* flags */);
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                2 /* requestedState */, 0 /* flags */);
+
+        mController.addRequest(firstRequest);
+        mController.addRequest(secondRequest);
+
+        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+        mController.cancelOverrideRequests();
+
+        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+    }
+
     private static final class TestStatusChangeListener implements
             OverrideRequestController.StatusChangeListener {
         private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index abe7d89..176e5a9 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -111,7 +113,7 @@
 
         // Configure the brightness controller and grab an instance of the sensor listener,
         // through which we can deliver fake (for test) sensor values.
-        controller.configure(true /* enable */, null /* configuration */,
+        controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
                 0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
                 false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
 
@@ -227,7 +229,7 @@
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
 
         // User sets brightness to 100
-        mController.configure(true /* enable */, null /* configuration */,
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
                 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
                 false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
 
@@ -250,7 +252,7 @@
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
 
         // User sets brightness to 100
-        mController.configure(true /* enable */, null /* configuration */,
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
                 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
                 false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
 
@@ -267,7 +269,7 @@
         verifyNoMoreInteractions(mBrightnessMappingStrategy);
 
         // User sets idle brightness to 0.5
-        mController.configure(true /* enable */, null /* configuration */,
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
                 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
                 false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
 
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index aca8632..beecdad 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -20,6 +20,11 @@
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static com.android.server.display.AutomaticBrightnessController
+                                                      .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
+
 import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static org.junit.Assert.assertEquals;
@@ -47,6 +52,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
@@ -87,6 +93,7 @@
     private TestLooper mTestLooper;
     private Handler mHandler;
     private Binder mDisplayToken;
+    private String mDisplayUniqueId;
     private Context mContextSpy;
 
     @Rule
@@ -108,6 +115,7 @@
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mDisplayToken = null;
+        mDisplayUniqueId = "unique_id";
         mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(resolver);
@@ -123,8 +131,8 @@
     public void testNoHbmData() {
         initHandler(null);
         final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
-                DEFAULT_MAX, null, () -> {}, mContextSpy);
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy);
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
         assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
     }
@@ -133,9 +141,9 @@
     public void testNoHbmData_Enabled() {
         initHandler(null);
         final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
-                DEFAULT_MAX, null, () -> {}, mContextSpy);
-        hbmc.setAutoBrightnessEnabled(true);
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
         assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
@@ -152,7 +160,7 @@
     public void testAutoBrightnessEnabled_NoLux() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
     }
 
@@ -160,7 +168,7 @@
     public void testAutoBrightnessEnabled_LowLux() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
         assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
     }
@@ -169,7 +177,7 @@
     public void testAutoBrightnessEnabled_HighLux() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
     }
@@ -178,9 +186,9 @@
     public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmc.setAutoBrightnessEnabled(false);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED);
 
         assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
     }
@@ -189,7 +197,7 @@
     public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
 
@@ -221,7 +229,7 @@
     public void testInHBM_ThenLowLux() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
 
@@ -245,7 +253,7 @@
     public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
 
         hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
@@ -274,7 +282,7 @@
     public void testInHBM_TestMultipleEvents_DueToLux() {
         final HighBrightnessModeController hbmc = createDefaultHbm();
 
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
 
         // Go into HBM for half the allowed window
@@ -316,7 +324,7 @@
         listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
 
         // Try to go into HBM mode but fail
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         advanceTime(10);
 
@@ -335,7 +343,7 @@
         listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_LIGHT));
 
         // Try to go into HBM mode
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         advanceTime(1);
 
@@ -378,7 +386,7 @@
         final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
 
         // Turn on sunlight
-        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
@@ -451,6 +459,137 @@
         assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
     }
 
+    @Test
+    public void testHbmStats_StateChange() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onBrightnessChanged(TRANSITION_POINT);
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_HDR
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
+                0, 0, 0 /*flags*/);
+        advanceTime(0);
+
+        // Verify Stats HBM_OFF
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+
+        // Verify Stats HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.onAmbientLuxChange(1);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+        // Verify Stats HBM_OFF
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
+    }
+
+    @Test
+    public void testHbmStats_ThermalOff() throws Exception {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener thermListener = mThermalEventListenerCaptor.getValue();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(1);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        thermListener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+        advanceTime(10);
+        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
+    }
+
+    @Test
+    public void testHbmStats_TimeOut() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        // Use up all the time in the window.
+        advanceTime(TIME_WINDOW_MILLIS + 1);
+
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
+    }
+
+    @Test
+    public void testHbmStats_DisplayOff() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
+    }
+
+    @Test
+    public void testHbmStats_HdrPlaying() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+        advanceTime(0);
+
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
+    }
+
     private void assertState(HighBrightnessModeController hbmc,
             float brightnessMin, float brightnessMax, int hbmMode) {
         assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
@@ -466,8 +605,8 @@
     private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
         initHandler(clock);
         return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
-                DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {},
-                mContextSpy);
+                DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
+                DEFAULT_HBM_DATA, () -> {}, mContextSpy);
     }
 
     private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index b811e28..9a6f61e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -49,12 +49,8 @@
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.METERED_YES;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.TAG_ALL;
-import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -75,6 +71,7 @@
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
 import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -108,6 +105,8 @@
 import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.Intent;
@@ -125,8 +124,6 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicy;
 import android.net.NetworkStateSnapshot;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiInfo;
@@ -138,7 +135,6 @@
 import android.os.PowerSaveState;
 import android.os.RemoteException;
 import android.os.SimpleClock;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
@@ -263,12 +259,13 @@
     private @Mock CarrierConfigManager mCarrierConfigManager;
     private @Mock TelephonyManager mTelephonyManager;
     private @Mock UserManager mUserManager;
+    private @Mock NetworkStatsManager mStatsManager;
+    private TestDependencies mDeps;
 
     private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
             ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
 
     private ActivityManagerInternal mActivityManagerInternal;
-    private NetworkStatsManagerInternal mStatsService;
 
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
@@ -335,8 +332,47 @@
                 .setBatterySaverEnabled(false).build();
         final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class);
         when(pmInternal.getLowPowerState(anyInt())).thenReturn(state);
+    }
 
-        mStatsService = addLocalServiceMock(NetworkStatsManagerInternal.class);
+    private class TestDependencies extends NetworkPolicyManagerService.Dependencies {
+        private final SparseArray<NetworkStats.Bucket> mMockedStats = new SparseArray<>();
+
+        TestDependencies(Context context) {
+            super(context);
+        }
+
+        @Override
+        long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+            int total = 0;
+            for (int i = 0; i < mMockedStats.size(); i++) {
+                NetworkStats.Bucket bucket = mMockedStats.valueAt(i);
+                total += bucket.getRxBytes() + bucket.getTxBytes();
+            }
+            return total;
+        }
+
+        @Override
+        List<NetworkStats.Bucket> getNetworkUidBytes(NetworkTemplate template, long start,
+                long end) {
+            final List<NetworkStats.Bucket> ret = new ArrayList<>();
+            for (int i = 0; i < mMockedStats.size(); i++) {
+                ret.add(mMockedStats.valueAt(i));
+            }
+            return ret;
+        }
+
+        private void setMockedTotalBytes(int uid, long rxBytes, long txBytes) {
+            final NetworkStats.Bucket bucket = mock(NetworkStats.Bucket.class);
+            when(bucket.getUid()).thenReturn(uid);
+            when(bucket.getRxBytes()).thenReturn(rxBytes);
+            when(bucket.getTxBytes()).thenReturn(txBytes);
+            mMockedStats.set(uid, bucket);
+        }
+
+        private void increaseMockedTotalBytes(int uid, long rxBytes, long txBytes) {
+            final NetworkStats.Bucket bucket = mMockedStats.get(uid);
+            setMockedTotalBytes(uid, bucket.getRxBytes() + rxBytes, bucket.getTxBytes() + txBytes);
+        }
     }
 
     @Before
@@ -376,6 +412,8 @@
                         return mConnManager;
                     case Context.USER_SERVICE:
                         return mUserManager;
+                    case Context.NETWORK_STATS_SERVICE:
+                        return mStatsManager;
                     default:
                         return super.getSystemService(name);
                 }
@@ -400,8 +438,9 @@
         }).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class));
 
         mFutureIntent = newRestrictBackgroundChangedFuture();
+        mDeps = new TestDependencies(mServiceContext);
         mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
-                mNetworkManager, mIpm, mClock, mPolicyDir, true);
+                mNetworkManager, mIpm, mClock, mPolicyDir, true, mDeps);
         mService.bindConnectivityManager();
         mPolicyListener = new NetworkPolicyListenerAnswer(mService);
 
@@ -456,6 +495,9 @@
         verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
+        // Simulate NetworkStatsService broadcast stats updated to signal its readiness.
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_UPDATED));
+
         NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
         mDefaultWarningBytes = defaultPolicy.warningBytes;
         mDefaultLimitBytes = defaultPolicy.limitBytes;
@@ -479,7 +521,6 @@
         LocalServices.removeServiceForTest(DeviceIdleInternal.class);
         LocalServices.removeServiceForTest(AppStandbyInternal.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
-        LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
     }
 
     @After
@@ -1108,10 +1149,7 @@
         when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots);
 
         // pretend that 512 bytes total have happened
-        stats = new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, 256L, 2L, 256L, 2L);
-        when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START, CYCLE_END))
-                .thenReturn(stats.getTotalBytes());
+        mDeps.setMockedTotalBytes(UID_A, 256L, 256L);
 
         mPolicyListener.expect().onMeteredIfacesChanged(any());
         setNetworkPolicies(new NetworkPolicy(
@@ -1124,26 +1162,6 @@
 
     @Test
     public void testNotificationWarningLimitSnooze() throws Exception {
-        // Create a place to store fake usage
-        final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
-                .thenAnswer(new Answer<Long>() {
-                    @Override
-                    public Long answer(InvocationOnMock invocation) throws Throwable {
-                        final NetworkStatsHistory.Entry entry = history.getValues(
-                                invocation.getArgument(1), invocation.getArgument(2), null);
-                        return entry.rxBytes + entry.txBytes;
-                    }
-                });
-        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
-                .thenAnswer(new Answer<NetworkStats>() {
-                    @Override
-                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
-                        return stats;
-                    }
-                });
-
         // Get active mobile network in place
         expectMobileDefaults();
         mService.updateNetworks();
@@ -1161,9 +1179,7 @@
 
         // Normal usage means no notification
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0);
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
@@ -1178,9 +1194,7 @@
 
         // Push over warning
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0);
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
@@ -1196,9 +1210,7 @@
 
         // Push over warning, but with a config that isn't from an identified carrier
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0);
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
@@ -1215,9 +1227,7 @@
 
         // Push over limit
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0));
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1810), 0);
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
@@ -1248,26 +1258,6 @@
 
     @Test
     public void testNotificationRapid() throws Exception {
-        // Create a place to store fake usage
-        final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
-                .thenAnswer(new Answer<Long>() {
-                    @Override
-                    public Long answer(InvocationOnMock invocation) throws Throwable {
-                        final NetworkStatsHistory.Entry entry = history.getValues(
-                                invocation.getArgument(1), invocation.getArgument(2), null);
-                        return entry.rxBytes + entry.txBytes;
-                    }
-                });
-        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
-                .thenAnswer(new Answer<NetworkStats>() {
-                    @Override
-                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
-                        return stats;
-                    }
-                });
-
         // Get active mobile network in place
         expectMobileDefaults();
         mService.updateNetworks();
@@ -1285,9 +1275,7 @@
 
         // Using 20% data in 20% time is normal
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0);
 
             reset(mNotifManager);
             mService.updateNetworks();
@@ -1297,16 +1285,9 @@
         // Using 80% data in 20% time is alarming; but spread equally among
         // three UIDs means we get generic alert
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
-            stats.clear();
-            stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
-                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
-            stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
-                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
-            stats.insertEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
-                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(480), 0);
+            mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0);
+            mDeps.setMockedTotalBytes(UID_C, DataUnit.MEGABYTES.toBytes(480), 0);
 
             reset(mNotifManager);
             mService.updateNetworks();
@@ -1325,14 +1306,9 @@
         // Using 80% data in 20% time is alarming; but mostly done by one UID
         // means we get specific alert
         {
-            history.clear();
-            history.recordData(start, end,
-                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
-            stats.clear();
-            stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
-                    DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
-            stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
-                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+            mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(960), 0);
+            mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0);
+            mDeps.setMockedTotalBytes(UID_C, 0, 0);
 
             reset(mNotifManager);
             mService.updateNetworks();
@@ -1362,13 +1338,10 @@
 
         // bring up wifi network with metered policy
         snapshots = List.of(buildWifi());
-        stats = new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, 0L, 0L, 0L, 0L);
+        mDeps.setMockedTotalBytes(UID_A, 0L, 0L);
 
         {
             when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots);
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
 
             mPolicyListener.expect().onMeteredIfacesChanged(any());
             setNetworkPolicies(new NetworkPolicy(
@@ -1647,18 +1620,6 @@
         final NetworkPolicyManagerInternal internal = LocalServices
                 .getService(NetworkPolicyManagerInternal.class);
 
-        // Create a place to store fake usage
-        final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
-                .thenAnswer(invocation -> {
-                    final NetworkStatsHistory.Entry entry = history.getValues(
-                            invocation.getArgument(1), invocation.getArgument(2), null);
-                    return entry.rxBytes + entry.txBytes;
-                });
-        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
-                .thenReturn(stats);
-
         // Get active mobile network in place
         expectMobileDefaults();
         mService.updateNetworks();
@@ -1669,9 +1630,7 @@
         setCurrentTimeMillis(end);
 
         // Get some data usage in place
-        history.clear();
-        history.recordData(start, end,
-                new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+        mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0);
 
         // No data plan
         {
@@ -1786,22 +1745,11 @@
                 true);
     }
 
-    private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) {
-        stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
-                rxBytes, 1, txBytes, 1, 0);
-        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
-                .thenReturn(stats.getTotalBytes());
-        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
-                .thenReturn(stats);
-    }
-
     private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException {
-        final NetworkPolicyManagerInternal npmi = LocalServices
-                .getService(NetworkPolicyManagerInternal.class);
-        npmi.onStatsProviderWarningOrLimitReached("TEST");
+        mService.onStatsProviderWarningOrLimitReached();
         // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED.
         postMsgAndWaitForCompletion();
-        verify(mStatsService).forceUpdate();
+        verify(mStatsManager).forceUpdate();
         // Wait for processing of MSG_*_INTERFACE_QUOTAS.
         postMsgAndWaitForCompletion();
     }
@@ -1814,13 +1762,12 @@
     public void testStatsProviderWarningAndLimitReached() throws Exception {
         final int CYCLE_DAY = 15;
 
-        final NetworkStats stats = new NetworkStats(0L, 1);
-        increaseMockedTotalBytes(stats, 2999, 2000);
+        mDeps.setMockedTotalBytes(UID_A, 2999, 2000);
 
         // Get active mobile network in place
         expectMobileDefaults();
         mService.updateNetworks();
-        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
+        verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
                 Long.MAX_VALUE);
 
         // Set warning to 7KB and limit to 10KB.
@@ -1830,32 +1777,32 @@
         postMsgAndWaitForCompletion();
 
         // Verifies that remaining quotas are set to providers.
-        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
-        reset(mStatsService);
+        verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
+        reset(mStatsManager);
 
         // Increase the usage and simulates that limit reached fires earlier by provider,
         // but actually the quota is not yet reached. Verifies that the limit reached leads to
         // a force update and new quotas should be set.
-        increaseMockedTotalBytes(stats, 1000, 999);
+        mDeps.increaseMockedTotalBytes(UID_A, 1000, 999);
         triggerOnStatsProviderWarningOrLimitReached();
-        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
-        reset(mStatsService);
+        verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
+        reset(mStatsManager);
 
         // Increase the usage and simulate warning reached, the new warning should be unlimited
         // since service will disable warning quota to stop lower layer from keep triggering
         // warning reached event.
-        increaseMockedTotalBytes(stats, 1000L, 1000);
+        mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000);
         triggerOnStatsProviderWarningOrLimitReached();
-        verify(mStatsService).setStatsProviderWarningAndLimitAsync(
+        verify(mStatsManager).setStatsProviderWarningAndLimitAsync(
                 TEST_IFACE, Long.MAX_VALUE, 1002L);
-        reset(mStatsService);
+        reset(mStatsManager);
 
         // Increase the usage that over the warning and limit, the new limit should set to 1 to
         // block the network traffic.
-        increaseMockedTotalBytes(stats, 1000L, 1000);
+        mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000);
         triggerOnStatsProviderWarningOrLimitReached();
-        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
-        reset(mStatsService);
+        verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
+        reset(mStatsManager);
     }
 
     private void enableRestrictedMode(boolean enable) throws Exception {
@@ -2145,7 +2092,7 @@
     }
 
     private void verifyAdvisePersistThreshold() throws Exception {
-        verify(mStatsService).advisePersistThreshold(anyLong());
+        verify(mStatsManager).advisePersistThreshold(anyLong());
     }
 
     private static class TestAbstractFuture<T> extends AbstractFuture<T> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index e4273dc..429445f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -315,8 +315,8 @@
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
                 asHandle(currentUser));
         try {
-            assertThat(mUserManager.removeUserOrSetEphemeral(user1.id,
-                    /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
+            assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+                    /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
         } finally {
             mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
                     asHandle(currentUser));
@@ -335,8 +335,8 @@
                 asHandle(currentUser));
         try {
             synchronized (mUserRemoveLock) {
-                assertThat(mUserManager.removeUserOrSetEphemeral(user1.id,
-                        /* evenWhenDisallowed= */ true))
+                assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+                        /* overrideDevicePolicy= */ true))
                                 .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
                 waitForUserRemovalLocked(user1.id);
             }
@@ -352,8 +352,8 @@
     @MediumTest
     @Test
     public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception {
-        assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM,
-                /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
+        assertThat(mUserManager.removeUserWhenPossible(UserHandle.SYSTEM,
+                /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
 
         assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue();
     }
@@ -362,8 +362,8 @@
     @Test
     public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception {
         assertThat(hasUser(Integer.MAX_VALUE)).isFalse();
-        assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE,
-                /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
+        assertThat(mUserManager.removeUserWhenPossible(UserHandle.of(Integer.MAX_VALUE),
+                /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
     }
 
     @MediumTest
@@ -374,8 +374,8 @@
         // Switch to the user just created.
         switchUser(user1.id, null, /* ignoreHandle= */ true);
 
-        assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, /* evenWhenDisallowed= */ false))
-                .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+        assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+                /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
 
         assertThat(hasUser(user1.id)).isTrue();
         assertThat(getUser(user1.id).isEphemeral()).isTrue();
@@ -395,8 +395,9 @@
     public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception {
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         synchronized (mUserRemoveLock) {
-            assertThat(mUserManager.removeUserOrSetEphemeral(user1.id,
-                    /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+            assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+                    /* overrideDevicePolicy= */ false))
+                            .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
             waitForUserRemovalLocked(user1.id);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 761cea7..90b19a4 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -160,12 +160,12 @@
     }
 
     @Test
-    public void create_stateWithCancelStickyRequestFlag() {
+    public void create_stateWithCancelOverrideRequestFlag() {
         String configString = "<device-state-config>\n"
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
                 + "        <flags>\n"
-                + "            <flag>FLAG_CANCEL_STICKY_REQUESTS</flag>\n"
+                + "            <flag>FLAG_CANCEL_OVERRIDE_REQUESTS</flag>\n"
                 + "        </flags>\n"
                 + "        <conditions/>\n"
                 + "    </device-state>\n"
@@ -183,7 +183,7 @@
 
         verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
         final DeviceState[] expectedStates = new DeviceState[]{
-                new DeviceState(1, "", DeviceState.FLAG_CANCEL_STICKY_REQUESTS),
+                new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
                 new DeviceState(2, "", 0 /* flags */) };
         assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d831903..a192bf8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2701,6 +2701,8 @@
 
         // should trigger a broadcast
         mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+        Thread.sleep(500);
+        waitForIdle();
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
 
@@ -2728,6 +2730,8 @@
 
         // should trigger a broadcast
         mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+        Thread.sleep(500);
+        waitForIdle();
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 1362628..f6400b6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -601,6 +601,8 @@
         when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED);
 
         mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+        Thread.sleep(500);
+        waitForIdle();
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -616,6 +618,8 @@
         when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
 
         mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+        Thread.sleep(500);
+        waitForIdle();
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index d49cf67..b48c9c3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -542,6 +542,7 @@
         mHelper.setInvalidMessageSent(PKG_P, UID_P);
         mHelper.setValidMessageSent(PKG_P, UID_P);
         mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
+        mHelper.setValidBubbleSent(PKG_P, UID_P);
 
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
 
@@ -561,6 +562,7 @@
         assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1));
         assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P));
         assertTrue(mHelper.didUserEverDemoteInvalidMsgApp(PKG_P, UID_P));
+        assertTrue(mHelper.hasSentValidBubble(PKG_P, UID_P));
         assertEquals(channel1,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
         compareChannels(channel2,
@@ -3174,6 +3176,19 @@
     }
 
     @Test
+    public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+        group.setBlocked(true);
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
+        assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+
+        NotificationChannelGroup group3 = group.clone();
+        group3.setBlocked(false);
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true);
+        assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+    }
+
+    @Test
     public void testIsGroup_appCannotResetBlock() throws Exception {
         NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
@@ -4706,7 +4721,7 @@
     public void testGetConversations_noDisabledGroups() {
         NotificationChannelGroup group = new NotificationChannelGroup("a", "a");
         group.setBlocked(true);
-        mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+        mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false);
         NotificationChannel parent = new NotificationChannel("parent", "p", 1);
         mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
 
@@ -4927,6 +4942,18 @@
     }
 
     @Test
+    public void testValidBubbleSent() {
+        // create package preferences
+        mHelper.canShowBadge(PKG_P, UID_P);
+        // false by default
+        assertFalse(mHelper.hasSentValidBubble(PKG_P, UID_P));
+
+        // set something valid was sent
+        mHelper.setValidBubbleSent(PKG_P, UID_P);
+        assertTrue(mHelper.hasSentValidBubble(PKG_P, UID_P));
+    }
+
+    @Test
     public void testPullPackageChannelPreferencesStats() {
         String channelId = "parent";
         String name = "messages";
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java
new file mode 100644
index 0000000..6e11d8c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.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.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.view.Display;
+import android.window.DisplayWindowPolicyController;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Tests for the {@link DisplayWindowPolicyControllerHelper} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:DisplayWindowPolicyControllerHelperTests
+ */
+@RunWith(WindowTestRunner.class)
+public class DisplayWindowPolicyControllerHelperTests extends WindowTestsBase {
+    private static final int TEST_USER_0_ID = 0;
+    private static final int TEST_USER_1_ID = 10;
+
+    private TestDisplayWindowPolicyController mDwpc = new TestDisplayWindowPolicyController();
+    private DisplayContent mSecondaryDisplay;
+
+    @Before
+    public void setUp() {
+        doReturn(mDwpc).when(mWm.mDisplayManagerInternal)
+                .getDisplayWindowPolicyController(anyInt());
+        mSecondaryDisplay = createNewDisplay();
+        assertNotEquals(Display.DEFAULT_DISPLAY, mSecondaryDisplay.getDisplayId());
+        assertTrue(mSecondaryDisplay.mDwpcHelper.hasController());
+    }
+
+    @Test
+    public void testOnRunningActivityChanged() {
+        final ActivityRecord activity1 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_0_ID);
+        verifyTopActivityAndRunningUid(activity1,
+                true /* expectedUid0 */, false /* expectedUid1 */);
+        final ActivityRecord activity2 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_1_ID);
+        verifyTopActivityAndRunningUid(activity2,
+                true /* expectedUid0 */, true /* expectedUid1 */);
+        final ActivityRecord activity3 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_0_ID);
+        verifyTopActivityAndRunningUid(activity3,
+                true /* expectedUid0 */, true /* expectedUid1 */);
+
+        activity3.finishing = true;
+        verifyTopActivityAndRunningUid(activity2,
+                true /* expectedUid0 */, true /* expectedUid1 */);
+
+        activity2.finishing = true;
+        verifyTopActivityAndRunningUid(activity1,
+                true /* expectedUid0 */, false /* expectedUid1 */);
+
+        activity1.finishing = true;
+        verifyTopActivityAndRunningUid(null /* expectedTopActivity */,
+                false /* expectedUid0 */, false /* expectedUid1 */);
+    }
+
+    private void verifyTopActivityAndRunningUid(ActivityRecord expectedTopActivity,
+            boolean expectedUid0, boolean expectedUid1) {
+        mSecondaryDisplay.onRunningActivityChanged();
+        int uidAmount = (expectedUid0 && expectedUid1) ? 2 : (expectedUid0 || expectedUid1) ? 1 : 0;
+        assertEquals(expectedTopActivity == null ? null :
+                expectedTopActivity.info.getComponentName(), mDwpc.mTopActivity);
+        assertEquals(expectedTopActivity == null ? UserHandle.USER_NULL :
+                expectedTopActivity.info.applicationInfo.uid, mDwpc.mTopActivityUid);
+        assertEquals(uidAmount, mDwpc.mRunningUids.size());
+        assertTrue(mDwpc.mRunningUids.contains(TEST_USER_0_ID) == expectedUid0);
+        assertTrue(mDwpc.mRunningUids.contains(TEST_USER_1_ID) == expectedUid1);
+
+    }
+
+    private ActivityRecord launchActivityOnDisplay(DisplayContent display, int uid) {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(display)
+                .setUserId(uid)
+                .build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .setUid(uid)
+                .setOnTop(true)
+                .build();
+        return activity;
+    }
+
+    private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
+
+        ComponentName mTopActivity = null;
+        int mTopActivityUid = UserHandle.USER_NULL;
+        ArraySet<Integer> mRunningUids = new ArraySet<>();
+
+        @Override
+        public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
+            return false;
+        }
+
+        @Override
+        public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
+                int systemWindowFlags) {
+            return false;
+        }
+
+        @Override
+        public void onTopActivityChanged(ComponentName topActivity, int uid) {
+            super.onTopActivityChanged(topActivity, uid);
+            mTopActivity = topActivity;
+            mTopActivityUid = uid;
+        }
+
+        @Override
+        public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
+            super.onRunningAppsChanged(runningUids);
+            mRunningUids.clear();
+            mRunningUids.addAll(runningUids);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index b4c449a..632a59d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -117,8 +117,7 @@
         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
                 mFinishedCallback);
 
-        // Remove the app window so that the animation target can not be created
-        activity.removeImmediately();
+        // The activity doesn't contain window so the animation target cannot be created.
         mController.startAnimation();
 
         // Verify that the finish callback to reparent the leash is called
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 9ad8c5b..0debdfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,14 +21,17 @@
 import static com.android.server.wm.testing.Assert.assertThrows;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
@@ -471,6 +474,7 @@
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                 .setParentTask(task)
                 .setOrganizer(mOrganizer)
+                .setFragmentToken(mFragmentToken)
                 .build();
 
         // Mock the task to invisible
@@ -485,4 +489,38 @@
         // Verifies that event was not sent
         verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
     }
+
+    /**
+     * Tests that a task fragment info changed event is still sent if the task is invisible only
+     * when the info changed event is because of the last activity in a task finishing.
+     */
+    @Test
+    public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() {
+        // Create a TaskFragment with an activity, all within a parent task
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(mFragmentToken)
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final Task parentTask = taskFragment.getTask();
+        final ActivityRecord activity = taskFragment.getTopNonFinishingActivity();
+        assertTrue(parentTask.shouldBeVisible(null));
+
+        // Dispatch pending info changed event from creating the activity
+        mController.registerOrganizer(mIOrganizer);
+        taskFragment.mTaskFragmentAppearedSent = true;
+        mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+        mController.dispatchPendingEvents();
+
+        // Finish the activity and verify that the task is invisible
+        activity.finishing = true;
+        assertFalse(parentTask.shouldBeVisible(null));
+
+        // Verify the info changed callback still occurred despite the task being invisible
+        reset(mOrganizer);
+        mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+        mController.dispatchPendingEvents();
+        verify(mOrganizer).onTaskFragmentInfoChanged(any());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index bec53d7..8b14e98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
@@ -77,6 +78,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
 import java.util.ArrayList;
@@ -271,6 +273,22 @@
     }
 
     @Test
+    public void testRemoveImmediatelyClearsLeash() {
+        final AnimationAdapter animAdapter = mock(AnimationAdapter.class);
+        final WindowToken token = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
+        final SurfaceControl.Transaction t = token.getPendingTransaction();
+        token.startAnimation(t, animAdapter, false /* hidden */,
+                SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
+        final ArgumentCaptor<SurfaceControl> leashCaptor =
+                ArgumentCaptor.forClass(SurfaceControl.class);
+        verify(animAdapter).startAnimation(leashCaptor.capture(), eq(t), anyInt(), any());
+        assertTrue(token.mSurfaceAnimator.hasLeash());
+        token.removeImmediately();
+        assertFalse(token.mSurfaceAnimator.hasLeash());
+        verify(t).remove(eq(leashCaptor.getValue()));
+    }
+
+    @Test
     public void testAddChildByIndex() {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
         final TestWindowContainer root = builder.setLayer(0).build();
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 464389b..30ca162 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -257,8 +257,6 @@
                 + mCardId
                 + ", mEid="
                 + mEid
-                + ", mIccId="
-                + SubscriptionInfo.givePrintableIccid(getIccId())
                 + ", mPhysicalSlotIndex="
                 + mPhysicalSlotIndex
                 + ", mIsRemovable="
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 2b1c8c8..17f34db 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -271,16 +271,13 @@
     @NonNull
     @Override
     public String toString() {
-        return "UiccSlotInfo (mIsActive="
-                + mIsActive
+        return "UiccSlotInfo ("
                 + ", mIsEuicc="
                 + mIsEuicc
                 + ", mCardId="
                 + mCardId
                 + ", cardState="
                 + mCardStateInfo
-                + ", phoneId="
-                + mLogicalSlotIdx
                 + ", mIsExtendedApduSupported="
                 + mIsExtendedApduSupported
                 + ", mIsRemovable="
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 56879c9..c87d8e1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -31,7 +31,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -39,7 +38,6 @@
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -157,11 +155,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index c28466c..f2696d8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -34,12 +34,10 @@
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -117,11 +115,7 @@
 
     @Presubmit
     @Test
-    fun entireScreenCovered() {
-        // This test doesn't work in shell transitions because of b/206086894
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.entireScreenCovered()
-    }
+    fun entireScreenCovered() = testSpec.entireScreenCovered()
 
     @Presubmit
     @Test
@@ -159,11 +153,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index c7f1b99..24b1598 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -32,7 +32,6 @@
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
@@ -134,11 +133,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 46ed0ad..e5d82a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -34,11 +34,9 @@
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -126,11 +124,7 @@
 
     @Presubmit
     @Test
-    fun entireScreenCovered() {
-        // This test doesn't work in shell transitions because of b/206086894
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.entireScreenCovered()
-    }
+    fun entireScreenCovered() = testSpec.entireScreenCovered()
 
     @Presubmit
     @Test
@@ -152,11 +146,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index ebe4be2..87f8ef2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -34,11 +34,9 @@
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -130,11 +128,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f6e5adc..0ad0a03 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -101,8 +101,6 @@
     @Presubmit
     @Test
     fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        // This test doesn't work in shell transitions because of b/204570898
-        assumeFalse(isShellTransitionsEnabled)
         val component = FlickerComponentName("", "RecentTaskScreenshotSurface")
         testSpec.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
@@ -116,8 +114,6 @@
     @Presubmit
     @Test
     fun launcherWindowBecomesInvisible() {
-        // This test doesn't work in shell transitions because of b/204574221
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWm {
             this.isAppWindowVisible(LAUNCHER_COMPONENT)
                     .then()
@@ -127,11 +123,7 @@
 
     @Presubmit
     @Test
-    fun imeWindowIsAlwaysVisible() {
-        // This test doesn't work in shell transitions because of b/204570898
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
-    }
+    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
 
     @Presubmit
     @Test
@@ -202,8 +194,6 @@
     @Presubmit
     @Test
     fun appLayerReplacesLauncher() {
-        // This test doesn't work in shell transitions because of b/204574221
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             this.isVisible(LAUNCHER_COMPONENT)
                 .then()
@@ -219,11 +209,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 0879b98..3f0de7f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,13 +25,11 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Rule
 import org.junit.Test
@@ -167,11 +165,7 @@
      */
     @FlakyTest(bugId = 206753786)
     @Test
-    fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerRotatesScales()
-    }
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index e44bee6..3ae484b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,10 +26,8 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -102,8 +100,6 @@
     @Presubmit
     @Test
     fun appWindowFullScreen() {
-        // This test doesn't work in shell transitions because of b/206101151
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWm {
             this.invoke("isFullScreen") {
                 val appWindow = it.windowState(testApp.`package`)
@@ -139,8 +135,6 @@
     @Presubmit
     @Test
     fun appLayerAlwaysVisible() {
-        // This test doesn't work in shell transitions because of b/206101151
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             isVisible(testApp.component)
         }
@@ -152,8 +146,6 @@
     @Presubmit
     @Test
     fun appLayerRotates() {
-        // This test doesn't work in shell transitions because of b/206101151
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             this.invoke("entireScreenCovered") { entry ->
                 entry.entry.displays.map { display ->
@@ -193,8 +185,6 @@
     @Presubmit
     @Test
     fun focusDoesNotChange() {
-        // This test doesn't work in shell transitions because of b/206101151
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertEventLog {
             this.focusDoesNotChange()
         }
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index 4a724b7..2fbcf9d 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -18,25 +18,29 @@
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import org.junit.Test;
 
 import java.util.HashSet;
 import java.util.Set;
 
-public class VcnCellUnderlyingNetworkTemplateTest {
+public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
     private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>();
     private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
 
     // Package private for use in VcnGatewayConnectionConfigTest
     static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnCellUnderlyingNetworkTemplate.Builder()
-                .setNetworkQuality(NETWORK_QUALITY_OK)
                 .setMetered(MATCH_FORBIDDEN)
+                .setMinUpstreamBandwidthKbps(
+                        TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                        TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+                .setMinDownstreamBandwidthKbps(
+                        TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                        TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
                 .setOperatorPlmnIds(ALLOWED_PLMN_IDS)
                 .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
                 .setRoaming(MATCH_FORBIDDEN)
@@ -47,8 +51,19 @@
     @Test
     public void testBuilderAndGetters() {
         final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
-        assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
         assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(
+                TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinEntryUpstreamBandwidthKbps());
+        assertEquals(
+                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinExitUpstreamBandwidthKbps());
+        assertEquals(
+                TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(
+                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinExitDownstreamBandwidthKbps());
         assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds());
         assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds());
         assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming());
@@ -59,8 +74,14 @@
     public void testBuilderAndGettersForDefaultValues() {
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 new VcnCellUnderlyingNetworkTemplate.Builder().build();
-        assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
         assertEquals(MATCH_ANY, networkPriority.getMetered());
+
+        // Explicitly expect 0, as documented in Javadoc on setter methods.
+        assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps());
+        assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps());
+        assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps());
+
         assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds());
         assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds());
         assertEquals(MATCH_ANY, networkPriority.getRoaming());
@@ -68,6 +89,29 @@
     }
 
     @Test
+    public void testBuilderRequiresStricterEntryCriteria() {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder()
+                    .setMinUpstreamBandwidthKbps(
+                            TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                            TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS);
+
+            fail("Expected IAE for exit threshold > entry threshold");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder()
+                    .setMinDownstreamBandwidthKbps(
+                            TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                            TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS);
+
+            fail("Expected IAE for exit threshold > entry threshold");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testPersistableBundle() {
         final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java
new file mode 100644
index 0000000..399e136
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+public class VcnUnderlyingNetworkTemplateTestBase {
+    // Public for use in NetworkPriorityClassifierTest
+    public static final int TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS = 200;
+    public static final int TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS = 100;
+    public static final int TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS = 400;
+    public static final int TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS = 300;
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
index cb5b47b..4063178 100644
--- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -17,8 +17,6 @@
 
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -28,15 +26,19 @@
 
 import java.util.Set;
 
-public class VcnWifiUnderlyingNetworkTemplateTest {
+public class VcnWifiUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
     private static final String SSID = "TestWifi";
-    private static final int INVALID_NETWORK_QUALITY = -1;
 
     // Package private for use in VcnGatewayConnectionConfigTest
     static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnWifiUnderlyingNetworkTemplate.Builder()
-                .setNetworkQuality(NETWORK_QUALITY_OK)
                 .setMetered(MATCH_FORBIDDEN)
+                .setMinUpstreamBandwidthKbps(
+                        TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                        TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+                .setMinDownstreamBandwidthKbps(
+                        TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                        TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
                 .setSsids(Set.of(SSID))
                 .build();
     }
@@ -44,8 +46,19 @@
     @Test
     public void testBuilderAndGetters() {
         final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
-        assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
         assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(
+                TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinEntryUpstreamBandwidthKbps());
+        assertEquals(
+                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinExitUpstreamBandwidthKbps());
+        assertEquals(
+                TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(
+                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                networkPriority.getMinExitDownstreamBandwidthKbps());
         assertEquals(Set.of(SSID), networkPriority.getSsids());
     }
 
@@ -53,18 +66,37 @@
     public void testBuilderAndGettersForDefaultValues() {
         final VcnWifiUnderlyingNetworkTemplate networkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder().build();
-        assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
         assertEquals(MATCH_ANY, networkPriority.getMetered());
+
+        // Explicitly expect 0, as documented in Javadoc on setter methods..
+        assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps());
+        assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps());
+        assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps());
+
         assertTrue(networkPriority.getSsids().isEmpty());
     }
 
     @Test
-    public void testBuildWithInvalidNetworkQuality() {
+    public void testBuilderRequiresStricterEntryCriteria() {
         try {
             new VcnWifiUnderlyingNetworkTemplate.Builder()
-                    .setNetworkQuality(INVALID_NETWORK_QUALITY);
-            fail("Expected to fail due to the invalid network quality");
-        } catch (Exception expected) {
+                    .setMinUpstreamBandwidthKbps(
+                            TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                            TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS);
+
+            fail("Expected IAE for exit threshold > entry threshold");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            new VcnWifiUnderlyingNetworkTemplate.Builder()
+                    .setMinDownstreamBandwidthKbps(
+                            TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                            TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS);
+
+            fail("Expected IAE for exit threshold > entry threshold");
+        } catch (IllegalArgumentException expected) {
         }
     }
 
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 4bb7de8..6c849b5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -18,7 +18,10 @@
 
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
 
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY;
@@ -76,6 +79,12 @@
     private static final int CARRIER_ID = 1;
     private static final int CARRIER_ID_OTHER = 2;
 
+    private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
+    private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
+
+    private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
+    private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
+
     private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
 
     private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
@@ -83,6 +92,8 @@
                     .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                     .setSignalStrength(WIFI_RSSI)
                     .setSsid(SSID)
+                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
                     .build();
 
     private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
@@ -93,6 +104,8 @@
                     .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                     .setSubscriptionIds(Set.of(SUB_ID))
                     .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
                     .build();
 
     private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
@@ -146,7 +159,6 @@
     public void testMatchWithoutNotMeteredBit() {
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
-                        .setNetworkQuality(NETWORK_QUALITY_OK)
                         .setMetered(MATCH_FORBIDDEN)
                         .build();
 
@@ -161,11 +173,133 @@
                         null /* carrierConfig */));
     }
 
+    private void verifyMatchesPriorityRuleForUpstreamBandwidth(
+            int entryUpstreamBandwidth,
+            int exitUpstreamBandwidth,
+            UnderlyingNetworkRecord currentlySelected,
+            boolean expectMatch) {
+        final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkTemplate.Builder()
+                        .setMinUpstreamBandwidthKbps(entryUpstreamBandwidth, exitUpstreamBandwidth)
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesPriorityRule(
+                        mVcnContext,
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        currentlySelected,
+                        null /* carrierConfig */));
+    }
+
+    private void verifyMatchesPriorityRuleForDownstreamBandwidth(
+            int entryDownstreamBandwidth,
+            int exitDownstreamBandwidth,
+            UnderlyingNetworkRecord currentlySelected,
+            boolean expectMatch) {
+        final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkTemplate.Builder()
+                        .setMinDownstreamBandwidthKbps(
+                                entryDownstreamBandwidth, exitDownstreamBandwidth)
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesPriorityRule(
+                        mVcnContext,
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        currentlySelected,
+                        null /* carrierConfig */));
+    }
+
+    @Test
+    public void testMatchWithEntryUpstreamBandwidthEquals() {
+        verifyMatchesPriorityRuleForUpstreamBandwidth(
+                TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                null /* currentlySelected */,
+                true);
+    }
+
+    @Test
+    public void testMatchWithEntryUpstreamBandwidthTooLow() {
+        verifyMatchesPriorityRuleForUpstreamBandwidth(
+                LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+                LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+                null /* currentlySelected */,
+                false);
+    }
+
+    @Test
+    public void testMatchWithEntryDownstreamBandwidthEquals() {
+        verifyMatchesPriorityRuleForDownstreamBandwidth(
+                TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                null /* currentlySelected */,
+                true);
+    }
+
+    @Test
+    public void testMatchWithEntryDownstreamBandwidthTooLow() {
+        verifyMatchesPriorityRuleForDownstreamBandwidth(
+                LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+                LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+                null /* currentlySelected */,
+                false);
+    }
+
+    @Test
+    public void testMatchWithExitUpstreamBandwidthEquals() {
+        verifyMatchesPriorityRuleForUpstreamBandwidth(
+                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
+                mWifiNetworkRecord,
+                true);
+    }
+
+    @Test
+    public void testMatchWithExitUpstreamBandwidthTooLow() {
+        verifyMatchesPriorityRuleForUpstreamBandwidth(
+                LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+                LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
+                mWifiNetworkRecord,
+                false);
+    }
+
+    @Test
+    public void testMatchWithExitDownstreamBandwidthEquals() {
+        verifyMatchesPriorityRuleForDownstreamBandwidth(
+                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
+                mWifiNetworkRecord,
+                true);
+    }
+
+    @Test
+    public void testMatchWithExitDownstreamBandwidthTooLow() {
+        verifyMatchesPriorityRuleForDownstreamBandwidth(
+                LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+                LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
+                mWifiNetworkRecord,
+                false);
+    }
+
     private void verifyMatchWifi(
             boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) {
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
-                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setMinUpstreamBandwidthKbps(
+                                TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+                        .setMinDownstreamBandwidthKbps(
+                                TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
                         .build();
         final UnderlyingNetworkRecord selectedNetworkRecord =
                 isSelectedNetwork ? mWifiNetworkRecord : null;
@@ -214,7 +348,12 @@
         final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER;
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
-                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setMinUpstreamBandwidthKbps(
+                                TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                                TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+                        .setMinDownstreamBandwidthKbps(
+                                TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                                TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
                         .setSsids(Set.of(nwPrioritySsid))
                         .build();
 
@@ -238,7 +377,13 @@
     }
 
     private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() {
-        return new VcnCellUnderlyingNetworkTemplate.Builder().setNetworkQuality(NETWORK_QUALITY_OK);
+        return new VcnCellUnderlyingNetworkTemplate.Builder()
+                .setMinUpstreamBandwidthKbps(
+                        TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
+                        TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS)
+                .setMinDownstreamBandwidthKbps(
+                        TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
+                        TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS);
     }
 
     @Test
diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh
index 36bea57..95b43cd 100755
--- a/tools/aosp/aosp_sha.sh
+++ b/tools/aosp/aosp_sha.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 LOCAL_DIR="$( dirname "${BASH_SOURCE}" )"
 
-if git branch -vv | grep -q -E "^\*[^\[]+\[aosp/"; then
+if git log -n 1 --format='%D' HEAD@{upstream} | grep -q aosp/; then
     # Change appears to be in AOSP
     exit 0
 elif git log -n 1 --format='%B' $1 | grep -q -E "^Ignore-AOSP-First: .+" ; then