Merge "[NS D01] Remove candidates that don't satisfy the request."
diff --git a/Android.bp b/Android.bp
index df852bd..412099d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -372,6 +372,7 @@
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
+        "com.android.sysprop.init",
         "PlatformProperties",
     ],
     sdk_version: "core_platform",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 50d23ad2..4616ced 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -197,20 +197,17 @@
             api_file: "api/module-lib-current.txt",
             removed_api_file: "api/module-lib-removed.txt",
         },
-        // TODO(b/147559833) enable the compatibility check against the last release API
-        // and the API lint
-        //last_released: {
-        //    api_file: ":last-released-module-lib-api",
-        //    removed_api_file: "api/module-lib-removed.txt",
-        //    baseline_file: ":module-lib-api-incompatibilities-with-last-released"
-        //},
-        //api_lint: {
-        //    enabled: true,
-        //    new_since: ":last-released-module-lib-api",
-        //    baseline_file: "api/module-lib-lint-baseline.txt",
-        //},
+        last_released: {
+            api_file: ":last-released-module-lib-api",
+            removed_api_file: "api/module-lib-removed.txt",
+            baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-module-lib-api",
+            baseline_file: "api/module-lib-lint-baseline.txt",
+        },
     },
-    //jdiff_enabled: true,
 }
 
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 088cadb..b905273 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -606,10 +606,6 @@
         return hasLateConstraint;
     }
 
-    private static boolean kindofEqualsBundle(BaseBundle a, BaseBundle b) {
-        return (a == b) || (a != null && a.kindofEquals(b));
-    }
-
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof JobInfo)) {
@@ -620,11 +616,11 @@
             return false;
         }
         // XXX won't be correct if one is parcelled and the other not.
-        if (!kindofEqualsBundle(extras, j.extras)) {
+        if (!BaseBundle.kindofEquals(extras, j.extras)) {
             return false;
         }
         // XXX won't be correct if one is parcelled and the other not.
-        if (!kindofEqualsBundle(transientExtras, j.transientExtras)) {
+        if (!BaseBundle.kindofEquals(transientExtras, j.transientExtras)) {
             return false;
         }
         // XXX for now we consider two different clip data objects to be different,
@@ -896,7 +892,7 @@
          * @param flags Flags for the observer.
          */
         public TriggerContentUri(@NonNull Uri uri, @Flags int flags) {
-            mUri = uri;
+            mUri = Objects.requireNonNull(uri);
             mFlags = flags;
         }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 08b1c2b..abf78c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -56,6 +56,12 @@
  * instantiate this class directly; instead, retrieve it through
  * {@link android.content.Context#getSystemService
  * Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 30
+ * ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
+ * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative
+ * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
+ * to throttle apps that show buggy behavior, regardless of target SDK version.
  */
 @SystemService(Context.JOB_SCHEDULER_SERVICE)
 public abstract class JobScheduler {
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index 4ffcf8a..4b4fb96 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -80,27 +80,22 @@
     }
 
     /**
-     * Add the specified package to the power save whitelist.
-     *
-     * @return true if the package was successfully added to the whitelist
+     * Add the specified package to the permanent power save whitelist.
      */
     @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
-    public boolean addToWhitelist(@NonNull String packageName) {
-        return addToWhitelist(Collections.singletonList(packageName)) == 1;
+    public void addToWhitelist(@NonNull String packageName) {
+        addToWhitelist(Collections.singletonList(packageName));
     }
 
     /**
-     * Add the specified packages to the power save whitelist.
-     *
-     * @return the number of packages that were successfully added to the whitelist
+     * Add the specified packages to the permanent power save whitelist.
      */
     @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
-    public int addToWhitelist(@NonNull List<String> packageNames) {
+    public void addToWhitelist(@NonNull List<String> packageNames) {
         try {
-            return mService.addPowerSaveWhitelistApps(packageNames);
+            mService.addPowerSaveWhitelistApps(packageNames);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
-            return 0;
         }
     }
 
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 1072406..7833a03 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import android.annotation.NonNull;
 import android.app.job.JobInfo;
 import android.util.proto.ProtoOutputStream;
 
@@ -44,6 +45,10 @@
     void removeBackingUpUid(int uid);
     void clearAllBackingUpUids();
 
+    /** Returns the package responsible for backing up media on the device. */
+    @NonNull
+    String getMediaBackupPackage();
+
     /**
      * The user has started interacting with the app.  Take any appropriate action.
      */
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index d2d942a..dc72d6d 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -85,6 +85,7 @@
     /**
      * Checks if an app has been idle for a while and filters out apps that are excluded.
      * It returns false if the current system state allows all apps to be considered active.
+     * This happens if the device is plugged in or otherwise temporarily allowed to make exceptions.
      * Called by interface impls.
      */
     boolean isAppIdleFiltered(String packageName, int appId, int userId,
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 b516279..ff7944d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,8 +37,6 @@
 import android.app.job.JobWorkItem;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -57,7 +55,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -80,6 +77,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.ArrayUtils;
@@ -90,7 +88,6 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.compat.PlatformCompat;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.controllers.BackgroundJobsController;
@@ -155,16 +152,6 @@
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
 
-    /**
-     * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
-     * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
-     * calls the APIs too frequently.
-     */
-    @ChangeId
-    // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
-    protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
-
     @VisibleForTesting
     public static Clock sSystemClock = Clock.systemUTC();
 
@@ -262,9 +249,11 @@
      */
     private final List<JobRestriction> mJobRestrictions;
 
+    @NonNull
+    private final String mSystemGalleryPackage;
+
     private final CountQuotaTracker mQuotaTracker;
     private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
-    private final PlatformCompat mPlatformCompat;
 
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -986,9 +975,7 @@
                 Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
                 mAppStandbyInternal.restrictApp(
                         pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
-                if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
-                        && mPlatformCompat.isChangeEnabledByPackageName(
-                                CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+                if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
                     final boolean isDebuggable;
                     synchronized (mLock) {
                         if (!mDebuggableApps.containsKey(packageName)) {
@@ -1370,8 +1357,6 @@
         // Set up the app standby bucketing tracker
         mStandbyTracker = new StandbyTracker();
         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
-        mPlatformCompat =
-                (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
         mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
         mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
                 mConstants.API_QUOTA_SCHEDULE_COUNT,
@@ -1413,6 +1398,9 @@
         mJobRestrictions = new ArrayList<>();
         mJobRestrictions.add(new ThermalStatusRestriction(this));
 
+        mSystemGalleryPackage = Objects.requireNonNull(
+                context.getString(R.string.config_systemGallery));
+
         // If the job store determined that it can't yet reschedule persisted jobs,
         // we need to start watching the clock.
         if (!mJobs.jobTimesInflatedValid()) {
@@ -2378,6 +2366,11 @@
         }
 
         @Override
+        public String getMediaBackupPackage() {
+            return mSystemGalleryPackage;
+        }
+
+        @Override
         public void reportAppUsage(String packageName, int userId) {
             JobSchedulerService.this.reportAppUsage(packageName, userId);
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index a775cf5..5fcd774 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -344,7 +344,7 @@
                     mContext.getContentResolver().unregisterContentObserver(obs);
                     ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser =
                             mObservers.get(obs.mUserId);
-                    if (observerOfUser !=  null) {
+                    if (observerOfUser != null) {
                         observerOfUser.remove(obs.mUri);
                     }
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 1e89158..789f20b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -19,6 +19,7 @@
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.app.AppGlobals;
@@ -30,6 +31,7 @@
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.MediaStore;
 import android.text.format.DateFormat;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -207,6 +209,18 @@
      */
     private int mDynamicConstraints = 0;
 
+    /**
+     * Indicates whether the job is responsible for backing up media, so we can be lenient in
+     * applying standby throttling.
+     *
+     * Doesn't exempt jobs with a deadline constraint, as they can be started without any content or
+     * network changes, in which case this exemption does not make sense.
+     *
+     * TODO(b/149519887): Use a more explicit signal, maybe an API flag, that the scheduling package
+     * needs to provide at the time of scheduling a job.
+     */
+    private final boolean mHasMediaBackupExemption;
+
     // Set to true if doze constraint was satisfied due to app being whitelisted.
     public boolean dozeWhitelisted;
 
@@ -415,9 +429,11 @@
         this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.numFailures = numFailures;
 
+        boolean requiresNetwork = false;
         int requiredConstraints = job.getConstraintFlags();
         if (job.getRequiredNetwork() != null) {
             requiredConstraints |= CONSTRAINT_CONNECTIVITY;
+            requiresNetwork = true;
         }
         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
             requiredConstraints |= CONSTRAINT_TIMING_DELAY;
@@ -425,8 +441,16 @@
         if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
             requiredConstraints |= CONSTRAINT_DEADLINE;
         }
+        boolean mediaOnly = false;
         if (job.getTriggerContentUris() != null) {
             requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
+            mediaOnly = true;
+            for (JobInfo.TriggerContentUri uri : job.getTriggerContentUris()) {
+                if (!MediaStore.AUTHORITY.equals(uri.getUri().getAuthority())) {
+                    mediaOnly = false;
+                    break;
+                }
+            }
         }
         this.requiredConstraints = requiredConstraints;
         mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
@@ -450,6 +474,9 @@
             // our source UID into place.
             job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
         }
+        final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
+        mHasMediaBackupExemption = !job.hasLateConstraint() && mediaOnly && requiresNetwork
+                && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
     }
 
     /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -545,7 +572,6 @@
 
         int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                 sourceUserId, elapsedNow);
-        JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
         return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
                 standbyBucket, tag, 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
@@ -734,7 +760,14 @@
             // like other ACTIVE apps.
             return ACTIVE_INDEX;
         }
-        return getStandbyBucket();
+        final int actualBucket = getStandbyBucket();
+        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+                && mHasMediaBackupExemption) {
+            // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
+            // source package may not have been used directly in a while.
+            return Math.min(WORKING_INDEX, actualBucket);
+        }
+        return actualBucket;
     }
 
     /** Returns the real standby bucket of the job. */
@@ -1615,11 +1648,11 @@
                     pw.println();
                 }
             }
-            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+            if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) {
                 pw.print(prefix); pw.print("  Extras: ");
                 pw.println(job.getExtras().toShortString());
             }
-            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+            if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) {
                 pw.print(prefix); pw.print("  Transient extras: ");
                 pw.println(job.getTransientExtras().toShortString());
             }
@@ -1836,10 +1869,10 @@
                             job.getTriggerContentMaxDelay());
                 }
             }
-            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+            if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) {
                 job.getExtras().dumpDebug(proto, JobStatusDumpProto.JobInfo.EXTRAS);
             }
-            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+            if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) {
                 job.getTransientExtras().dumpDebug(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
             }
             if (job.getClipData() != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index f1bfa04..e343478 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -48,6 +48,7 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 
 import android.annotation.NonNull;
@@ -71,9 +72,8 @@
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
 import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Environment;
@@ -285,6 +285,7 @@
     long mInitialForegroundServiceStartTimeoutMillis;
 
     private volatile boolean mAppIdleEnabled;
+    private boolean mIsCharging;
     private boolean mSystemServicesReady = false;
     // There was a system update, defaults need to be initialized after services are ready
     private boolean mPendingInitializeDefaults;
@@ -360,6 +361,11 @@
         mHandler = new AppStandbyHandler(mInjector.getLooper());
         mPackageManager = mContext.getPackageManager();
 
+        DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver();
+        IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+        deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+        mContext.registerReceiver(deviceStateReceiver, deviceStates);
+
         synchronized (mAppIdleLock) {
             mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
                     mInjector.elapsedRealtime());
@@ -417,6 +423,8 @@
             if (mPendingOneTimeCheckIdleStates) {
                 postOneTimeCheckIdleStates();
             }
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            setChargingState(mInjector.isCharging());
         }
     }
 
@@ -515,6 +523,16 @@
                 appUsage.bucketingReason, false);
     }
 
+    @VisibleForTesting
+    void setChargingState(boolean isCharging) {
+        synchronized (mAppIdleLock) {
+            if (mIsCharging != isCharging) {
+                if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+                mIsCharging = isCharging;
+            }
+        }
+    }
+
     @Override
     public void postCheckIdleStates(int userId) {
         mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -977,6 +995,11 @@
         if (isAppSpecial(packageName, appId, userId)) {
             return false;
         } else {
+            synchronized (mAppIdleLock) {
+                if (!mAppIdleEnabled || mIsCharging) {
+                    return false;
+                }
+            }
             return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
         }
     }
@@ -1543,6 +1566,8 @@
 
         pw.println();
         pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+        pw.print(" mIsCharging=");
+        pw.print(mIsCharging);
         pw.println();
         pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
         pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
@@ -1560,6 +1585,7 @@
         private final Looper mLooper;
         private IDeviceIdleController mDeviceIdleController;
         private IBatteryStats mBatteryStats;
+        private BatteryManager mBatteryManager;
         private PackageManagerInternal mPackageManagerInternal;
         private DisplayManager mDisplayManager;
         private PowerManager mPowerManager;
@@ -1593,6 +1619,7 @@
                 mDisplayManager = (DisplayManager) mContext.getSystemService(
                         Context.DISPLAY_SERVICE);
                 mPowerManager = mContext.getSystemService(PowerManager.class);
+                mBatteryManager = mContext.getSystemService(BatteryManager.class);
 
                 final ActivityManager activityManager =
                         (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1630,6 +1657,10 @@
             return buildFlag && runtimeFlag;
         }
 
+        boolean isCharging() {
+            return mBatteryManager.isCharging();
+        }
+
         boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
             return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
         }
@@ -1766,15 +1797,19 @@
         }
     };
 
-    private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
-
-    private final ConnectivityManager.NetworkCallback mNetworkCallback
-            = new ConnectivityManager.NetworkCallback() {
+    private class DeviceStateReceiver extends BroadcastReceiver {
         @Override
-        public void onAvailable(Network network) {
-            mConnectivityManager.unregisterNetworkCallback(this);
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case BatteryManager.ACTION_CHARGING:
+                    setChargingState(true);
+                    break;
+                case BatteryManager.ACTION_DISCHARGING:
+                    setChargingState(false);
+                    break;
+            }
         }
-    };
+    }
 
     private final DisplayManager.DisplayListener mDisplayListener
             = new DisplayManager.DisplayListener() {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 3eed26b..7d18578 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -50,6 +50,7 @@
 import com.google.android.exoplayer2.upstream.DataSpec;
 import com.google.android.exoplayer2.upstream.TransferListener;
 import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.video.ColorInfo;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -810,19 +811,17 @@
     // Private static methods.
 
     private static MediaFormat toMediaFormat(Format format) {
-
         MediaFormat result = new MediaFormat();
-        if (format.bitrate != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
-        }
-        if (format.channelCount != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
-        }
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
 
-        if (format.colorInfo != null) {
-            result.setInteger(MediaFormat.KEY_COLOR_TRANSFER, format.colorInfo.colorTransfer);
-            result.setInteger(MediaFormat.KEY_COLOR_RANGE, format.colorInfo.colorRange);
-            result.setInteger(MediaFormat.KEY_COLOR_STANDARD, format.colorInfo.colorSpace);
+        ColorInfo colorInfo = format.colorInfo;
+        if (colorInfo != null) {
+            setOptionalMediaFormatInt(
+                    result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
+            setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
+            setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
+
             if (format.colorInfo.hdrStaticInfo != null) {
                 result.setByteBuffer(
                         MediaFormat.KEY_HDR_STATIC_INFO,
@@ -830,63 +829,50 @@
             }
         }
 
-        if (format.sampleMimeType != null) {
-            result.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
-        }
-        if (format.codecs != null) {
-            result.setString(MediaFormat.KEY_CODECS_STRING, format.codecs);
-        }
+        setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
+        setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
         if (format.frameRate != Format.NO_VALUE) {
             result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate);
         }
-        if (format.width != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_WIDTH, format.width);
-        }
-        if (format.height != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_HEIGHT, format.height);
-        }
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
+
         List<byte[]> initData = format.initializationData;
         if (initData != null) {
             for (int i = 0; i < initData.size(); i++) {
                 result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
             }
         }
-        if (format.language != null) {
-            result.setString(MediaFormat.KEY_LANGUAGE, format.language);
-        }
-        if (format.maxInputSize != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
-        }
-        if (format.pcmEncoding != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
-        }
-        if (format.rotationDegrees != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees);
-        }
-        if (format.sampleRate != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
-        }
+        setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
+
         int selectionFlags = format.selectionFlags;
-        if ((selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) {
-            result.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 1);
+        result.setInteger(
+                MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
+        result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
+        result.setInteger(
+                MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
+
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
+        setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
+
+        if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) {
+            int parWidth = 1;
+            int parHeight = 1;
+            if (format.pixelWidthHeightRatio < 1.0f) {
+                parHeight = 1 << 30;
+                parWidth = (int) (format.pixelWidthHeightRatio * parHeight);
+            } else if (format.pixelWidthHeightRatio > 1.0f) {
+                parWidth = 1 << 30;
+                parHeight = (int) (parWidth / format.pixelWidthHeightRatio);
+            }
+            result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth);
+            result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight);
+            result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio);
         }
-        if ((selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
-            result.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
-        }
-        if ((selectionFlags & C.SELECTION_FLAG_FORCED) != 0) {
-            result.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
-        }
-        if (format.encoderDelay != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
-        }
-        if (format.encoderPadding != Format.NO_VALUE) {
-            result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
-        }
-        // TODO: Implement float to fraction conversion.
-        // if (format.pixelWidthHeightRatio != Format.NO_VALUE) {
-        //     result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, );
-        //     result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, );
-        // }
 
         // LACK OF SUPPORT FOR:
         //    format.accessibilityChannel;
@@ -899,6 +885,19 @@
         return result;
     }
 
+    private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
+        if (value != Format.NO_VALUE) {
+            mediaFormat.setInteger(key, value);
+        }
+    }
+
+    private static void setOptionalMediaFormatString(
+            MediaFormat mediaFormat, String key, @Nullable String value) {
+        if (value != null) {
+            mediaFormat.setString(key, value);
+        }
+    }
+
     private static DrmInitData toFrameworkDrmInitData(
             com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
         // TODO: Implement.
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 63a853a..ab669d4 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_visibility: [ ":__pkg__" ]
+}
+
 genrule {
     name: "statslog-statsd-java-gen",
     tools: ["stats-log-api-gen"],
@@ -25,6 +29,9 @@
     srcs: [
         ":statslog-statsd-java-gen",
     ],
+    visibility: [
+        "//cts/hostsidetests/statsd/apps:__subpackages__",
+    ]
 }
 
 filegroup {
@@ -34,6 +41,9 @@
         ":framework-statsd-aidl-sources",
         ":statslog-statsd-java-gen",
     ],
+    visibility: [
+        "//frameworks/base", // For the "global" stubs.
+    ],
 }
 
 java_defaults {
@@ -139,6 +149,10 @@
         "framework-statsd-defaults",
     ],
     srcs: [ ":framework-statsd-stubs-srcs-publicapi" ],
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/statsd", // statsd apex
+    ]
 }
 
 java_library {
@@ -147,6 +161,10 @@
         "framework-statsd-defaults",
     ],
     srcs: [ ":framework-statsd-stubs-srcs-systemapi" ],
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/statsd", // statsd apex
+    ]
 }
 
 java_library {
@@ -155,4 +173,9 @@
         "framework-statsd-defaults",
     ],
     srcs: [ ":framework-statsd-stubs-srcs-module_libs_api" ],
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/statsd", // statsd apex
+        "//frameworks/opt/net/wifi/service" // wifi service
+    ]
 }
diff --git a/api/current.txt b/api/current.txt
index 611fec3..7bfd9a4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -289,6 +289,7 @@
     field public static final int allowBackup = 16843392; // 0x1010280
     field public static final int allowClearUserData = 16842757; // 0x1010005
     field public static final int allowEmbedded = 16843765; // 0x10103f5
+    field public static final int allowNativeHeapPointerTagging = 16844307; // 0x1010613
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
@@ -334,9 +335,6 @@
     field public static final int autoUrlDetect = 16843404; // 0x101028c
     field public static final int autoVerify = 16844014; // 0x10104ee
     field public static final int autofillHints = 16844118; // 0x1010556
-    field public static final int autofillInlineSuggestionChip = 16844307; // 0x1010613
-    field public static final int autofillInlineSuggestionSubtitle = 16844309; // 0x1010615
-    field public static final int autofillInlineSuggestionTitle = 16844308; // 0x1010614
     field public static final int autofilledHighlight = 16844136; // 0x1010568
     field public static final int background = 16842964; // 0x10100d4
     field public static final int backgroundDimAmount = 16842802; // 0x1010032
@@ -1081,6 +1079,7 @@
     field public static final int preferenceScreenStyle = 16842891; // 0x101008b
     field public static final int preferenceStyle = 16842894; // 0x101008e
     field public static final int presentationTheme = 16843712; // 0x10103c0
+    field public static final int preserveLegacyExternalStorage = 16844308; // 0x1010614
     field public static final int previewImage = 16843482; // 0x10102da
     field public static final int primaryContentAlpha = 16844114; // 0x1010552
     field public static final int priority = 16842780; // 0x101001c
@@ -2258,7 +2257,6 @@
     field public static final int ThemeOverlay_Material_Dialog = 16974550; // 0x10302d6
     field public static final int ThemeOverlay_Material_Dialog_Alert = 16974551; // 0x10302d7
     field public static final int ThemeOverlay_Material_Light = 16974410; // 0x103024a
-    field public static final int Theme_AutofillInlineSuggestion = 16974565; // 0x10302e5
     field public static final int Theme_Black = 16973832; // 0x1030008
     field public static final int Theme_Black_NoTitleBar = 16973833; // 0x1030009
     field public static final int Theme_Black_NoTitleBar_Fullscreen = 16973834; // 0x103000a
@@ -2876,6 +2874,7 @@
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>);
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
+    field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
     field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
     field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
@@ -2883,6 +2882,7 @@
     field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
     field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
+    field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
     field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
     field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
@@ -2890,6 +2890,7 @@
     field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
     field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
     field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
+    field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
     field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
     field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22
     field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23
@@ -2954,7 +2955,7 @@
   }
 
   public static final class AccessibilityService.ScreenshotResult {
-    method @Nullable public android.graphics.ColorSpace getColorSpace();
+    method @NonNull public android.graphics.ColorSpace getColorSpace();
     method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
     method public long getTimestamp();
   }
@@ -6422,9 +6423,13 @@
   public class StatusBarManager {
   }
 
-  public final class SyncNotedAppOp {
+  public final class SyncNotedAppOp implements android.os.Parcelable {
+    ctor public SyncNotedAppOp(@IntRange(from=0L) int, @Nullable String);
+    method public int describeContents();
     method @Nullable public String getFeatureId();
     method @NonNull public String getOp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.SyncNotedAppOp> CREATOR;
   }
 
   @Deprecated public class TabActivity extends android.app.ActivityGroup {
@@ -6917,6 +6922,7 @@
     method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
     method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
     method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int);
+    method public boolean hasLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
     method public boolean installCaCert(@Nullable android.content.ComponentName, byte[]);
     method public boolean installExistingPackage(@NonNull android.content.ComponentName, String);
     method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate, @NonNull String);
@@ -6935,7 +6941,6 @@
     method public boolean isDeviceOwnerApp(String);
     method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
     method public boolean isLockTaskPermitted(String);
-    method public boolean isLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
     method public boolean isLogoutEnabled();
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
@@ -6982,6 +6987,7 @@
     method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
     method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
+    method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
@@ -7001,7 +7007,6 @@
     method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
     method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
-    method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean);
     method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
     method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
@@ -7238,7 +7243,7 @@
   public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
-    method public boolean isFactoryResetProtectionDisabled();
+    method public boolean isFactoryResetProtectionEnabled();
     method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
   }
@@ -7247,7 +7252,7 @@
     ctor public FactoryResetProtectionPolicy.Builder();
     method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
     method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
-    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionEnabled(boolean);
   }
 
   public class FreezePeriod {
@@ -10141,7 +10146,6 @@
     field public static final String ALARM_SERVICE = "alarm";
     field public static final String APPWIDGET_SERVICE = "appwidget";
     field public static final String APP_OPS_SERVICE = "appops";
-    field public static final String APP_SEARCH_SERVICE = "app_search";
     field public static final String AUDIO_SERVICE = "audio";
     field public static final String BATTERY_SERVICE = "batterymanager";
     field public static final int BIND_ABOVE_CLIENT = 8; // 0x8
@@ -12068,7 +12072,7 @@
     field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
     field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
-    field public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub";
+    field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
     field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
     field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
@@ -12703,8 +12707,7 @@
 
   public class Resources {
     ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
-    method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader);
-    method public void clearLoaders();
+    method public void addLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
     method public final void finishPreloading();
     method public final void flushLayoutCache();
     method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
@@ -12731,7 +12734,6 @@
     method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
     method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
-    method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders();
     method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12759,8 +12761,7 @@
     method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
     method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader);
-    method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>);
+    method public void removeLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
     method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
     field @AnyRes public static final int ID_NULL = 0; // 0x0
   }
@@ -27044,7 +27045,7 @@
     method @NonNull public String getId();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
-    method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferrableRoutes();
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferableRoutes();
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
@@ -27424,7 +27425,7 @@
   public final class RouteDiscoveryPreference implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
-    method public boolean isActiveScan();
+    method public boolean shouldPerformActiveScan();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
   }
@@ -27433,8 +27434,8 @@
     ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
     ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
     method @NonNull public android.media.RouteDiscoveryPreference build();
-    method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
   }
 
   public final class RoutingSessionInfo implements android.os.Parcelable {
@@ -27445,7 +27446,7 @@
     method @NonNull public String getId();
     method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
     method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
-    method @NonNull public java.util.List<java.lang.String> getTransferrableRoutes();
+    method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
@@ -27459,16 +27460,16 @@
     method @NonNull public android.media.RoutingSessionInfo.Builder addDeselectableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder addSelectableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder addSelectedRoute(@NonNull String);
-    method @NonNull public android.media.RoutingSessionInfo.Builder addTransferrableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder addTransferableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo build();
     method @NonNull public android.media.RoutingSessionInfo.Builder clearDeselectableRoutes();
     method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectableRoutes();
     method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectedRoutes();
-    method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferrableRoutes();
+    method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferableRoutes();
     method @NonNull public android.media.RoutingSessionInfo.Builder removeDeselectableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectedRoute(@NonNull String);
-    method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferrableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
@@ -30816,7 +30817,7 @@
 
   public class AudioGroup {
     ctor @Deprecated public AudioGroup();
-    ctor public AudioGroup(@Nullable android.content.Context);
+    ctor public AudioGroup(@NonNull android.content.Context);
     method public void clear();
     method public int getMode();
     method public android.net.rtp.AudioStream[] getStreams();
@@ -37314,6 +37315,7 @@
     method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
     method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener);
     method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback);
+    field @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE";
     field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
     field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
     field public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
@@ -43047,6 +43049,7 @@
     method @NonNull public android.service.autofill.FillResponse build();
     method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
+    method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
     method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
     method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
     method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
@@ -43964,7 +43967,7 @@
 
 package android.service.quickaccesswallet {
 
-  public final class GetWalletCardsCallback {
+  public interface GetWalletCardsCallback {
     method public void onFailure(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
     method public void onSuccess(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
   }
@@ -44005,7 +44008,6 @@
     method public abstract void onWalletCardsRequested(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.GetWalletCardsCallback);
     method public abstract void onWalletDismissed();
     method public final void sendWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent);
-    field public static final String ACTION_DISMISS_WALLET = "android.service.quickaccesswallet.action.DISMISS_WALLET";
     field public static final String ACTION_VIEW_WALLET = "android.service.quickaccesswallet.action.VIEW_WALLET";
     field public static final String ACTION_VIEW_WALLET_SETTINGS = "android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
     field public static final String SERVICE_INTERFACE = "android.service.quickaccesswallet.QuickAccessWalletService";
@@ -45514,7 +45516,7 @@
     field public static final int DIRECTION_INCOMING = 0; // 0x0
     field public static final int DIRECTION_OUTGOING = 1; // 0x1
     field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
-    field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+    field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_CONFERENCE = 1; // 0x1
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -45603,7 +45605,8 @@
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
     method public final int getConnectionProperties();
-    method public final long getConnectionTime();
+    method public final long getConnectionStartElapsedRealtimeMillis();
+    method @IntRange(from=0) public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
@@ -45633,8 +45636,9 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConnectionCapabilities(int);
     method public final void setConnectionProperties(int);
-    method public final void setConnectionStartElapsedRealTime(long);
-    method public final void setConnectionTime(long);
+    method @Deprecated public final void setConnectionStartElapsedRealTime(long);
+    method public final void setConnectionStartElapsedRealtimeMillis(long);
+    method public final void setConnectionTime(@IntRange(from=0) long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
     method public final void setExtras(@Nullable android.os.Bundle);
@@ -45794,7 +45798,7 @@
     field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
     field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
     field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
-    field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+    field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
@@ -46891,7 +46895,7 @@
   }
 
   public final class CellIdentityGsm extends android.telephony.CellIdentity {
-    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
     method public int getArfcn();
     method public int getBsic();
     method public int getCid();
@@ -46907,7 +46911,7 @@
   }
 
   public final class CellIdentityLte extends android.telephony.CellIdentity {
-    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
     method @NonNull public java.util.List<java.lang.Integer> getBands();
     method public int getBandwidth();
     method public int getCi();
@@ -46925,7 +46929,7 @@
   }
 
   public final class CellIdentityNr extends android.telephony.CellIdentity {
-    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
     method @NonNull public java.util.List<java.lang.Integer> getBands();
     method @Nullable public String getMccString();
     method @Nullable public String getMncString();
@@ -46938,7 +46942,7 @@
   }
 
   public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
-    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
     method public int getCid();
     method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
     method public int getCpid();
@@ -46952,7 +46956,7 @@
   }
 
   public final class CellIdentityWcdma extends android.telephony.CellIdentity {
-    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
     method public int getCid();
     method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
     method public int getLac();
@@ -46973,7 +46977,7 @@
     method @NonNull public abstract android.telephony.CellIdentity getCellIdentity();
     method @NonNull public abstract android.telephony.CellSignalStrength getCellSignalStrength();
     method @Deprecated public long getTimeStamp();
-    method public long getTimestampNanos();
+    method public long getTimestampMillis();
     method public boolean isRegistered();
     field public static final int CONNECTION_NONE = 0; // 0x0
     field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
@@ -47133,6 +47137,19 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ClosedSubscriberGroupInfo> CREATOR;
   }
 
+  public final class DisplayInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getNetworkType();
+    method public int getOverrideNetworkType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DisplayInfo> CREATOR;
+    field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
+    field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
+    field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+  }
+
   public class IccOpenLogicalChannelResponse implements android.os.Parcelable {
     method public int describeContents();
     method public int getChannel();
@@ -47229,7 +47246,7 @@
     method @Nullable public android.telephony.CellIdentity getCellIdentity();
     method public int getDomain();
     method public int getNrState();
-    method @NonNull public String getRegisteredPlmn();
+    method @Nullable public String getRegisteredPlmn();
     method public int getTransportType();
     method public boolean isRegistered();
     method public boolean isRoaming();
@@ -47389,6 +47406,7 @@
     method public void onDataActivity(int);
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
+    method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.DisplayInfo);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
     method public void onMessageWaitingIndicatorChanged(boolean);
     method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
@@ -47406,6 +47424,7 @@
     field public static final int LISTEN_CELL_LOCATION = 16; // 0x10
     field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
     field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
+    field public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
     field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
     field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
@@ -47488,7 +47507,7 @@
     method @Deprecated public int getGsmBitErrorRate();
     method @Deprecated public int getGsmSignalStrength();
     method public int getLevel();
-    method public long getTimestampNanos();
+    method public long getTimestampMillis();
     method @Deprecated public boolean isGsm();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalStrength> CREATOR;
@@ -52138,6 +52157,8 @@
     field public static final int CONTEXT_CLICK = 6; // 0x6
     field public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2
     field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1
+    field public static final int GESTURE_END = 13; // 0xd
+    field public static final int GESTURE_START = 12; // 0xc
     field public static final int KEYBOARD_PRESS = 3; // 0x3
     field public static final int KEYBOARD_RELEASE = 7; // 0x7
     field public static final int KEYBOARD_TAP = 3; // 0x3
diff --git a/api/module-lib-lint-baseline.txt b/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..6e59596
--- /dev/null
+++ b/api/module-lib-lint-baseline.txt
@@ -0,0 +1,33 @@
+// Baseline format: 1.0
+ActionValue: android.net.TetheringConstants#EXTRA_ADD_TETHER_TYPE:
+    Inconsistent extra value; expected `android.net.extra.ADD_TETHER_TYPE`, was `extraAddTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_PROVISION_CALLBACK:
+    Inconsistent extra value; expected `android.net.extra.PROVISION_CALLBACK`, was `extraProvisionCallback`
+ActionValue: android.net.TetheringConstants#EXTRA_REM_TETHER_TYPE:
+    Inconsistent extra value; expected `android.net.extra.REM_TETHER_TYPE`, was `extraRemTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_RUN_PROVISION:
+    Inconsistent extra value; expected `android.net.extra.RUN_PROVISION`, was `extraRunProvision`
+ActionValue: android.net.TetheringConstants#EXTRA_SET_ALARM:
+    Inconsistent extra value; expected `android.net.extra.SET_ALARM`, was `extraSetAlarm`
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+
+ManagerConstructor: android.net.TetheringManager#TetheringManager(android.content.Context, java.util.function.Supplier<android.os.IBinder>):
+    Managers must always be obtained from Context; no direct constructors
+
+
+PrivateSuperclass: android.location.GnssAntennaInfo.PhaseCenterVariationCorrections:
+    Public class android.location.GnssAntennaInfo.PhaseCenterVariationCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
+PrivateSuperclass: android.location.GnssAntennaInfo.SignalGainCorrections:
+    Public class android.location.GnssAntennaInfo.SignalGainCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
+
+
+StaticUtils: android.net.TetheringConstants:
+    Fully-static utility classes must not have constructor
diff --git a/api/system-current.txt b/api/system-current.txt
index b3a2c13..773cd4d 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18,6 +18,7 @@
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
+    field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
     field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
     field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
@@ -256,7 +257,6 @@
 
   public static final class R.attr {
     field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
-    field public static final int isAutofillInlineSuggestionTheme = 16844310; // 0x1010616
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int minExtensionVersion = 16844306; // 0x1010612
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
@@ -364,6 +364,7 @@
   }
 
   public class AppOpsManager {
+    method @Nullable @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
     method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static String[] getOpStrs();
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
@@ -680,6 +681,19 @@
     method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
   }
 
+  public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
+    ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
+    method public int describeContents();
+    method @Nullable public String getFeatureId();
+    method @NonNull public String getMessage();
+    method @NonNull public String getOp();
+    method @NonNull public String getPackageName();
+    method public int getSamplingStrategy();
+    method @IntRange(from=0L) public int getUid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.RuntimeAppOpAccessMessage> CREATOR;
+  }
+
   public class SearchManager implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
     method public void launchAssist(@Nullable android.os.Bundle);
   }
@@ -1809,7 +1823,7 @@
     field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
     field public static final String TETHERING_SERVICE = "tethering";
     field public static final String VR_SERVICE = "vrmanager";
-    field public static final String WIFI_COND_SERVICE = "wificond";
+    field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
   }
@@ -2217,6 +2231,7 @@
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
+    field public static final int FLAG_PERMISSION_AUTO_REVOKED = 1048576; // 0x100000
     field public static final int FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED = 131072; // 0x20000
     field public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 262144; // 0x40000
     field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
@@ -2302,7 +2317,7 @@
     method public void onPermissionsChanged(int);
   }
 
-  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -4876,7 +4891,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities(@NonNull android.content.Context);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
-    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Descrambler openDescrambler();
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.filter.Filter openFilter(int, int, long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.filter.FilterCallback);
@@ -4896,8 +4911,10 @@
   }
 
   public final class TunerConstants {
-    field public static final int INVALID_STREAM_ID = -1; // 0xffffffff
-    field public static final int INVALID_TS_PID = -1; // 0xffffffff
+    field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
+    field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
+    field public static final int INVALID_STREAM_ID = 65535; // 0xffff
+    field public static final int INVALID_TS_PID = 65535; // 0xffff
     field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
     field public static final int RESULT_INVALID_STATE = 3; // 0x3
     field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
@@ -6031,7 +6048,7 @@
 
   public class CaptivePortal implements android.os.Parcelable {
     method public void logEvent(int, @NonNull String);
-    method public void reevaluateNetwork();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
     method public void useNetwork();
     field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -7597,7 +7614,7 @@
     field @Deprecated public int numAssociation;
     field @Deprecated public int numScorerOverride;
     field @Deprecated public int numScorerOverrideAndSwitchedNetwork;
-    field @Deprecated public boolean requirePMF;
+    field @Deprecated public boolean requirePmf;
     field @Deprecated @Nullable public String saePasswordId;
     field @Deprecated public boolean shared;
     field @Deprecated public boolean useExternalScores;
@@ -8329,21 +8346,21 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.RadioChainInfo> CREATOR;
   }
 
-  public class WifiCondManager {
+  public class WifiNl80211Manager {
     method public void abortScan(@NonNull String);
     method public void enableVerboseLogging(boolean);
     method @NonNull public int[] getChannelsMhzForBand(int);
     method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
     method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
-    method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
-    method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
-    method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback);
-    method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback);
+    method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
+    method @Nullable public static android.net.wifi.wificond.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
+    method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback);
+    method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SendMgmtFrameCallback);
     method public void setOnServiceDeadCallback(@NonNull Runnable);
-    method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback);
+    method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback);
     method public boolean setupInterfaceForSoftApMode(@NonNull String);
-    method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String);
-    method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.PnoScanRequestCallback);
+    method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.SignalPollResult signalPoll(@NonNull String);
+    method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.PnoScanRequestCallback);
     method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>);
     method public boolean stopPnoScan(@NonNull String);
     method public boolean tearDownClientInterface(@NonNull String);
@@ -8358,43 +8375,43 @@
     field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
   }
 
-  public static class WifiCondManager.OemSecurityType {
-    ctor public WifiCondManager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
+  public static class WifiNl80211Manager.OemSecurityType {
+    ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
     field public final int groupCipher;
     field @NonNull public final java.util.List<java.lang.Integer> keyManagement;
     field @NonNull public final java.util.List<java.lang.Integer> pairwiseCipher;
     field public final int protocol;
   }
 
-  public static interface WifiCondManager.PnoScanRequestCallback {
+  public static interface WifiNl80211Manager.PnoScanRequestCallback {
     method public void onPnoRequestFailed();
     method public void onPnoRequestSucceeded();
   }
 
-  public static interface WifiCondManager.ScanEventCallback {
+  public static interface WifiNl80211Manager.ScanEventCallback {
     method public void onScanFailed();
     method public void onScanResultReady();
   }
 
-  public static interface WifiCondManager.SendMgmtFrameCallback {
+  public static interface WifiNl80211Manager.SendMgmtFrameCallback {
     method public void onAck(int);
     method public void onFailure(int);
   }
 
-  public static class WifiCondManager.SignalPollResult {
+  public static class WifiNl80211Manager.SignalPollResult {
     field public final int associationFrequencyMHz;
     field public final int currentRssiDbm;
     field public final int rxBitrateMbps;
     field public final int txBitrateMbps;
   }
 
-  public static interface WifiCondManager.SoftApCallback {
+  public static interface WifiNl80211Manager.SoftApCallback {
     method public void onConnectedClientsChanged(@NonNull android.net.wifi.wificond.NativeWifiClient, boolean);
     method public void onFailure();
     method public void onSoftApChannelSwitched(int, int);
   }
 
-  public static class WifiCondManager.TxPacketCounters {
+  public static class WifiNl80211Manager.TxPacketCounters {
     field public final int txPacketFailed;
     field public final int txPacketSucceeded;
   }
@@ -8839,8 +8856,8 @@
   }
 
   public class PowerWhitelistManager {
-    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean addToWhitelist(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
     method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
     field public static final int EVENT_MMS = 2; // 0x2
@@ -9679,6 +9696,7 @@
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
     field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
+    field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -10185,6 +10203,7 @@
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
+    method public final boolean requestAutofill(@NonNull android.content.ComponentName, @NonNull android.view.autofill.AutofillId);
     field public static final String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
   }
 
@@ -10854,27 +10873,26 @@
   public abstract class Conference extends android.telecom.Conferenceable {
     method @Deprecated public final android.telecom.AudioState getAudioState();
     method @Deprecated public final long getConnectTimeMillis();
-    method public final long getConnectionStartElapsedRealTime();
     method public android.telecom.Connection getPrimaryConnection();
     method @NonNull public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
-    method public final void setAddress(@NonNull android.net.Uri, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
     method public final void setCallerDisplayName(@NonNull String, int);
-    method public void setConferenceState(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
     method @Deprecated public final void setConnectTimeMillis(long);
   }
 
   public abstract class Connection extends android.telecom.Conferenceable {
     method @Deprecated public final android.telecom.AudioState getAudioState();
-    method public final long getConnectElapsedTimeMillis();
-    method public final long getConnectTimeMillis();
+    method @IntRange(from=0) public final long getConnectTimeMillis();
+    method public final long getConnectionStartElapsedRealtimeMillis();
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
-    method public final void setConnectTimeMillis(long);
-    method public final void setConnectionStartElapsedRealTime(long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
     method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
     method public void setTelecomCallId(@NonNull String);
     field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -11033,7 +11051,7 @@
   }
 
   public static class PhoneAccount.Builder {
-    method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
   }
 
   public class PhoneAccountSuggestionService extends android.app.Service {
@@ -11108,7 +11126,7 @@
     method public int getCallState();
     method public android.telecom.PhoneAccountHandle getConnectionManager();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
     method @Deprecated public android.content.ComponentName getDefaultPhoneApp();
     method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
@@ -12572,6 +12590,7 @@
     method public void notifyDataActivityChanged(int, int);
     method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
     method public void notifyDisconnectCause(int, int, int, int);
+    method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
     method public void notifyEmergencyNumberList(int, int);
     method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void notifyMessageWaitingChanged(int, int, boolean);
@@ -14062,6 +14081,14 @@
 
 }
 
+package android.view.inline {
+
+  public final class InlinePresentationSpec implements android.os.Parcelable {
+    method @Nullable public String getStyle();
+  }
+
+}
+
 package android.webkit {
 
   public abstract class CookieManager {
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 0caee6b..f440381 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -206,7 +206,7 @@
     
 MutableBareField: android.net.wifi.WifiConfiguration#meteredOverride:
     
-MutableBareField: android.net.wifi.WifiConfiguration#requirePMF:
+MutableBareField: android.net.wifi.WifiConfiguration#requirePmf:
     
 MutableBareField: android.net.wifi.WifiConfiguration#saePasswordId:
     
@@ -234,7 +234,7 @@
     If implemented by developer, should follow the on<Something> style; otherwise consider marking final
 
 
-PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
+PairedRegistration: android.net.wifi.wificond.WifiNl80211Manager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback):
     
 
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 7e8eb0c..0a821e4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -15,6 +15,7 @@
     field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+    field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -163,6 +164,7 @@
   public class AppOpsManager {
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
+    method @Nullable @RequiresPermission("android.permission.GET_APP_OPS_STATS") public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
     method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static int getNumOps();
@@ -446,6 +448,19 @@
     method public android.graphics.Rect getSourceRectHint();
   }
 
+  public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
+    ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
+    method public int describeContents();
+    method @Nullable public String getFeatureId();
+    method @NonNull public String getMessage();
+    method @NonNull public String getOp();
+    method @NonNull public String getPackageName();
+    method public int getSamplingStrategy();
+    method @IntRange(from=0L) public int getUid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.RuntimeAppOpAccessMessage> CREATOR;
+  }
+
   public class StatusBarManager {
     method public void collapsePanels();
     method public void expandNotificationsPanel();
@@ -1641,7 +1656,7 @@
 
   public class CaptivePortal implements android.os.Parcelable {
     method public void logEvent(int, @NonNull String);
-    method public void reevaluateNetwork();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
     method public void useNetwork();
     field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -2450,8 +2465,8 @@
   }
 
   public class PowerWhitelistManager {
-    method @RequiresPermission("android.permission.DEVICE_POWER") public boolean addToWhitelist(@NonNull String);
-    method @RequiresPermission("android.permission.DEVICE_POWER") public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull String);
+    method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public void whitelistAppTemporarily(@NonNull String, long);
     method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
     field public static final int EVENT_MMS = 2; // 0x2
@@ -2961,6 +2976,9 @@
     field public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
     field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
     field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
+    field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
+    field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
+    field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
     field public static final String NOTIFICATION_BADGING = "notification_badging";
     field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
@@ -3187,6 +3205,7 @@
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
+    method public final boolean requestAutofill(@NonNull android.content.ComponentName, @NonNull android.view.autofill.AutofillId);
     field public static final String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
   }
 
@@ -3384,6 +3403,34 @@
 
 }
 
+package android.service.quickaccesswallet {
+
+  public interface QuickAccessWalletClient {
+    method public void addWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
+    method @NonNull public static android.service.quickaccesswallet.QuickAccessWalletClient create(@NonNull android.content.Context);
+    method @Nullable public android.content.Intent createWalletIntent();
+    method @Nullable public android.content.Intent createWalletSettingsIntent();
+    method public void disconnect();
+    method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
+    method public boolean isWalletFeatureAvailable();
+    method public boolean isWalletFeatureAvailableWhenDeviceLocked();
+    method public boolean isWalletServiceAvailable();
+    method public void notifyWalletDismissed();
+    method public void removeWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
+    method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
+  }
+
+  public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+    method public void onWalletCardRetrievalError(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
+    method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
+  }
+
+  public static interface QuickAccessWalletClient.WalletServiceEventListener {
+    method public void onWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent);
+  }
+
+}
+
 package android.service.quicksettings {
 
   public class TileService extends android.app.Service {
@@ -3440,23 +3487,22 @@
   }
 
   public abstract class Conference extends android.telecom.Conferenceable {
-    method public final long getConnectionStartElapsedRealTime();
     method public android.telecom.Connection getPrimaryConnection();
     method @NonNull public final String getTelecomCallId();
-    method public final void setAddress(@NonNull android.net.Uri, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
     method public final void setCallerDisplayName(@NonNull String, int);
-    method public void setConferenceState(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
   }
 
   public abstract class Connection extends android.telecom.Conferenceable {
-    method public final long getConnectElapsedTimeMillis();
-    method public final long getConnectTimeMillis();
+    method @IntRange(from=0) public final long getConnectTimeMillis();
+    method public final long getConnectionStartElapsedRealtimeMillis();
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
-    method public final void setConnectTimeMillis(long);
-    method public final void setConnectionStartElapsedRealTime(long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
     method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
     method public void setTelecomCallId(@NonNull String);
     field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -3488,7 +3534,7 @@
   }
 
   public static class PhoneAccount.Builder {
-    method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
   }
 
   public class PhoneAccountSuggestionService extends android.app.Service {
@@ -3502,7 +3548,7 @@
   public class TelecomManager {
     method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCurrentTtyMode();
-    method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(int);
+    method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
     field public static final int TTY_MODE_FULL = 1; // 0x1
@@ -3720,6 +3766,7 @@
     method public void notifyDataActivityChanged(int, int);
     method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
     method public void notifyDisconnectCause(int, int, int, int);
+    method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
     method public void notifyEmergencyNumberList(int, int);
     method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void notifyMessageWaitingChanged(int, int, boolean);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 956fd29..2ee055d 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -123,11 +123,11 @@
         "libutils",
     ],
     shared_libs: [
-        "libbinder",
+        "libbinder_ndk",
         "libincident",
         "liblog",
         "libstatssocket",
-        "statsd-aidl-cpp",
+        "statsd-aidl-ndk_platform",
     ],
 }
 
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 6b9d0e4..d95d594 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -25,6 +25,7 @@
 
 using std::string;
 using std::vector;
+using android::base::StringPrintf;
 
 // These constants must be kept in sync with those in StatsDimensionsValue.java
 const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2;
@@ -73,7 +74,7 @@
                     break;
                 case STRING:
                     childParcel.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE;
-                    childParcel.stringValue = String16(dim.mValue.str_value.c_str());
+                    childParcel.stringValue = dim.mValue.str_value;
                     break;
                 default:
                     ALOGE("Encountered FieldValue with unsupported value type.");
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 4adcf96..a766bba 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <android/os/StatsDimensionsValueParcel.h>
+#include <aidl/android/os/StatsDimensionsValueParcel.h>
 #include <utils/JenkinsHash.h>
 #include <vector>
 #include "android-base/stringprintf.h"
@@ -27,7 +27,7 @@
 namespace os {
 namespace statsd {
 
-using android::base::StringPrintf;
+using ::aidl::android::os::StatsDimensionsValueParcel;
 
 struct Metric2Condition {
     int64_t conditionId;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 168833f..7087c68 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -28,8 +28,6 @@
 
 #include <android-base/file.h>
 #include <android-base/strings.h>
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionController.h>
 #include <cutils/multiuser.h>
 #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
 #include <frameworks/base/cmds/statsd/src/uid_data.pb.h>
@@ -47,85 +45,42 @@
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_MESSAGE;
 
+using Status = ::ndk::ScopedAStatus;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 constexpr const char* kPermissionDump = "android.permission.DUMP";
-constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS";
-
-constexpr const char* kOpUsage = "android:get_usage_stats";
 
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 
 // for StatsDataDumpProto
 const int FIELD_ID_REPORTS_LIST = 1;
 
-static binder::Status ok() {
-    return binder::Status::ok();
-}
-
-static binder::Status exception(uint32_t code, const std::string& msg) {
+static Status exception(int32_t code, const std::string& msg) {
     ALOGE("%s (%d)", msg.c_str(), code);
-    return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
+    return ::ndk::ScopedAStatus(AStatus_fromExceptionCodeWithMessage(code, msg.c_str()));
 }
 
 static bool checkPermission(const char* permission) {
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
+    pid_t pid = AIBinder_getCallingPid();
+    uid_t uid = AIBinder_getCallingUid();
     return checkPermissionForIds(permission, pid, uid);
 }
 
-binder::Status checkUid(uid_t expectedUid) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
+Status checkUid(uid_t expectedUid) {
+    uid_t uid = AIBinder_getCallingUid();
     if (uid == expectedUid || uid == AID_ROOT) {
-        return ok();
+        return Status::ok();
     } else {
-        return exception(binder::Status::EX_SECURITY,
-                StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
-    }
-}
-
-binder::Status checkDumpAndUsageStats(const String16& packageName) {
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    // Root, system, and shell always have access
-    if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) {
-        return ok();
-    }
-
-    // Caller must be granted these permissions
-    if (!checkPermission(kPermissionDump)) {
-        return exception(binder::Status::EX_SECURITY,
-                StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump));
-    }
-    if (!checkPermission(kPermissionUsage)) {
-        return exception(binder::Status::EX_SECURITY,
-                StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage));
-    }
-
-    // Caller must also have usage stats op granted
-    PermissionController pc;
-    switch (pc.noteOp(String16(kOpUsage), uid, packageName)) {
-        case PermissionController::MODE_ALLOWED:
-        case PermissionController::MODE_DEFAULT:
-            return ok();
-        default:
-            return exception(binder::Status::EX_SECURITY,
-                    StringPrintf("UID %d / PID %d lacks app-op %s", uid, pid, kOpUsage));
+        return exception(EX_SECURITY,
+                         StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
     }
 }
 
 #define ENFORCE_UID(uid) {                                        \
-    binder::Status status = checkUid((uid));                      \
-    if (!status.isOk()) {                                         \
-        return status;                                            \
-    }                                                             \
-}
-
-#define ENFORCE_DUMP_AND_USAGE_STATS(packageName) {               \
-    binder::Status status = checkDumpAndUsageStats(packageName);  \
+    Status status = checkUid((uid));                              \
     if (!status.isOk()) {                                         \
         return status;                                            \
     }                                                             \
@@ -134,13 +89,13 @@
 StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQueue> queue)
     : mAnomalyAlarmMonitor(new AlarmMonitor(
               MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
-              [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+              [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) {
                   if (sc != nullptr) {
                       sc->setAnomalyAlarm(timeMillis);
                       StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
                   }
               },
-              [](const sp<IStatsCompanionService>& sc) {
+              [](const shared_ptr<IStatsCompanionService>& sc) {
                   if (sc != nullptr) {
                       sc->cancelAnomalyAlarm();
                       StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
@@ -148,19 +103,21 @@
               })),
       mPeriodicAlarmMonitor(new AlarmMonitor(
               MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
-              [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+              [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) {
                   if (sc != nullptr) {
                       sc->setAlarmForSubscriberTriggering(timeMillis);
                       StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
                   }
               },
-              [](const sp<IStatsCompanionService>& sc) {
+              [](const shared_ptr<IStatsCompanionService>& sc) {
                   if (sc != nullptr) {
                       sc->cancelAlarmForSubscriberTriggering();
                       StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
                   }
               })),
-      mEventQueue(queue) {
+      mEventQueue(queue),
+      mStatsCompanionServiceDeathRecipient(AIBinder_DeathRecipient_new(
+              StatsService::statsCompanionServiceDied)) {
     mUidMap = UidMap::getInstance();
     mPullerManager = new StatsPullerManager();
     StatsPuller::SetUidMap(mUidMap);
@@ -169,22 +126,20 @@
             mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor,
             getElapsedRealtimeNs(),
             [this](const ConfigKey& key) {
-                sp<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+                shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
                 if (receiver == nullptr) {
-                    VLOG("Could not find a broadcast receiver for %s",
-                        key.ToString().c_str());
+                    VLOG("Could not find a broadcast receiver for %s", key.ToString().c_str());
                     return false;
                 } else if (receiver->sendDataBroadcast(
                            mProcessor->getLastReportTimeNs(key)).isOk()) {
                     return true;
                 } else {
-                    VLOG("Failed to send a broadcast for receiver %s",
-                        key.ToString().c_str());
+                    VLOG("Failed to send a broadcast for receiver %s", key.ToString().c_str());
                     return false;
                 }
             },
             [this](const int& uid, const vector<int64_t>& activeConfigs) {
-                sp<IPendingIntentRef> receiver =
+                shared_ptr<IPendingIntentRef> receiver =
                     mConfigManager->GetActiveConfigsChangedReceiver(uid);
                 if (receiver == nullptr) {
                     VLOG("Could not find receiver for uid %d", uid);
@@ -245,36 +200,6 @@
 }
 
 /**
- * Implement our own because the default binder implementation isn't
- * properly handling SHELL_COMMAND_TRANSACTION.
- */
-status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                                  uint32_t flags) {
-    switch (code) {
-        case SHELL_COMMAND_TRANSACTION: {
-            int in = data.readFileDescriptor();
-            int out = data.readFileDescriptor();
-            int err = data.readFileDescriptor();
-            int argc = data.readInt32();
-            Vector<String8> args;
-            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
-                args.add(String8(data.readString16()));
-            }
-            sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
-            sp<IResultReceiver> resultReceiver =
-                    IResultReceiver::asInterface(data.readStrongBinder());
-
-            err = command(in, out, err, args, resultReceiver);
-            if (resultReceiver != nullptr) {
-                resultReceiver->send(err);
-            }
-            return NO_ERROR;
-        }
-        default: { return BnStatsd::onTransact(code, data, reply, flags); }
-    }
-}
-
-/**
  * Write data from statsd.
  * Format for statsdStats:  adb shell dumpsys stats --metadata [-v] [--proto]
  * Format for data report:  adb shell dumpsys stats [anything other than --metadata] [--proto]
@@ -283,20 +208,21 @@
  *     (bugreports call "adb shell dumpsys stats --dump-priority NORMAL -a --proto")
  * TODO: Come up with a more robust method of enacting <serviceutils/PriorityDumper.h>.
  */
-status_t StatsService::dump(int fd, const Vector<String16>& args) {
+status_t StatsService::dump(int fd, const char** args, uint32_t numArgs) {
     if (!checkPermission(kPermissionDump)) {
         return PERMISSION_DENIED;
     }
-    int lastArg = args.size() - 1;
+
+    int lastArg = numArgs - 1;
     bool asProto = false;
-    if (lastArg >= 0 && !args[lastArg].compare(String16("--proto"))) { // last argument
+    if (lastArg >= 0 && string(args[lastArg]) == "--proto") { // last argument
         asProto = true;
         lastArg--;
     }
-    if (args.size() > 0 && !args[0].compare(String16("--metadata"))) { // first argument
+    if (numArgs > 0 && string(args[0]) == "--metadata") { // first argument
         // Request is to dump statsd stats.
         bool verbose = false;
-        if (lastArg >= 0 && !args[lastArg].compare(String16("-v"))) {
+        if (lastArg >= 0 && string(args[lastArg]) == "-v") {
             verbose = true;
             lastArg--;
         }
@@ -351,67 +277,72 @@
 /**
  * Implementation of the adb shell cmd stats command.
  */
-status_t StatsService::command(int in, int out, int err, Vector<String8>& args,
-                               sp<IResultReceiver> resultReceiver) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    if (uid != AID_ROOT && uid != AID_SHELL) {
+status_t StatsService::handleShellCommand(int in, int out, int err, const char** argv,
+                                          uint32_t argc) {
+    uid_t uid = AIBinder_getCallingUid();
+    if (uid != AID_ROOT || uid != AID_SHELL) {
         return PERMISSION_DENIED;
     }
 
-    const int argCount = args.size();
-    if (argCount >= 1) {
+    Vector<String8> utf8Args;
+    utf8Args.setCapacity(argc);
+    for (uint32_t i = 0; i < argc; i++) {
+        utf8Args.push(String8(argv[i]));
+    }
+
+    if (argc >= 1) {
         // adb shell cmd stats config ...
-        if (!args[0].compare(String8("config"))) {
-            return cmd_config(in, out, err, args);
+        if (!utf8Args[0].compare(String8("config"))) {
+            return cmd_config(in, out, err, utf8Args);
         }
 
-        if (!args[0].compare(String8("print-uid-map"))) {
-            return cmd_print_uid_map(out, args);
+        if (!utf8Args[0].compare(String8("print-uid-map"))) {
+            return cmd_print_uid_map(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("dump-report"))) {
-            return cmd_dump_report(out, args);
+        if (!utf8Args[0].compare(String8("dump-report"))) {
+            return cmd_dump_report(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
-            return cmd_print_pulled_metrics(out, args);
+        if (!utf8Args[0].compare(String8("pull-source")) && argc > 1) {
+            return cmd_print_pulled_metrics(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("send-broadcast"))) {
-            return cmd_trigger_broadcast(out, args);
+        if (!utf8Args[0].compare(String8("send-broadcast"))) {
+            return cmd_trigger_broadcast(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("print-stats"))) {
-            return cmd_print_stats(out, args);
+        if (!utf8Args[0].compare(String8("print-stats"))) {
+            return cmd_print_stats(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("meminfo"))) {
+        if (!utf8Args[0].compare(String8("meminfo"))) {
             return cmd_dump_memory_info(out);
         }
 
-        if (!args[0].compare(String8("write-to-disk"))) {
+        if (!utf8Args[0].compare(String8("write-to-disk"))) {
             return cmd_write_data_to_disk(out);
         }
 
-        if (!args[0].compare(String8("log-app-breadcrumb"))) {
-            return cmd_log_app_breadcrumb(out, args);
+        if (!utf8Args[0].compare(String8("log-app-breadcrumb"))) {
+            return cmd_log_app_breadcrumb(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("log-binary-push"))) {
-            return cmd_log_binary_push(out, args);
+        if (!utf8Args[0].compare(String8("log-binary-push"))) {
+            return cmd_log_binary_push(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("clear-puller-cache"))) {
+        if (!utf8Args[0].compare(String8("clear-puller-cache"))) {
             return cmd_clear_puller_cache(out);
         }
 
-        if (!args[0].compare(String8("print-logs"))) {
-            return cmd_print_logs(out, args);
+        if (!utf8Args[0].compare(String8("print-logs"))) {
+            return cmd_print_logs(out, utf8Args);
         }
-        if (!args[0].compare(String8("send-active-configs"))) {
-            return cmd_trigger_active_config_broadcast(out, args);
+        if (!utf8Args[0].compare(String8("send-active-configs"))) {
+            return cmd_trigger_active_config_broadcast(out, utf8Args);
         }
-        if (!args[0].compare(String8("data-subscribe"))) {
+        if (!utf8Args[0].compare(String8("data-subscribe"))) {
             {
                 std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
                 if (mShellSubscriber == nullptr) {
@@ -419,14 +350,10 @@
                 }
             }
             int timeoutSec = -1;
-            if (argCount >= 2) {
-                timeoutSec = atoi(args[1].c_str());
+            if (argc >= 2) {
+                timeoutSec = atoi(utf8Args[1].c_str());
             }
-            if (resultReceiver == nullptr) {
-                ALOGI("Null resultReceiver given, no subscription will be started");
-                return UNEXPECTED_NULL;
-            }
-            mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec);
+            mShellSubscriber->startNewSubscription(in, out, timeoutSec);
             return NO_ERROR;
         }
     }
@@ -556,7 +483,7 @@
     const int argCount = args.size();
     if (argCount == 2) {
         // Automatically pick the UID
-        uid = IPCThreadState::self()->getCallingUid();
+        uid = AIBinder_getCallingUid();
         name.assign(args[1].c_str(), args[1].size());
         good = true;
     } else if (argCount == 3) {
@@ -572,17 +499,15 @@
         return UNKNOWN_ERROR;
     }
     ConfigKey key(uid, StrToInt64(name));
-    sp<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+    shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
     if (receiver == nullptr) {
         VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str());
         return UNKNOWN_ERROR;
-    } else if (receiver->sendDataBroadcast(
-               mProcessor->getLastReportTimeNs(key)).isOk()) {
+    } else if (receiver->sendDataBroadcast(mProcessor->getLastReportTimeNs(key)).isOk()) {
         VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
              args[2].c_str());
     } else {
-        VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(),
-             args[2].c_str());
+        VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(), args[2].c_str());
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
@@ -594,7 +519,7 @@
     vector<int64_t> configIds;
     if (argCount == 1) {
         // Automatically pick the uid and send a broadcast that has no active configs.
-        uid = IPCThreadState::self()->getCallingUid();
+        uid = AIBinder_getCallingUid();
         mProcessor->GetActiveConfigs(uid, configIds);
     } else {
         int curArg = 1;
@@ -608,7 +533,7 @@
             }
             curArg++;
         } else {
-            uid = IPCThreadState::self()->getCallingUid();
+            uid = AIBinder_getCallingUid();
         }
         if (curArg == argCount || args[curArg] != "--configs") {
             VLOG("Reached end of args, or specify configs not set. Sending actual active configs,");
@@ -628,7 +553,7 @@
             }
         }
     }
-    sp<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
+    shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
     if (receiver == nullptr) {
         VLOG("Could not find receiver for uid %d", uid);
         return UNKNOWN_ERROR;
@@ -651,7 +576,7 @@
 
             if (argCount == 3) {
                 // Automatically pick the UID
-                uid = IPCThreadState::self()->getCallingUid();
+                uid = AIBinder_getCallingUid();
                 name.assign(args[2].c_str(), args[2].size());
                 good = true;
             } else if (argCount == 4) {
@@ -734,7 +659,7 @@
         }
         if (argCount == 2) {
             // Automatically pick the UID
-            uid = IPCThreadState::self()->getCallingUid();
+            uid = AIBinder_getCallingUid();
             name.assign(args[1].c_str(), args[1].size());
             good = true;
         } else if (argCount == 3) {
@@ -826,7 +751,7 @@
     const int argCount = args.size();
     if (argCount == 3) {
         // Automatically pick the UID
-        uid = IPCThreadState::self()->getCallingUid();
+        uid = AIBinder_getCallingUid();
         label = atoi(args[1].c_str());
         state = atoi(args[2].c_str());
         good = true;
@@ -903,9 +828,8 @@
 }
 
 status_t StatsService::cmd_clear_puller_cache(int out) {
-    IPCThreadState* ipc = IPCThreadState::self();
     VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i",
-            ipc->getCallingPid(), ipc->getCallingUid());
+            AIBinder_getCallingPid(), AIBinder_getCallingUid());
     if (checkPermission(kPermissionDump)) {
         int cleared = mPullerManager->ForceClearPullerCache();
         dprintf(out, "Puller removed %d cached data!\n", cleared);
@@ -916,9 +840,8 @@
 }
 
 status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) {
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", ipc->getCallingPid(),
-         ipc->getCallingUid());
+    VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", AIBinder_getCallingPid(),
+         AIBinder_getCallingUid());
     if (checkPermission(kPermissionDump)) {
         bool enabled = true;
         if (args.size() >= 2) {
@@ -950,24 +873,24 @@
     }
     uid = goodUid;
 
-    int32_t callingUid = IPCThreadState::self()->getCallingUid();
+    int32_t callingUid = AIBinder_getCallingUid();
     return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids.
             || (callingUid == goodUid) // Anyone can 'impersonate' themselves.
             || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL.
 }
 
-Status StatsService::informAllUidData(const ParcelFileDescriptor& fd) {
+Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) {
     ENFORCE_UID(AID_SYSTEM);
     // Read stream into buffer.
     string buffer;
     if (!android::base::ReadFdToString(fd.get(), &buffer)) {
-        return exception(Status::EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
+        return exception(EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
     }
 
     // Parse buffer.
     UidData uidData;
     if (!uidData.ParseFromString(buffer)) {
-        return exception(Status::EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
+        return exception(EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
     }
 
     vector<String16> versionStrings;
@@ -1002,20 +925,28 @@
     return Status::ok();
 }
 
-Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version,
-                                      const String16& version_string, const String16& installer) {
+Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version,
+                                      const string& versionString, const string& installer) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackage was called");
-    mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version, version_string, installer);
+    // TODO(b/149254662): This is gross. We should consider changing statsd
+    // internals to use std::string.
+    String16 utf16App = String16(app.c_str());
+    String16 utf16VersionString = String16(versionString.c_str());
+    String16 utf16Installer = String16(installer.c_str());
+
+    mUidMap->updateApp(getElapsedRealtimeNs(), utf16App, uid, version, utf16VersionString,
+                       utf16Installer);
     return Status::ok();
 }
 
-Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
+Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackageRemoved was called");
-    mUidMap->removeApp(getElapsedRealtimeNs(), app, uid);
+    String16 utf16App = String16(app.c_str());
+    mUidMap->removeApp(getElapsedRealtimeNs(), utf16App, uid);
     mConfigManager->RemoveConfigs(uid);
     return Status::ok();
 }
@@ -1079,7 +1010,7 @@
 }
 
 void StatsService::sayHiToStatsCompanion() {
-    sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+    shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion != nullptr) {
         VLOG("Telling statsCompanion that statsd is ready");
         statsCompanion->statsdReady();
@@ -1092,14 +1023,14 @@
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::statsCompanionReady was called");
-    sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+    shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion == nullptr) {
-        return Status::fromExceptionCode(
-                Status::EX_NULL_POINTER,
-                "statscompanion unavailable despite it contacting statsd!");
+        return exception(EX_NULL_POINTER,
+                         "StatsCompanion unavailable despite it contacting statsd.");
     }
     VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
-    IInterface::asBinder(statsCompanion)->linkToDeath(this);
+    AIBinder_linkToDeath(statsCompanion->asBinder().get(),
+                         mStatsCompanionServiceDeathRecipient.get(), this);
     mPullerManager->SetStatsCompanionService(statsCompanion);
     mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion);
     mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion);
@@ -1127,39 +1058,47 @@
     }
 }
 
-Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+Status StatsService::getData(int64_t key, const int32_t callingUid, vector<int8_t>* output) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::getData with Uid %i", callingUid);
     ConfigKey configKey(callingUid, key);
+    // TODO(b/149254662): Since libbinder_ndk uses int8_t instead of uint8_t,
+    // there are inconsistencies with internal statsd logic. Instead of
+    // modifying lots of files, we create a temporary output array of int8_t and
+    // copy its data into output. This is a bad hack, but hopefully
+    // libbinder_ndk will transition to using uint8_t soon: progress is tracked
+    // in b/144957764. Same applies to StatsService::getMetadata.
+    vector<uint8_t> unsignedOutput;
     // The dump latency does not matter here since we do not include the current bucket, we do not
     // need to pull any new data anyhow.
     mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/,
-                             true /* erase_data */, GET_DATA_CALLED, FAST, output);
+                             true /* erase_data */, GET_DATA_CALLED, FAST, &unsignedOutput);
+    *output = vector<int8_t>(unsignedOutput.begin(), unsignedOutput.end());
     return Status::ok();
 }
 
-Status StatsService::getMetadata(vector<uint8_t>* output) {
+Status StatsService::getMetadata(vector<int8_t>* output) {
     ENFORCE_UID(AID_SYSTEM);
 
-    StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
+    vector<uint8_t> unsignedOutput;
+    StatsdStats::getInstance().dumpStats(&unsignedOutput, false); // Don't reset the counters.
+    *output = vector<int8_t>(unsignedOutput.begin(), unsignedOutput.end());
     return Status::ok();
 }
 
-Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
+Status StatsService::addConfiguration(int64_t key, const vector <int8_t>& config,
                                       const int32_t callingUid) {
     ENFORCE_UID(AID_SYSTEM);
 
     if (addConfigurationChecked(callingUid, key, config)) {
         return Status::ok();
     } else {
-        ALOGE("Could not parse malformatted StatsdConfig");
-        return Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT,
-                                         "config does not correspond to a StatsdConfig proto");
+        return exception(EX_ILLEGAL_ARGUMENT, "Could not parse malformatted StatsdConfig.");
     }
 }
 
-bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector<uint8_t>& config) {
+bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector<int8_t>& config) {
     ConfigKey configKey(uid, key);
     StatsdConfig cfg;
     if (config.size() > 0) {  // If the config is empty, skip parsing.
@@ -1180,7 +1119,7 @@
 }
 
 Status StatsService::setDataFetchOperation(int64_t key,
-                                           const sp<IPendingIntentRef>& pir,
+                                           const shared_ptr<IPendingIntentRef>& pir,
                                            const int32_t callingUid) {
     ENFORCE_UID(AID_SYSTEM);
 
@@ -1194,7 +1133,7 @@
     return Status::ok();
 }
 
-Status StatsService::setActiveConfigsChangedOperation(const sp<IPendingIntentRef>& pir,
+Status StatsService::setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
                                                       const int32_t callingUid,
                                                       vector<int64_t>* output) {
     ENFORCE_UID(AID_SYSTEM);
@@ -1225,7 +1164,7 @@
 
 Status StatsService::setBroadcastSubscriber(int64_t configId,
                                             int64_t subscriberId,
-                                            const sp<IPendingIntentRef>& pir,
+                                            const shared_ptr<IPendingIntentRef>& pir,
                                             const int32_t callingUid) {
     ENFORCE_UID(AID_SYSTEM);
 
@@ -1252,14 +1191,14 @@
     // Permission check not necessary as it's meant for applications to write to
     // statsd.
     android::util::stats_write(util::APP_BREADCRUMB_REPORTED,
-                               (int32_t) IPCThreadState::self()->getCallingUid(), label,
+                               (int32_t) AIBinder_getCallingUid(), label,
                                state);
     return Status::ok();
 }
 
 Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs,
                                     int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
-                                    const sp<android::os::IPullAtomCallback>& pullerCallback) {
+                                    const shared_ptr<IPullAtomCallback>& pullerCallback) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::registerPullAtomCallback called.");
@@ -1270,10 +1209,10 @@
 
 Status StatsService::registerNativePullAtomCallback(int32_t atomTag, int64_t coolDownNs,
                                     int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
-                                    const sp<android::os::IPullAtomCallback>& pullerCallback) {
+                                    const shared_ptr<IPullAtomCallback>& pullerCallback) {
 
     VLOG("StatsService::registerNativePullAtomCallback called.");
-    int32_t uid = IPCThreadState::self()->getCallingUid();
+    int32_t uid = AIBinder_getCallingUid();
     mPullerManager->RegisterPullAtomCallback(uid, atomTag, coolDownNs, timeoutNs, additiveFields,
                                              pullerCallback);
     return Status::ok();
@@ -1288,7 +1227,7 @@
 
 Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
     VLOG("StatsService::unregisterNativePullAtomCallback called.");
-    int32_t uid = IPCThreadState::self()->getCallingUid();
+    int32_t uid = AIBinder_getCallingUid();
     mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
     return Status::ok();
 }
@@ -1314,7 +1253,13 @@
     return Status::ok();
 }
 
-void StatsService::binderDied(const wp <IBinder>& who) {
+
+void StatsService::statsCompanionServiceDied(void* cookie) {
+    auto thiz = static_cast<StatsService*>(cookie);
+    thiz->statsCompanionServiceDiedImpl();
+}
+
+void StatsService::statsCompanionServiceDiedImpl() {
     ALOGW("statscompanion service died");
     StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
     if (mProcessor != nullptr) {
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 0527d43..e6723c8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -27,27 +27,30 @@
 #include "shell/ShellSubscriber.h"
 #include "statscompanion_util.h"
 
-#include <android/os/BnStatsd.h>
-#include <android/os/IPendingIntentRef.h>
-#include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsd.h>
-#include <binder/IResultReceiver.h>
-#include <binder/ParcelFileDescriptor.h>
+#include <aidl/android/os/BnStatsd.h>
+#include <aidl/android/os/IPendingIntentRef.h>
+#include <aidl/android/os/IPullAtomCallback.h>
 #include <utils/Looper.h>
 
 #include <mutex>
 
 using namespace android;
-using namespace android::binder;
 using namespace android::os;
 using namespace std;
 
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnStatsd;
+using aidl::android::os::IPendingIntentRef;
+using aidl::android::os::IPullAtomCallback;
+using ::ndk::ScopedAIBinder_DeathRecipient;
+using ::ndk::ScopedFileDescriptor;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
-class StatsService : public BnStatsd,
-                     public IBinder::DeathRecipient {
+class StatsService : public BnStatsd {
 public:
     StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue);
     virtual ~StatsService();
@@ -55,10 +58,9 @@
     /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
     const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5;
 
-    virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
-    virtual status_t dump(int fd, const Vector<String16>& args);
-    virtual status_t command(int inFd, int outFd, int err, Vector<String8>& args,
-                             sp<IResultReceiver> resultReceiver);
+    virtual status_t dump(int fd, const char** args, uint32_t numArgs) override;
+    virtual status_t handleShellCommand(int in, int out, int err, const char** argv,
+                                        uint32_t argc) override;
 
     virtual Status systemRunning();
     virtual Status statsCompanionReady();
@@ -66,10 +68,10 @@
     virtual Status informPollAlarmFired();
     virtual Status informAlarmForSubscriberTriggeringFired();
 
-    virtual Status informAllUidData(const ParcelFileDescriptor& fd);
-    virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version,
-                                    const String16& version_string, const String16& installer);
-    virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
+    virtual Status informAllUidData(const ScopedFileDescriptor& fd);
+    virtual Status informOnePackage(const string& app, int32_t uid, int64_t version,
+                                    const string& versionString, const string& installer);
+    virtual Status informOnePackageRemoved(const string& app, int32_t uid);
     virtual Status informDeviceShutdown();
 
     /**
@@ -92,13 +94,13 @@
      */
     virtual Status getData(int64_t key,
                            const int32_t callingUid,
-                           vector<uint8_t>* output) override;
+                           vector<int8_t>* output) override;
 
 
     /**
      * Binder call for clients to get metadata across all configs in statsd.
      */
-    virtual Status getMetadata(vector<uint8_t>* output) override;
+    virtual Status getMetadata(vector<int8_t>* output) override;
 
 
     /**
@@ -106,14 +108,14 @@
      * should requestData for this configuration.
      */
     virtual Status addConfiguration(int64_t key,
-                                    const vector<uint8_t>& config,
+                                    const vector<int8_t>& config,
                                     const int32_t callingUid) override;
 
     /**
      * Binder call to let clients register the data fetch operation for a configuration.
      */
     virtual Status setDataFetchOperation(int64_t key,
-                                         const sp<IPendingIntentRef>& pir,
+                                         const shared_ptr<IPendingIntentRef>& pir,
                                          const int32_t callingUid) override;
 
     /**
@@ -125,7 +127,7 @@
     /**
      * Binder call to let clients register the active configs changed operation.
      */
-    virtual Status setActiveConfigsChangedOperation(const sp<IPendingIntentRef>& pir,
+    virtual Status setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
                                                     const int32_t callingUid,
                                                     vector<int64_t>* output) override;
 
@@ -144,7 +146,7 @@
      */
     virtual Status setBroadcastSubscriber(int64_t configId,
                                           int64_t subscriberId,
-                                          const sp<IPendingIntentRef>& pir,
+                                          const shared_ptr<IPendingIntentRef>& pir,
                                           const int32_t callingUid) override;
 
     /**
@@ -167,14 +169,14 @@
      */
     virtual Status registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs,
             int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
-            const sp<android::os::IPullAtomCallback>& pullerCallback) override;
+            const shared_ptr<IPullAtomCallback>& pullerCallback) override;
 
     /**
      * Binder call to register a callback function for a pulled atom.
      */
     virtual Status registerNativePullAtomCallback(int32_t atomTag, int64_t coolDownNs,
             int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
-            const sp<android::os::IPullAtomCallback>& pullerCallback) override;
+            const shared_ptr<IPullAtomCallback>& pullerCallback) override;
 
     /**
      * Binder call to unregister any existing callback for the given uid and atom.
@@ -191,9 +193,6 @@
      */
     virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut);
 
-    /** IBinder::DeathRecipient */
-    virtual void binderDied(const wp<IBinder>& who) override;
-
 private:
     /**
      * Load system properties at init.
@@ -317,7 +316,7 @@
     /**
      * Adds a configuration after checking permissions and obtaining UID from binder call.
      */
-    bool addConfigurationChecked(int uid, int64_t key, const vector<uint8_t>& config);
+    bool addConfigurationChecked(int uid, int64_t key, const vector<int8_t>& config);
 
     /**
      * Update a configuration.
@@ -325,6 +324,17 @@
     void set_config(int uid, const string& name, const StatsdConfig& config);
 
     /**
+     * Death recipient callback that is called when StatsCompanionService dies.
+     * The cookie is a pointer to a StatsService object.
+     */
+    static void statsCompanionServiceDied(void* cookie);
+
+    /**
+     * Implementation of statsCompanionServiceDied.
+     */
+    void statsCompanionServiceDiedImpl();
+
+    /**
      * Tracks the uid <--> package name mapping.
      */
     sp<UidMap> mUidMap;
@@ -367,6 +377,8 @@
     mutable mutex mShellSubscriberMutex;
     std::shared_ptr<LogEventQueue> mEventQueue;
 
+    ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient;
+
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_empty);
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
index bc36dad..02291181 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
@@ -26,17 +26,21 @@
 
 AlarmMonitor::AlarmMonitor(
         uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
-        const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
-        const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm)
-    : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
+        const std::function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>& updateAlarm,
+        const std::function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm)
+    : mRegisteredAlarmTimeSec(0),
+      mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
       mUpdateAlarm(updateAlarm),
       mCancelAlarm(cancelAlarm) {}
 
 AlarmMonitor::~AlarmMonitor() {}
 
-void AlarmMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+void AlarmMonitor::setStatsCompanionService(
+        shared_ptr<IStatsCompanionService> statsCompanionService) {
     std::lock_guard<std::mutex> lock(mLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+    // TODO(b/149254662): determine if tmpForLock is needed now that we have moved
+    // from sp to shared_ptr
+    shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
     if (statsCompanionService == nullptr) {
         VLOG("Erasing link to statsCompanionService");
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.h b/cmds/statsd/src/anomaly/AlarmMonitor.h
index 219695e..5c34e38 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.h
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.h
@@ -18,7 +18,7 @@
 
 #include "anomaly/indexed_priority_queue.h"
 
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <utils/RefBase.h>
 
 #include <unordered_set>
@@ -26,7 +26,9 @@
 
 using namespace android;
 
-using android::os::IStatsCompanionService;
+using aidl::android::os::IStatsCompanionService;
+using std::function;
+using std::shared_ptr;
 using std::unordered_set;
 
 namespace android {
@@ -64,8 +66,9 @@
      * alarm.
      */
     AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
-                 const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
-                 const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm);
+                 const function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>&
+                         updateAlarm,
+                 const function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm);
     ~AlarmMonitor();
 
     /**
@@ -74,7 +77,7 @@
      * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
      * update IStatsCompanionService (until such time as it is set non-null).
      */
-    void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+    void setStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
 
     /**
      * Adds the given alarm (reference) to the queue.
@@ -123,7 +126,7 @@
     /**
      * Binder interface for communicating with StatsCompanionService.
      */
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+    shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
 
     /**
      * Amount by which the soonest projected alarm must differ from
@@ -147,10 +150,10 @@
     int64_t secToMs(uint32_t timeSec);
 
     // Callback function to update the alarm via StatsCompanionService.
-    std::function<void(const sp<IStatsCompanionService>, int64_t)> mUpdateAlarm;
+    std::function<void(const shared_ptr<IStatsCompanionService>, int64_t)> mUpdateAlarm;
 
     // Callback function to cancel the alarm via StatsCompanionService.
-    std::function<void(const sp<IStatsCompanionService>)> mCancelAlarm;
+    std::function<void(const shared_ptr<IStatsCompanionService>)> mCancelAlarm;
 
 };
 
diff --git a/cmds/statsd/src/anomaly/AlarmTracker.h b/cmds/statsd/src/anomaly/AlarmTracker.h
index 2fb3e3b..2da4a18 100644
--- a/cmds/statsd/src/anomaly/AlarmTracker.h
+++ b/cmds/statsd/src/anomaly/AlarmTracker.h
@@ -22,12 +22,9 @@
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alarm
 
-#include <android/os/IStatsCompanionService.h>
 #include <stdlib.h>
 #include <utils/RefBase.h>
 
-using android::os::IStatsCompanionService;
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1bc235a..27c4f4e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -378,10 +378,10 @@
                 240 [(module) = "framework"];
         BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
         BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
-        UserspaceRebootReported userspace_reboot_reported = 243;
+        UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
         NotificationReported notification_reported = 244 [(module) = "framework"];
         NotificationPanelReported notification_panel_reported = 245;
-        NotificationChannelModified notification_panel_modified = 246;
+        NotificationChannelModified notification_channel_modified = 246;
         IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"];
         IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"];
         CellBroadcastMessageReported cb_message_reported =
@@ -8000,6 +8000,18 @@
     // of this pull. If this number grows prohibitively large, then this can
     // cause jank due to resource contention.
     optional int32 event_connection_count = 6;
+    // Set of timings measured from when SurfaceFlinger began compositing a
+    // frame, until the frame was requested to be presented to the display. This
+    // measures SurfaceFlinger's total CPU walltime on the critical path per
+    // frame.
+    optional FrameTimingHistogram frame_duration = 7
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when SurfaceFlinger first began using the
+    // GPU to composite a frame, until the GPU has finished compositing that
+    // frame. This measures the total additional time SurfaceFlinger needed to
+    // perform due to falling back into GPU composition.
+    optional FrameTimingHistogram render_engine_timing = 8
+        [(android.os.statsd.log_mode) = MODE_BYTES];
 }
 
 /**
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 986955b..9bdb588 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -42,42 +42,67 @@
 using android::base::StringPrintf;
 using std::unique_ptr;
 
-class ConfigReceiverDeathRecipient : public android::IBinder::DeathRecipient {
-    public:
-        ConfigReceiverDeathRecipient(sp<ConfigManager> configManager, const ConfigKey& configKey):
-            mConfigManager(configManager),
-            mConfigKey(configKey) {}
-        ~ConfigReceiverDeathRecipient() override = default;
-    private:
-        sp<ConfigManager> mConfigManager;
-        ConfigKey mConfigKey;
+struct ConfigReceiverDeathCookie {
+    ConfigReceiverDeathCookie(sp<ConfigManager> configManager, const ConfigKey& configKey,
+                              const shared_ptr<IPendingIntentRef>& pir):
+        mConfigManager(configManager),
+        mConfigKey(configKey),
+        mPir(pir) {}
 
-    void binderDied(const android::wp<android::IBinder>& who) override {
-        if (IInterface::asBinder(mConfigManager->GetConfigReceiver(mConfigKey)) == who.promote()) {
-             mConfigManager->RemoveConfigReceiver(mConfigKey);
-        }
-    }
+    sp<ConfigManager> mConfigManager;
+    ConfigKey mConfigKey;
+    shared_ptr<IPendingIntentRef> mPir;
 };
 
-class ActiveConfigChangedReceiverDeathRecipient : public android::IBinder::DeathRecipient {
-    public:
-        ActiveConfigChangedReceiverDeathRecipient(sp<ConfigManager> configManager, const int uid):
-            mConfigManager(configManager),
-            mUid(uid) {}
-        ~ActiveConfigChangedReceiverDeathRecipient() override = default;
-    private:
-        sp<ConfigManager> mConfigManager;
-        int mUid;
+static void configReceiverDied(void* cookie) {
+    auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
+    sp<ConfigManager> configManager = cookie_->mConfigManager;
+    ConfigKey configKey = cookie_->mConfigKey;
+    shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
 
-    void binderDied(const android::wp<android::IBinder>& who) override {
-        if (IInterface::asBinder(mConfigManager->GetActiveConfigsChangedReceiver(mUid))
-              == who.promote()) {
-            mConfigManager->RemoveActiveConfigsChangedReceiver(mUid);
-        }
+    // TODO(b/149254662): Fix threading. This currently fails if a new
+    // pir gets set between the get and the remove.
+    if (configManager->GetConfigReceiver(configKey) == pir) {
+        configManager->RemoveConfigReceiver(configKey);
     }
+    // The death recipient corresponding to this specific pir can never
+    // be triggered again, so free up resources.
+    // TODO(b/149254662): Investigate other options to manage the memory.
+    delete cookie_;
+}
+
+struct ActiveConfigChangedReceiverDeathCookie {
+    ActiveConfigChangedReceiverDeathCookie(sp<ConfigManager> configManager, const int uid,
+                                           const shared_ptr<IPendingIntentRef>& pir):
+        mConfigManager(configManager),
+        mUid(uid),
+        mPir(pir) {}
+
+    sp<ConfigManager> mConfigManager;
+    int mUid;
+    shared_ptr<IPendingIntentRef> mPir;
 };
 
-ConfigManager::ConfigManager() {
+static void activeConfigChangedReceiverDied(void* cookie) {
+    auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
+    sp<ConfigManager> configManager = cookie_->mConfigManager;
+    int uid = cookie_->mUid;
+    shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+
+    // TODO(b/149254662): Fix threading. This currently fails if a new
+    // pir gets set between the get and the remove.
+    if (configManager->GetActiveConfigsChangedReceiver(uid) == pir) {
+        configManager->RemoveActiveConfigsChangedReceiver(uid);
+    }
+    // The death recipient corresponding to this specific pir can never
+    // be triggered again, so free up resources.
+    delete cookie_;
+}
+
+ConfigManager::ConfigManager():
+    mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)),
+    mActiveConfigChangedReceiverDeathRecipient(
+        AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) {
 }
 
 ConfigManager::~ConfigManager() {
@@ -150,10 +175,11 @@
 }
 
 void ConfigManager::SetConfigReceiver(const ConfigKey& key,
-                                      const sp<IPendingIntentRef>& pir) {
+                                      const shared_ptr<IPendingIntentRef>& pir) {
     lock_guard<mutex> lock(mMutex);
     mConfigReceivers[key] = pir;
-    IInterface::asBinder(pir)->linkToDeath(new ConfigReceiverDeathRecipient(this, key));
+    AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(),
+                         new ConfigReceiverDeathCookie(this, key, pir));
 }
 
 void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
@@ -162,11 +188,11 @@
 }
 
 void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
-                                                    const sp<IPendingIntentRef>& pir) {
+                                                    const shared_ptr<IPendingIntentRef>& pir) {
     lock_guard<mutex> lock(mMutex);
     mActiveConfigsChangedReceivers[uid] = pir;
-    IInterface::asBinder(pir)->linkToDeath(
-        new ActiveConfigChangedReceiverDeathRecipient(this, uid));
+    AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(),
+                         new ActiveConfigChangedReceiverDeathCookie(this, uid, pir));
 }
 
 void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
@@ -279,7 +305,7 @@
     return ret;
 }
 
-const sp<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
+const shared_ptr<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
     lock_guard<mutex> lock(mMutex);
 
     auto it = mConfigReceivers.find(key);
@@ -290,7 +316,8 @@
     }
 }
 
-const sp<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+const shared_ptr<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid)
+        const {
     lock_guard<mutex> lock(mMutex);
 
     auto it = mActiveConfigsChangedReceivers.find(uid);
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 2095173..824e588 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -19,12 +19,15 @@
 #include "config/ConfigKey.h"
 #include "config/ConfigListener.h"
 
-#include <android/os/IPendingIntentRef.h>
+#include <aidl/android/os/IPendingIntentRef.h>
 #include <mutex>
 #include <string>
 
 #include <stdio.h>
 
+using aidl::android::os::IPendingIntentRef;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -62,12 +65,12 @@
     /**
      * Sets the broadcast receiver for a configuration key.
      */
-    void SetConfigReceiver(const ConfigKey& key, const sp<IPendingIntentRef>& pir);
+    void SetConfigReceiver(const ConfigKey& key, const shared_ptr<IPendingIntentRef>& pir);
 
     /**
      * Returns the package name and class name representing the broadcast receiver for this config.
      */
-    const sp<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
+    const shared_ptr<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
 
     /**
      * Returns all config keys registered.
@@ -83,13 +86,13 @@
      * Sets the broadcast receiver that is notified whenever the list of active configs
      * changes for this uid.
      */
-    void SetActiveConfigsChangedReceiver(const int uid, const sp<IPendingIntentRef>& pir);
+    void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr<IPendingIntentRef>& pir);
 
     /**
      * Returns the broadcast receiver for active configs changed for this uid.
      */
 
-    const sp<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
+    const shared_ptr<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
 
     /**
      * Erase any active configs changed broadcast receiver associated with this uid.
@@ -141,18 +144,23 @@
     /**
      * Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef.
      */
-    std::map<ConfigKey, sp<IPendingIntentRef>> mConfigReceivers;
+    std::map<ConfigKey, shared_ptr<IPendingIntentRef>> mConfigReceivers;
 
     /**
      * Each uid can be subscribed by up to one receiver to notify that the list of active configs
      * for this uid has changed. The receiver is specified as IPendingIntentRef.
      */
-     std::map<int, sp<IPendingIntentRef>> mActiveConfigsChangedReceivers;
+     std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
 
     /**
      * The ConfigListeners that will be told about changes.
      */
     std::vector<sp<ConfigListener>> mListeners;
+
+    // Death recipients that are triggered when the host process holding an
+    // IPendingIntentRef dies.
+    ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient;
+    ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/PullResultReceiver.cpp b/cmds/statsd/src/external/PullResultReceiver.cpp
index 6b6fe7d..8aa4792 100644
--- a/cmds/statsd/src/external/PullResultReceiver.cpp
+++ b/cmds/statsd/src/external/PullResultReceiver.cpp
@@ -16,17 +16,12 @@
 
 #include "PullResultReceiver.h"
 
-using namespace android::binder;
-using namespace android::util;
-using namespace std;
-
 namespace android {
 namespace os {
 namespace statsd {
 
 PullResultReceiver::PullResultReceiver(
-        std::function<void(int32_t, bool, const vector<android::util::StatsEventParcel>&)>
-             pullFinishCb)
+        std::function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCb)
     : pullFinishCallback(std::move(pullFinishCb)) {
 }
 
diff --git a/cmds/statsd/src/external/PullResultReceiver.h b/cmds/statsd/src/external/PullResultReceiver.h
index 17d06e4..ceaae80 100644
--- a/cmds/statsd/src/external/PullResultReceiver.h
+++ b/cmds/statsd/src/external/PullResultReceiver.h
@@ -14,29 +14,33 @@
  * limitations under the License.
  */
 
-#include <android/os/BnPullAtomResultReceiver.h>
+#include <aidl/android/os/BnPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
 
 using namespace std;
 
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnPullAtomResultReceiver;
+using aidl::android::util::StatsEventParcel;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 class PullResultReceiver : public BnPullAtomResultReceiver {
 public:
-    PullResultReceiver(function<void(int32_t, bool, const vector<android::util::StatsEventParcel>&)>
+    PullResultReceiver(function<void(int32_t, bool, const vector<StatsEventParcel>&)>
                                pullFinishCallback);
     ~PullResultReceiver();
 
     /**
      * Binder call for finishing a pull.
      */
-    binder::Status pullFinished(int32_t atomTag, bool success,
-                                const vector<android::util::StatsEventParcel>& output) override;
+    Status pullFinished(int32_t atomTag, bool success,
+                        const vector<StatsEventParcel>& output) override;
 
 private:
-    function<void(int32_t, bool, const vector<android::util::StatsEventParcel>&)>
-            pullFinishCallback;
+    function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCallback;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 1a11f0e..b6c8e34 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -18,24 +18,24 @@
 #include "Log.h"
 
 #include "StatsCallbackPuller.h"
-
-#include <android/os/IPullAtomCallback.h>
-#include <android/util/StatsEventParcel.h>
-
 #include "PullResultReceiver.h"
 #include "StatsPullerManager.h"
 #include "logd/LogEvent.h"
 #include "stats_log_util.h"
 
-using namespace android::binder;
-using namespace android::util;
+#include <aidl/android/util/StatsEventParcel.h>
+
 using namespace std;
 
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
+
 namespace android {
 namespace os {
 namespace statsd {
 
-StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback,
+StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
                                          const int64_t coolDownNs, int64_t timeoutNs,
                                          const vector<int> additiveFields)
     : StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) {
@@ -57,7 +57,7 @@
     shared_ptr<vector<shared_ptr<LogEvent>>> sharedData =
             make_shared<vector<shared_ptr<LogEvent>>>();
 
-    sp<PullResultReceiver> resultReceiver = new PullResultReceiver(
+    shared_ptr<PullResultReceiver> resultReceiver = SharedRefBase::make<PullResultReceiver>(
             [cv_mutex, cv, pullFinish, pullSuccess, sharedData](
                     int32_t atomTag, bool success, const vector<StatsEventParcel>& output) {
                 // This is the result of the pull, executing in a statsd binder thread.
@@ -66,9 +66,11 @@
                 {
                     lock_guard<mutex> lk(*cv_mutex);
                     for (const StatsEventParcel& parcel: output) {
+                        uint8_t* buf = reinterpret_cast<uint8_t*>(
+                                const_cast<int8_t*>(parcel.buffer.data()));
                         shared_ptr<LogEvent> event = make_shared<LogEvent>(
-                                const_cast<uint8_t*>(parcel.buffer.data()), parcel.buffer.size(),
-                                /*uid=*/-1, /*pid=*/-1, /*useNewSchema=*/true);
+                                buf, parcel.buffer.size(), /*uid=*/-1, /*pid=*/-1,
+                                /*useNewSchema=*/true);
                         sharedData->push_back(event);
                     }
                     *pullSuccess = success;
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.h b/cmds/statsd/src/external/StatsCallbackPuller.h
index fe6af19..e82e8bb 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.h
+++ b/cmds/statsd/src/external/StatsCallbackPuller.h
@@ -16,23 +16,25 @@
 
 #pragma once
 
-#include <android/os/IPullAtomCallback.h>
-
+#include <aidl/android/os/IPullAtomCallback.h>
 #include "StatsPuller.h"
 
+using aidl::android::os::IPullAtomCallback;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 class StatsCallbackPuller : public StatsPuller {
 public:
-    explicit StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback,
+    explicit StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
                                  const int64_t coolDownNs, const int64_t timeoutNs,
                                  const std::vector<int> additiveFields);
 
 private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
-    const sp<IPullAtomCallback> mCallback;
+    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
+    const shared_ptr<IPullAtomCallback> mCallback;
 
     FRIEND_TEST(StatsCallbackPullerTest, PullFail);
     FRIEND_TEST(StatsCallbackPullerTest, PullSuccess);
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 9c21695..fee571c 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <utils/RefBase.h>
 #include <mutex>
 #include <vector>
@@ -26,6 +26,9 @@
 #include "logd/LogEvent.h"
 #include "puller_util.h"
 
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -58,7 +61,8 @@
 
     static void SetUidMap(const sp<UidMap>& uidMap);
 
-    virtual void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService){};
+    virtual void SetStatsCompanionService(
+            shared_ptr<IStatsCompanionService> statsCompanionService) {};
 
 protected:
     const int mTagId;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 3ceff75..1c38542 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -19,8 +19,6 @@
 
 #include "StatsPullerManager.h"
 
-#include <android/os/IPullAtomCallback.h>
-#include <android/os/IStatsCompanionService.h>
 #include <cutils/log.h>
 #include <math.h>
 #include <stdint.h>
@@ -87,7 +85,10 @@
         return;
     }
 
-    sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
+    // TODO(b/149254662): Why are we creating a copy here? This is different
+    // from the other places where we create a copy because we don't reassign
+    // mStatsCompanionService so a destructor can't implicitly be called...
+    shared_ptr<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
     if (statsCompanionServiceCopy != nullptr) {
         statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000);
     } else {
@@ -97,9 +98,11 @@
 }
 
 void StatsPullerManager::SetStatsCompanionService(
-        sp<IStatsCompanionService> statsCompanionService) {
+        shared_ptr<IStatsCompanionService> statsCompanionService) {
+    // TODO(b/149254662): Why are we using AutoMutex instead of lock_guard?
+    // Additionally, do we need the temporary shared_ptr to prevent deadlocks?
     AutoMutex _l(mLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+    shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
         pulledAtom.second->SetStatsCompanionService(statsCompanionService);
@@ -250,10 +253,11 @@
 void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag,
                                                   const int64_t coolDownNs, const int64_t timeoutNs,
                                                   const vector<int32_t>& additiveFields,
-                                                  const sp<IPullAtomCallback>& callback) {
+                                                  const shared_ptr<IPullAtomCallback>& callback) {
     AutoMutex _l(mLock);
     VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
-    // TODO: linkToDeath with the callback so that we can remove it and delete the puller.
+    // TODO(b/146439412): linkToDeath with the callback so that we can remove it
+    // and delete the puller.
     StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
     kAllPullAtomInfo[{.atomTag = atomTag}] =
             new StatsCallbackPuller(atomTag, callback, coolDownNs, timeoutNs, additiveFields);
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index aef16dc..e067766 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -16,9 +16,8 @@
 
 #pragma once
 
-#include <android/os/IPullAtomCallback.h>
-#include <android/os/IStatsCompanionService.h>
-#include <binder/IServiceManager.h>
+#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <utils/RefBase.h>
 #include <utils/threads.h>
 
@@ -30,11 +29,14 @@
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 
+using aidl::android::os::IPullAtomCallback;
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
-
 typedef struct PullerKey {
     // The uid of the process that registers this puller.
     const int uid = -1;
@@ -93,18 +95,18 @@
     // Clear pull data cache if it is beyond respective cool down time.
     int ClearPullerCacheIfNecessary(int64_t timestampNs);
 
-    void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+    void SetStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
 
     void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
                                   const int64_t timeoutNs, const vector<int32_t>& additiveFields,
-                                  const sp<IPullAtomCallback>& callback);
+                                  const shared_ptr<IPullAtomCallback>& callback);
 
     void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
 
     std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
 
 private:
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+    shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
 
     typedef struct {
         int64_t nextPullTimeNs;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 103eb0c..9c50846 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -20,7 +20,8 @@
 #include "stats_log_util.h"
 #include "statslog.h"
 
-#include <binder/IPCThreadState.h>
+#include <android/binder_ibinder.h>
+#include <android-base/stringprintf.h>
 #include <private/android_filesystem_config.h>
 
 namespace android {
@@ -31,6 +32,7 @@
 const int FIELD_ID_EXPERIMENT_ID = 1;
 
 using namespace android::util;
+using android::base::StringPrintf;
 using android::util::ProtoOutputStream;
 using std::string;
 using std::vector;
@@ -180,8 +182,8 @@
     mLogdTimestampNs = getWallClockNs();
     mElapsedTimestampNs = getElapsedRealtimeNs();
     mTagId = android::util::BINARY_PUSH_STATE_CHANGED;
-    mLogUid = android::IPCThreadState::self()->getCallingUid();
-    mLogPid = android::IPCThreadState::self()->getCallingPid();
+    mLogUid = AIBinder_getCallingUid();
+    mLogPid = AIBinder_getCallingPid();
 
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName)));
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode)));
@@ -290,9 +292,9 @@
 }
 
 bool LogEvent::writeBytes(const string& value) {
-    if (mContext) {
+    /* if (mContext) {
         return android_log_write_char_array(mContext, value.c_str(), value.length()) >= 0;
-    }
+    }*/
     return false;
 }
 
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index a67bef4..ea449b0 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -20,7 +20,6 @@
 
 #include <android/util/ProtoOutputStream.h>
 #include <private/android_logger.h>
-#include <stats_event_list.h>
 #include <stats_event.h>
 
 #include <string>
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 140ef4e..d79b6a2 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -20,9 +20,9 @@
 #include "StatsService.h"
 #include "socket/StatsSocketListener.h"
 
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_process.h>
+#include <android/binder_manager.h>
 #include <utils/Looper.h>
 
 #include <stdio.h>
@@ -32,16 +32,11 @@
 
 using namespace android;
 using namespace android::os::statsd;
+using ::ndk::SharedRefBase;
+using std::shared_ptr;
+using std::make_shared;
 
-/**
- * Thread function data.
- */
-struct log_reader_thread_data {
-    sp<StatsService> service;
-};
-
-
-sp<StatsService> gStatsService = nullptr;
+shared_ptr<StatsService> gStatsService = nullptr;
 
 void sigHandler(int sig) {
     if (gStatsService != nullptr) {
@@ -68,20 +63,18 @@
     sp<Looper> looper(Looper::prepare(0 /* opts */));
 
     // Set up the binder
-    sp<ProcessState> ps(ProcessState::self());
-    ps->setThreadPoolMaxThreadCount(9);
-    ps->startThreadPool();
-    ps->giveThreadPoolName();
-    IPCThreadState::self()->disableBackgroundScheduling(true);
+    ABinderProcess_setThreadPoolMaxThreadCount(9);
+    ABinderProcess_startThreadPool();
 
     std::shared_ptr<LogEventQueue> eventQueue =
             std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/);
 
     // Create the service
-    gStatsService = new StatsService(looper, eventQueue);
-    if (defaultServiceManager()->addService(String16("stats"), gStatsService, false,
-                IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO)
-            != 0) {
+    gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue);
+    // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports
+    // setting dumpsys priorities.
+    binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats");
+    if (status != STATUS_OK) {
         ALOGE("Failed to add service as AIDL service");
         return -1;
     }
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index bfac6e3..4e3c506 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -21,10 +21,11 @@
 #include "packages/PackageInfoListener.h"
 #include "stats_util.h"
 
-#include <binder/IShellCallback.h>
 #include <gtest/gtest_prod.h>
 #include <stdio.h>
 #include <utils/RefBase.h>
+#include <utils/String16.h>
+
 #include <list>
 #include <mutex>
 #include <set>
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index d6a0433..a861a3b 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -29,49 +29,88 @@
 
 const static int FIELD_ID_ATOM = 1;
 
-void ShellSubscriber::startNewSubscription(int in, int out, sp<IResultReceiver> resultReceiver,
-                                           int timeoutSec) {
+void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) {
     VLOG("start new shell subscription");
+    int64_t subscriberId = getElapsedRealtimeNs();
+
     {
         std::lock_guard<std::mutex> lock(mMutex);
-        if (mResultReceiver != nullptr) {
+        if (mSubscriberId> 0) {
             VLOG("Only one shell subscriber is allowed.");
             return;
         }
+        mSubscriberId = subscriberId;
         mInput = in;
         mOutput = out;
-        mResultReceiver = resultReceiver;
-        IInterface::asBinder(mResultReceiver)->linkToDeath(this);
     }
 
-    // Note that the following is blocking, and it's intended as we cannot return until the shell
-    // cmd exits, otherwise all resources & FDs will be automatically closed.
+    bool success = readConfig();
+    if (!success) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        cleanUpLocked();
+    }
 
-    // Read config forever until EOF is reached. Clients may send multiple configs -- each new
-    // config replace the previous one.
-    readConfig(in);
-    VLOG("timeout : %d", timeoutSec);
-
-    // Now we have read an EOF we now wait for the semaphore until the client exits.
-    VLOG("Now wait for client to exit");
+    VLOG("Wait for client to exit or timeout (%d sec)", timeoutSec);
     std::unique_lock<std::mutex> lk(mMutex);
 
+    // Note that the following is blocking, and it's intended as we cannot return until the shell
+    // cmd exits or we time out.
     if (timeoutSec > 0) {
         mShellDied.wait_for(lk, timeoutSec * 1s,
-                            [this, resultReceiver] { return mResultReceiver != resultReceiver; });
+                            [this, subscriberId] { return mSubscriberId != subscriberId; });
     } else {
-        mShellDied.wait(lk, [this, resultReceiver] { return mResultReceiver != resultReceiver; });
+        mShellDied.wait(lk, [this, subscriberId] { return mSubscriberId != subscriberId; });
+    }
+}
+
+
+// Read configs until EOF is reached. There may be multiple configs in the input
+// -- each new config should replace the previous one.
+//
+// Returns a boolean indicating whether the input was read successfully.
+bool ShellSubscriber::readConfig() {
+    if (mInput < 0) {
+        return false;
+    }
+
+    while (true) {
+        // Read the size of the config.
+        size_t bufferSize = 0;
+        ssize_t bytesRead = read(mInput, &bufferSize, sizeof(bufferSize));
+        if (bytesRead == 0) {
+            VLOG("We have reached the end of the input.");
+            return true;
+        } else if (bytesRead < 0 || (size_t)bytesRead != sizeof(bufferSize)) {
+            ALOGE("Error reading config size");
+            return false;
+        }
+
+        // Read and parse the config.
+        vector<uint8_t> buffer(bufferSize);
+        bytesRead = read(mInput, buffer.data(), bufferSize);
+        if (bytesRead > 0 && (size_t)bytesRead == bufferSize) {
+            ShellSubscription config;
+            if (config.ParseFromArray(buffer.data(), bufferSize)) {
+                updateConfig(config);
+            } else {
+                ALOGE("Error parsing the config");
+                return false;
+            }
+        } else {
+            VLOG("Error reading the config, expected bytes: %zu, actual bytes: %zu", bufferSize, 
+                 bytesRead);
+            return false;
+        }
     }
 }
 
 void ShellSubscriber::updateConfig(const ShellSubscription& config) {
-    std::lock_guard<std::mutex> lock(mMutex);
     mPushedMatchers.clear();
     mPulledInfo.clear();
 
     for (const auto& pushed : config.pushed()) {
         mPushedMatchers.push_back(pushed);
-        VLOG("adding matcher for atom %d", pushed.atom_id());
+        VLOG("adding matcher for pushed atom %d", pushed.atom_id());
     }
 
     int64_t token = getElapsedRealtimeNs();
@@ -89,46 +128,20 @@
     }
 
     if (mPulledInfo.size() > 0 && minInterval > 0) {
-        // This thread is guaranteed to terminate after it detects the token is different or
-        // cleaned up.
+        // This thread is guaranteed to terminate after it detects the token is
+        // different.
         std::thread puller([token, minInterval, this] { startPull(token, minInterval); });
         puller.detach();
     }
 }
 
-void ShellSubscriber::writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data,
-                                          const SimpleAtomMatcher& matcher) {
-    if (mOutput == 0) return;
-    int count = 0;
-    mProto.clear();
-    for (const auto& event : data) {
-        VLOG("%s", event->ToString().c_str());
-        if (matchesSimple(*mUidMap, matcher, *event)) {
-            VLOG("matched");
-            count++;
-            uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
-                                              util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
-            event->ToProto(mProto);
-            mProto.end(atomToken);
-        }
-    }
-
-    if (count > 0) {
-        // First write the payload size.
-        size_t bufferSize = mProto.size();
-        write(mOutput, &bufferSize, sizeof(bufferSize));
-        VLOG("%d atoms, proto size: %zu", count, bufferSize);
-        // Then write the payload.
-        mProto.flush(mOutput);
-    }
-    mProto.clear();
-}
-
 void ShellSubscriber::startPull(int64_t token, int64_t intervalMillis) {
-    while (1) {
+    while (true) {
         int64_t nowMillis = getElapsedRealtimeMillis();
         {
             std::lock_guard<std::mutex> lock(mMutex);
+            // If the token has changed, the config has changed, so this
+            // puller can now stop.
             if (mPulledInfo.size() == 0 || mPullToken != token) {
                 VLOG("Pulling thread %lld done!", (long long)token);
                 return;
@@ -152,55 +165,47 @@
     }
 }
 
-void ShellSubscriber::readConfig(int in) {
-    if (in <= 0) {
+// Must be called with the lock acquired, so that mProto isn't being written to
+// at the same time by multiple threads.
+void ShellSubscriber::writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data,
+                                          const SimpleAtomMatcher& matcher) {
+    if (mOutput < 0) {
         return;
     }
-
-    while (1) {
-        size_t bufferSize = 0;
-        int result = 0;
-        if ((result = read(in, &bufferSize, sizeof(bufferSize))) == 0) {
-            VLOG("Done reading");
-            break;
-        } else if (result < 0 || result != sizeof(bufferSize)) {
-            ALOGE("Error reading config size");
-            break;
-        }
-
-        vector<uint8_t> buffer(bufferSize);
-        if ((result = read(in, buffer.data(), bufferSize)) > 0 && ((size_t)result) == bufferSize) {
-            ShellSubscription config;
-            if (config.ParseFromArray(buffer.data(), bufferSize)) {
-                updateConfig(config);
-            } else {
-                ALOGE("error parsing the config");
-                break;
-            }
-        } else {
-            VLOG("Error reading the config, returned: %d, expecting %zu", result, bufferSize);
-            break;
+    int count = 0;
+    mProto.clear();
+    for (const auto& event : data) {
+        VLOG("%s", event->ToString().c_str());
+        if (matchesSimple(*mUidMap, matcher, *event)) {
+            VLOG("matched");
+            count++;
+            uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
+                                              util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
+            event->ToProto(mProto);
+            mProto.end(atomToken);
         }
     }
-}
 
-void ShellSubscriber::cleanUpLocked() {
-    // The file descriptors will be closed by binder.
-    mInput = 0;
-    mOutput = 0;
-    mResultReceiver = nullptr;
-    mPushedMatchers.clear();
-    mPulledInfo.clear();
-    mPullToken = 0;
-    VLOG("done clean up");
+    if (count > 0) {
+        // First write the payload size.
+        size_t bufferSize = mProto.size();
+        write(mOutput, &bufferSize, sizeof(bufferSize));
+
+        VLOG("%d atoms, proto size: %zu", count, bufferSize);
+        // Then write the payload.
+        mProto.flush(mOutput);
+    }
 }
 
 void ShellSubscriber::onLogEvent(const LogEvent& event) {
+    // Acquire a lock to prevent corruption from multiple threads writing to
+    // mProto.
     std::lock_guard<std::mutex> lock(mMutex);
-
-    if (mOutput <= 0) {
+    if (mOutput < 0) {
         return;
     }
+
+    mProto.clear();
     for (const auto& matcher : mPushedMatchers) {
         if (matchesSimple(*mUidMap, matcher, event)) {
             VLOG("%s", event.ToString().c_str());
@@ -208,25 +213,27 @@
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
             event.ToProto(mProto);
             mProto.end(atomToken);
+
             // First write the payload size.
             size_t bufferSize = mProto.size();
             write(mOutput, &bufferSize, sizeof(bufferSize));
 
             // Then write the payload.
             mProto.flush(mOutput);
-            mProto.clear();
-            break;
         }
     }
 }
 
-void ShellSubscriber::binderDied(const wp<IBinder>& who) {
-    {
-        VLOG("Shell exits");
-        std::lock_guard<std::mutex> lock(mMutex);
-        cleanUpLocked();
-    }
-    mShellDied.notify_all();
+void ShellSubscriber::cleanUpLocked() {
+    // The file descriptors will be closed by binder.
+    mInput = -1;
+    mOutput = -1;
+    mSubscriberId = 0;
+    mPushedMatchers.clear();
+    mPulledInfo.clear();
+    // Setting mPullToken == 0 tells pull thread that its work is done.
+    mPullToken = 0;
+    VLOG("done clean up");
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/shell/ShellSubscriber.h b/cmds/statsd/src/shell/ShellSubscriber.h
index 86d8590..eaf2ad1 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.h
+++ b/cmds/statsd/src/shell/ShellSubscriber.h
@@ -19,7 +19,6 @@
 #include "logd/LogEvent.h"
 
 #include <android/util/ProtoOutputStream.h>
-#include <binder/IResultReceiver.h>
 #include <condition_variable>
 #include <mutex>
 #include <thread>
@@ -56,7 +55,7 @@
  * Only one shell subscriber allowed at a time, because each shell subscriber blocks one thread
  * until it exits.
  */
-class ShellSubscriber : public virtual IBinder::DeathRecipient {
+class ShellSubscriber : public virtual RefBase {
 public:
     ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
         : mUidMap(uidMap), mPullerMgr(pullerMgr){};
@@ -64,10 +63,7 @@
     /**
      * Start a new subscription.
      */
-    void startNewSubscription(int inFd, int outFd, sp<IResultReceiver> resultReceiver,
-                              int timeoutSec);
-
-    void binderDied(const wp<IBinder>& who);
+    void startNewSubscription(int inFd, int outFd, int timeoutSec);
 
     void onLogEvent(const LogEvent& event);
 
@@ -80,7 +76,7 @@
         int64_t mInterval;
         int64_t mPrevPullElapsedRealtimeMs;
     };
-    void readConfig(int in);
+    bool readConfig();
 
     void updateConfig(const ShellSubscription& config);
 
@@ -101,16 +97,16 @@
 
     std::condition_variable mShellDied;  // semaphore for waiting until shell exits.
 
-    int mInput;  // The input file descriptor
+    int mInput = -1;  // The input file descriptor
 
-    int mOutput;  // The output file descriptor
-
-    sp<IResultReceiver> mResultReceiver;
+    int mOutput = -1;  // The output file descriptor
 
     std::vector<SimpleAtomMatcher> mPushedMatchers;
 
     std::vector<PullInfo> mPulledInfo;
 
+    int64_t mSubscriberId = 0; // A unique id to identify a subscriber.
+
     int64_t mPullToken = 0;  // A unique token to identify a puller thread.
 };
 
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 73f640e..3d02ffb 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -17,6 +17,7 @@
 #include "hash.h"
 #include "stats_log_util.h"
 
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <private/android_filesystem_config.h>
 #include <set>
 #include <utils/SystemClock.h>
@@ -35,6 +36,10 @@
 using android::util::FIELD_TYPE_UINT64;
 using android::util::ProtoOutputStream;
 
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+using std::string;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -587,13 +592,13 @@
 }
 
 bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) {
-    sp<IStatsCompanionService> scs = getStatsCompanionService();
+    shared_ptr<IStatsCompanionService> scs = getStatsCompanionService();
     if (scs == nullptr) {
         return false;
     }
 
     bool success;
-    binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success);
+    ::ndk::ScopedAStatus status = scs->checkPermission(string(permission), pid, uid, &success);
     if (!status.isOk()) {
         return false;
     }
diff --git a/cmds/statsd/src/statscompanion_util.cpp b/cmds/statsd/src/statscompanion_util.cpp
index d338827..ce07ec0 100644
--- a/cmds/statsd/src/statscompanion_util.cpp
+++ b/cmds/statsd/src/statscompanion_util.cpp
@@ -18,26 +18,16 @@
 #include "Log.h"
 
 #include "statscompanion_util.h"
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
 
 namespace android {
 namespace os {
 namespace statsd {
 
-sp <IStatsCompanionService> getStatsCompanionService() {
-    sp<IStatsCompanionService> statsCompanion = nullptr;
-    // Get statscompanion service from service manager
-    static const sp <IServiceManager> sm(defaultServiceManager());
-    if (statsCompanion == nullptr) {
-        if (sm != nullptr) {
-            const String16 name("statscompanion");
-            statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
-            if (statsCompanion == nullptr) {
-                ALOGW("statscompanion service unavailable!");
-                return nullptr;
-            }
-        }
-    }
-    return statsCompanion;
+shared_ptr<IStatsCompanionService> getStatsCompanionService() {
+    ::ndk::SpAIBinder binder(AServiceManager_getService("statscompanion"));
+    return IStatsCompanionService::fromBinder(binder);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/statscompanion_util.h b/cmds/statsd/src/statscompanion_util.h
index dc4f283..e20c40b 100644
--- a/cmds/statsd/src/statscompanion_util.h
+++ b/cmds/statsd/src/statscompanion_util.h
@@ -16,14 +16,17 @@
 
 #pragma once
 
-#include "StatsLogProcessor.h"
+#include <aidl/android/os/IStatsCompanionService.h>
+
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
 namespace statsd {
 
 /** Fetches and returns the StatsCompanionService. */
-sp<IStatsCompanionService> getStatsCompanionService();
+shared_ptr<IStatsCompanionService> getStatsCompanionService();
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 1bac19e..4899b4a 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -690,14 +690,16 @@
         if (name[0] == '.') continue;
 
         FileName output;
+        string file_name;
         if (parseTimestampOnly) {
+            file_name = StringPrintf("%s/%s", path, name);
             output.mTimestampSec = StrToInt64(strtok(name, "_"));
             output.mIsHistory = false;
         } else {
             parseFileName(name, &output);
+            file_name = output.getFullFileName(path);
         }
         if (output.mTimestampSec == -1) continue;
-        string file_name = output.getFullFileName(path);
 
         // Check for timestamp and delete if it's too old.
         long fileAge = nowSec - output.mTimestampSec;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index 8fd6b46..93af5e9 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -27,38 +27,52 @@
 
 using std::vector;
 
-class BroadcastSubscriberDeathRecipient : public android::IBinder::DeathRecipient {
-    public:
-        BroadcastSubscriberDeathRecipient(const ConfigKey& configKey, int64_t subscriberId):
-            mConfigKey(configKey),
-            mSubscriberId(subscriberId) {}
-        ~BroadcastSubscriberDeathRecipient() override = default;
-    private:
-        ConfigKey mConfigKey;
-        int64_t mSubscriberId;
+struct BroadcastSubscriberDeathCookie {
+    BroadcastSubscriberDeathCookie(const ConfigKey& configKey, int64_t subscriberId,
+                                   const shared_ptr<IPendingIntentRef>& pir):
+        mConfigKey(configKey),
+        mSubscriberId(subscriberId),
+        mPir(pir) {}
 
-    void binderDied(const android::wp<android::IBinder>& who) override {
-        if (IInterface::asBinder(SubscriberReporter::getInstance().getBroadcastSubscriber(
-              mConfigKey, mSubscriberId)) == who.promote()) {
-            SubscriberReporter::getInstance().unsetBroadcastSubscriber(mConfigKey, mSubscriberId);
-        }
-    }
+    ConfigKey mConfigKey;
+    int64_t mSubscriberId;
+    shared_ptr<IPendingIntentRef> mPir;
 };
 
+static void broadcastSubscriberDied(void* cookie) {
+    BroadcastSubscriberDeathCookie* cookie_ = (BroadcastSubscriberDeathCookie*)cookie;
+    ConfigKey configKey = cookie_->mConfigKey;
+    int64_t subscriberId = cookie_->mSubscriberId;
+    shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+
+    // TODO(b/149254662): Fix threading. This currently fails if a new pir gets
+    // set between the get and the unset.
+    if (SubscriberReporter::getInstance().getBroadcastSubscriber(configKey, subscriberId) == pir) {
+        SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey, subscriberId);
+    }
+    // The death recipient corresponding to this specific pir can never be
+    // triggered again, so free up resources.
+    delete cookie_;
+}
+
+static ::ndk::ScopedAIBinder_DeathRecipient sBroadcastSubscriberDeathRecipient(
+        AIBinder_DeathRecipient_new(broadcastSubscriberDied));
+
 void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
                                                 int64_t subscriberId,
-                                                const sp<IPendingIntentRef>& pir) {
+                                                const shared_ptr<IPendingIntentRef>& pir) {
     VLOG("SubscriberReporter::setBroadcastSubscriber called.");
-    lock_guard<std::mutex> lock(mLock);
+    lock_guard<mutex> lock(mLock);
     mIntentMap[configKey][subscriberId] = pir;
-    IInterface::asBinder(pir)->linkToDeath(
-        new BroadcastSubscriberDeathRecipient(configKey, subscriberId));
+    // TODO(b/149254662): Is it ok to call linkToDeath while holding a lock?
+    AIBinder_linkToDeath(pir->asBinder().get(), sBroadcastSubscriberDeathRecipient.get(),
+                         new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir));
 }
 
 void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
                                                   int64_t subscriberId) {
     VLOG("SubscriberReporter::unsetBroadcastSubscriber called.");
-    lock_guard<std::mutex> lock(mLock);
+    lock_guard<mutex> lock(mLock);
     auto subscriberMapIt = mIntentMap.find(configKey);
     if (subscriberMapIt != mIntentMap.end()) {
         subscriberMapIt->second.erase(subscriberId);
@@ -80,7 +94,7 @@
     //  config id - the name of this config (for this particular uid)
 
     VLOG("SubscriberReporter::alertBroadcastSubscriber called.");
-    lock_guard<std::mutex> lock(mLock);
+    lock_guard<mutex> lock(mLock);
 
     if (!subscription.has_broadcast_subscriber_details()
             || !subscription.broadcast_subscriber_details().has_subscriber_id()) {
@@ -89,10 +103,12 @@
     }
     int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
 
-    vector<String16> cookies;
+    // TODO(b/149254662): Is there a way to convert a RepeatedPtrField into a
+    // vector without copying?
+    vector<string> cookies;
     cookies.reserve(subscription.broadcast_subscriber_details().cookie_size());
     for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) {
-        cookies.push_back(String16(cookie.c_str()));
+        cookies.push_back(cookie);
     }
 
     auto it1 = mIntentMap.find(configKey);
@@ -109,10 +125,10 @@
     sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey);
 }
 
-void SubscriberReporter::sendBroadcastLocked(const sp<IPendingIntentRef>& pir,
+void SubscriberReporter::sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir,
                                              const ConfigKey& configKey,
                                              const Subscription& subscription,
-                                             const vector<String16>& cookies,
+                                             const vector<string>& cookies,
                                              const MetricDimensionKey& dimKey) const {
     VLOG("SubscriberReporter::sendBroadcastLocked called.");
     pir->sendSubscriberBroadcast(
@@ -124,9 +140,9 @@
             dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel());
 }
 
-sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
-                                                                 int64_t subscriberId) {
-    lock_guard<std::mutex> lock(mLock);
+shared_ptr<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
+                                                                         int64_t subscriberId) {
+    lock_guard<mutex> lock(mLock);
     auto subscriberMapIt = mIntentMap.find(configKey);
     if (subscriberMapIt == mIntentMap.end()) {
         return nullptr;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 42599f5..0f97d39 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -16,9 +16,9 @@
 
 #pragma once
 
-#include <android/os/IPendingIntentRef.h>
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IPendingIntentRef.h>
 #include <utils/RefBase.h>
+#include <utils/String16.h>
 
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // subscription
@@ -28,6 +28,13 @@
 #include <unordered_map>
 #include <vector>
 
+using aidl::android::os::IPendingIntentRef;
+using std::mutex;
+using std::shared_ptr;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -51,7 +58,7 @@
      */
     void setBroadcastSubscriber(const ConfigKey& configKey,
                                 int64_t subscriberId,
-                                const sp<IPendingIntentRef>& pir);
+                                const shared_ptr<IPendingIntentRef>& pir);
 
     /**
      * Erases any intentSender information from the given (configKey, subscriberId) pair.
@@ -67,28 +74,25 @@
                                   const Subscription& subscription,
                                   const MetricDimensionKey& dimKey) const;
 
-    sp<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
+    shared_ptr<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey,
+                                                         int64_t subscriberId);
 
 private:
     SubscriberReporter() {};
 
-    mutable std::mutex mLock;
-
-    /** Binder interface for communicating with StatsCompanionService. */
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+    mutable mutex mLock;
 
     /** Maps <ConfigKey, SubscriberId> -> IPendingIntentRef (which represents a PendingIntent). */
-    std::unordered_map<ConfigKey,
-            std::unordered_map<int64_t, sp<IPendingIntentRef>>> mIntentMap;
+    unordered_map<ConfigKey, unordered_map<int64_t, shared_ptr<IPendingIntentRef>>> mIntentMap;
 
     /**
      * Sends a broadcast via the given intentSender (using mStatsCompanionService), along
      * with the information in the other parameters.
      */
-    void sendBroadcastLocked(const sp<IPendingIntentRef>& pir,
+    void sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir,
                              const ConfigKey& configKey,
                              const Subscription& subscription,
-                             const std::vector<String16>& cookies,
+                             const vector<string>& cookies,
                              const MetricDimensionKey& dimKey) const;
 };
 
diff --git a/cmds/statsd/tests/AlarmMonitor_test.cpp b/cmds/statsd/tests/AlarmMonitor_test.cpp
index 1fccb35..edbf8b5 100644
--- a/cmds/statsd/tests/AlarmMonitor_test.cpp
+++ b/cmds/statsd/tests/AlarmMonitor_test.cpp
@@ -17,14 +17,16 @@
 #include <gtest/gtest.h>
 
 using namespace android::os::statsd;
+using std::shared_ptr;
 
 #ifdef __ANDROID__
 TEST(AlarmMonitor, popSoonerThan) {
     std::string emptyMetricId;
     std::string emptyDimensionId;
     unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> set;
-    AlarmMonitor am(2, [](const sp<IStatsCompanionService>&, int64_t){},
-                    [](const sp<IStatsCompanionService>&){});
+    AlarmMonitor am(2,
+                    [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                    [](const shared_ptr<IStatsCompanionService>&){});
 
     set = am.popSoonerThan(5);
     EXPECT_TRUE(set.empty());
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 69e11ed..3d7e5c9 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -32,6 +32,8 @@
 
 using namespace android;
 using namespace testing;
+using ::ndk::SharedRefBase;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -49,10 +51,12 @@
     MockMetricsManager()
         : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(),
                          new StatsPullerManager(),
-                         new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {},
-                                          [](const sp<IStatsCompanionService>&) {}),
-                         new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {},
-                                          [](const sp<IStatsCompanionService>&) {})) {
+                         new AlarmMonitor(10,
+                                          [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                          [](const shared_ptr<IStatsCompanionService>&) {}),
+                         new AlarmMonitor(10,
+                                          [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                          [](const shared_ptr<IStatsCompanionService>&) {})) {
     }
 
     MOCK_METHOD0(byteSize, size_t());
@@ -1488,9 +1492,9 @@
     metric2ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY);
 
     // Send the config.
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     string serialized = config1.SerializeAsString();
-    service.addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
+    service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
 
     // Make sure the config is stored on disk. Otherwise, we will not reset on system server death.
     StatsdConfig tmpConfig;
@@ -1501,7 +1505,7 @@
     // Metric 2 is not active.
     // Metric 3 is active.
     // {{{---------------------------------------------------------------------------
-    sp<StatsLogProcessor> processor = service.mProcessor;
+    sp<StatsLogProcessor> processor = service->mProcessor;
     EXPECT_EQ(1, processor->mMetricsManagers.size());
     auto it = processor->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor->mMetricsManagers.end());
@@ -1592,7 +1596,7 @@
     EXPECT_TRUE(approximateSystemServerDeath < NS_PER_SEC + configAddedTimeNs);
 
     // System server dies.
-    service.binderDied(nullptr);
+    service->statsCompanionServiceDiedImpl();
 
     // We should have a new metrics manager. Lets get it and ensure activation status is restored.
     // {{{---------------------------------------------------------------------------
diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp
index 7c00531..86f786e 100644
--- a/cmds/statsd/tests/StatsService_test.cpp
+++ b/cmds/statsd/tests/StatsService_test.cpp
@@ -16,6 +16,7 @@
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
+#include <android/binder_interface_utils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -29,33 +30,34 @@
 namespace statsd {
 
 using android::util::ProtoOutputStream;
+using ::ndk::SharedRefBase;
 
 #ifdef __ANDROID__
 
 TEST(StatsServiceTest, TestAddConfig_simple) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     StatsdConfig config;
     config.set_id(12345);
     string serialized = config.SerializeAsString();
 
     EXPECT_TRUE(
-            service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+            service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
 }
 
 TEST(StatsServiceTest, TestAddConfig_empty) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     string serialized = "";
 
     EXPECT_TRUE(
-            service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+            service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
 }
 
 TEST(StatsServiceTest, TestAddConfig_invalid) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     string serialized = "Invalid config!";
 
     EXPECT_FALSE(
-            service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+            service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
 }
 
 TEST(StatsServiceTest, TestGetUidFromArgs) {
@@ -69,32 +71,32 @@
 
     int32_t uid;
 
-    StatsService service(nullptr, nullptr);
-    service.mEngBuild = true;
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mEngBuild = true;
 
     // "-1"
-    EXPECT_FALSE(service.getUidFromArgs(args, 0, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 0, uid));
 
     // "0"
-    EXPECT_TRUE(service.getUidFromArgs(args, 1, uid));
+    EXPECT_TRUE(service->getUidFromArgs(args, 1, uid));
     EXPECT_EQ(0, uid);
 
     // "1"
-    EXPECT_TRUE(service.getUidFromArgs(args, 2, uid));
+    EXPECT_TRUE(service->getUidFromArgs(args, 2, uid));
     EXPECT_EQ(1, uid);
 
     // "999999999999999999"
-    EXPECT_FALSE(service.getUidFromArgs(args, 3, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 3, uid));
 
     // "a1"
-    EXPECT_FALSE(service.getUidFromArgs(args, 4, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 4, uid));
 
     // ""
-    EXPECT_FALSE(service.getUidFromArgs(args, 5, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 5, uid));
 
     // For a non-userdebug, uid "1" cannot be impersonated.
-    service.mEngBuild = false;
-    EXPECT_FALSE(service.getUidFromArgs(args, 2, uid));
+    service->mEngBuild = false;
+    EXPECT_FALSE(service->getUidFromArgs(args, 2, uid));
 }
 
 #else
diff --git a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
index 5e6de1c..90ffcd0 100644
--- a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
@@ -15,12 +15,14 @@
 #include "src/anomaly/AlarmTracker.h"
 
 #include <gtest/gtest.h>
+#include <log/log_time.h>
 #include <stdio.h>
 #include <vector>
 
 using namespace testing;
 using android::sp;
 using std::set;
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
@@ -34,8 +36,9 @@
 
 TEST(AlarmTrackerTest, TestTriggerTimestamp) {
     sp<AlarmMonitor> subscriberAlarmMonitor =
-        new AlarmMonitor(100, [](const sp<IStatsCompanionService>&, int64_t){},
-                         [](const sp<IStatsCompanionService>&){});
+        new AlarmMonitor(100,
+                         [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                         [](const shared_ptr<IStatsCompanionService>&){});
     Alarm alarm;
     alarm.set_offset_millis(15 * MS_PER_SEC);
     alarm.set_period_millis(60 * 60 * MS_PER_SEC);  // 1hr
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index 5e60aba..9127be8 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android/binder_interface_utils.h>
 #include <gtest/gtest.h>
 
 #include <vector>
@@ -20,6 +21,8 @@
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
+using ::ndk::SharedRefBase;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -71,7 +74,8 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(), ATOM_TAG);
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             ATOM_TAG);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
@@ -216,7 +220,8 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(), ATOM_TAG);
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             ATOM_TAG);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
@@ -317,7 +322,8 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback, ATOM_TAG);
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             ATOM_TAG);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
@@ -426,7 +432,8 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(), ATOM_TAG);
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             ATOM_TAG);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
@@ -544,7 +551,8 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(), ATOM_TAG);
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             ATOM_TAG);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 9d58867..1eecbe5 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -12,9 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <android/os/BnPullAtomCallback.h>
-#include <android/os/IPullAtomResultReceiver.h>
-#include <binder/IPCThreadState.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_interface_utils.h>
 #include <gtest/gtest.h>
 
 #include <vector>
@@ -24,6 +23,9 @@
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
+using::ndk::SharedRefBase;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -34,19 +36,17 @@
 const int kConfigKey = 789130123;  // Randomly chosen to avoid collisions with existing configs.
 const int kCallingUid = 0; // Randomly chosen
 
-void SendConfig(StatsService& service, const StatsdConfig& config) {
+void SendConfig(shared_ptr<StatsService>& service, const StatsdConfig& config) {
     string str;
     config.SerializeToString(&str);
-    std::vector<uint8_t> configAsVec(str.begin(), str.end());
-    bool success;
-    service.addConfiguration(kConfigKey, configAsVec, kCallingUid);
+    std::vector<int8_t> configAsVec(str.begin(), str.end());
+    service->addConfiguration(kConfigKey, configAsVec, kCallingUid);
 }
 
 ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp,
                                bool include_current = false) {
     vector<uint8_t> output;
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), kConfigKey);
+    ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey);
     processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/,
                             true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output);
     ConfigMetricsReportList reports;
@@ -114,56 +114,56 @@
 }  // anonymous namespace
 
 TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 2).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 2).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 3);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 3);
     // Expect no metrics since the bucket has not finished yet.
     EXPECT_EQ(1, report.metrics_size());
     EXPECT_EQ(0, report.metrics(0).count_metrics().data_size());
 }
 
 TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     // Force the uidmap to update at timestamp 2.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
     // This is a new installation, so there shouldn't be a split (should be same as the without
     // split case).
-    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+    service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
                                String16(""));
     // Goes into the second bucket.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
     EXPECT_EQ(1, report.metrics_size());
     EXPECT_EQ(0, report.metrics(0).count_metrics().data_size());
 }
 
 TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
-    service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
-                               {String16("")});
+    service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+                                {String16("")});
 
     // Force the uidmap to update at timestamp 2.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+    service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
                                String16(""));
     // Goes into the second bucket.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
@@ -177,20 +177,20 @@
 }
 
 TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
-    service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
-                               {String16("")});
+    service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+                                {String16("")});
 
     // Force the uidmap to update at timestamp 2.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1);
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+    service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1);
     // Goes into the second bucket.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
@@ -204,44 +204,44 @@
 }
 
 TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
-    service.mPullerManager->RegisterPullAtomCallback(
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mPullerManager->RegisterPullAtomCallback(
             /*uid=*/0, android::util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
-            new FakeSubsystemSleepCallback());
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeValueMetricConfig(0));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
-                               String16("v2"), String16(""));
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+                                String16("v2"), String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
     EXPECT_EQ(1, report.metrics_size());
     EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size());
 }
 
 TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
-    service.mPullerManager->RegisterPullAtomCallback(
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mPullerManager->RegisterPullAtomCallback(
             /*uid=*/0, android::util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
-            new FakeSubsystemSleepCallback());
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
@@ -253,38 +253,38 @@
 }
 
 TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeGaugeMetricConfig(0));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
                                String16("v2"), String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
     EXPECT_EQ(1, report.metrics_size());
     EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
 }
 
 TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
-                               String16(""));
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
     backfillStartEndTimestamp(&report);
     ASSERT_EQ(1, report.metrics_size());
     ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index a140af8..9d39f9c 100644
--- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android/binder_interface_utils.h>
 #include <gtest/gtest.h>
 
 #include "src/StatsLogProcessor.h"
@@ -20,6 +21,8 @@
 
 #include <vector>
 
+using ::ndk::SharedRefBase;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -70,7 +73,7 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(),
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
                                              android::util::SUBSYSTEM_SLEEP_STATE);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -175,7 +178,7 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(),
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
                                              android::util::SUBSYSTEM_SLEEP_STATE);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -288,7 +291,7 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
-                                             new FakeSubsystemSleepCallback(),
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
                                              android::util::SUBSYSTEM_SLEEP_STATE);
     EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index a011692e..e416b4c 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -14,8 +14,10 @@
 
 #include "src/external/StatsCallbackPuller.h"
 
-#include <android/os/BnPullAtomCallback.h>
-#include <android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
+#include <android/binder_interface_utils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
@@ -35,6 +37,11 @@
 namespace statsd {
 
 using namespace testing;
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnPullAtomCallback;
+using aidl::android::os::IPullAtomResultReceiver;
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
 using std::make_shared;
 using std::shared_ptr;
 using std::vector;
@@ -58,15 +65,15 @@
     return event;
 }
 
-void executePull(const sp<IPullAtomResultReceiver>& resultReceiver) {
+void executePull(const shared_ptr<IPullAtomResultReceiver>& resultReceiver) {
     // Convert stats_events into StatsEventParcels.
-    std::vector<android::util::StatsEventParcel> parcels;
+    vector<StatsEventParcel> parcels;
     for (int i = 0; i < values.size(); i++) {
         AStatsEvent* event = createSimpleEvent(values[i]);
         size_t size;
         uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
 
-        android::util::StatsEventParcel p;
+        StatsEventParcel p;
         // vector.assign() creates a copy, but this is inevitable unless
         // stats_event.h/c uses a vector as opposed to a buffer.
         p.buffer.assign(buffer, buffer + size);
@@ -80,11 +87,11 @@
 
 class FakePullAtomCallback : public BnPullAtomCallback {
 public:
-    binder::Status onPullAtom(int atomTag,
-                              const sp<IPullAtomResultReceiver>& resultReceiver) override {
+    Status onPullAtom(int atomTag,
+                      const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
         // Force pull to happen in separate thread to simulate binder.
         pullThread = std::thread(executePull, resultReceiver);
-        return binder::Status::ok();
+        return Status::ok();
     }
 };
 
@@ -111,7 +118,7 @@
 }  // Anonymous namespace.
 
 TEST_F(StatsCallbackPullerTest, PullSuccess) {
-    sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
     int64_t value = 43;
     pullSuccess = true;
     values.push_back(value);
@@ -132,20 +139,20 @@
 }
 
 TEST_F(StatsCallbackPullerTest, PullFail) {
-    sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
     pullSuccess = false;
     int64_t value = 1234;
     values.push_back(value);
 
     StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
 
-    vector<std::shared_ptr<LogEvent>> dataHolder;
+    vector<shared_ptr<LogEvent>> dataHolder;
     EXPECT_FALSE(puller.PullInternal(&dataHolder));
     EXPECT_EQ(0, dataHolder.size());
 }
 
 TEST_F(StatsCallbackPullerTest, PullTimeout) {
-    sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
     pullSuccess = true;
     pullDelayNs = 500000000;  // 500ms.
     pullTimeoutNs = 10000;    // 10 microseconds.
@@ -154,7 +161,7 @@
 
     StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
 
-    vector<std::shared_ptr<LogEvent>> dataHolder;
+    vector<shared_ptr<LogEvent>> dataHolder;
     int64_t startTimeNs = getElapsedRealtimeNs();
     // Returns true to let StatsPuller code evaluate the timeout.
     EXPECT_TRUE(puller.PullInternal(&dataHolder));
@@ -174,7 +181,7 @@
 
 // Register a puller and ensure that the timeout logic works.
 TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) {
-    sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
     pullSuccess = true;
     pullDelayNs = 500000000;  // 500 ms.
     pullTimeoutNs = 10000;    // 10 microsseconds.
@@ -184,7 +191,7 @@
     StatsPullerManager pullerManager;
     pullerManager.RegisterPullAtomCallback(/*uid=*/-1, pullTagId, pullCoolDownNs, pullTimeoutNs,
                                            vector<int32_t>(), cb);
-    vector<std::shared_ptr<LogEvent>> dataHolder;
+    vector<shared_ptr<LogEvent>> dataHolder;
     int64_t startTimeNs = getElapsedRealtimeNs();
     // Returns false, since StatsPuller code will evaluate the timeout.
     EXPECT_FALSE(pullerManager.Pull(pullTagId, &dataHolder));
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index 73d1fd7..dac5f33 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -34,27 +34,6 @@
 
 #ifdef __ANDROID__
 
-class MyResultReceiver : public BnResultReceiver {
-public:
-    Mutex mMutex;
-    Condition mCondition;
-    bool mHaveResult = false;
-    int32_t mResult = 0;
-
-    virtual void send(int32_t resultCode) {
-        AutoMutex _l(mMutex);
-        mResult = resultCode;
-        mHaveResult = true;
-        mCondition.signal();
-    }
-
-    int32_t waitForResult() {
-        AutoMutex _l(mMutex);
-        mCondition.waitRelative(mMutex, 1000000000);
-        return mResult;
-    }
-};
-
 void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap,
                   sp<MockStatsPullerManager> pullerManager,
                   const vector<std::shared_ptr<LogEvent>>& pushedEvents,
@@ -67,10 +46,7 @@
     ASSERT_EQ(0, pipe(fds_data));
 
     size_t bufferSize = config.ByteSize();
-
     // write the config to pipe, first write size of the config
-    vector<uint8_t> size_buffer(sizeof(bufferSize));
-    std::memcpy(size_buffer.data(), &bufferSize, sizeof(bufferSize));
     write(fds_config[1], &bufferSize, sizeof(bufferSize));
     // then write config itself
     vector<uint8_t> buffer(bufferSize);
@@ -79,11 +55,10 @@
     close(fds_config[1]);
 
     sp<ShellSubscriber> shellClient = new ShellSubscriber(uidMap, pullerManager);
-    sp<MyResultReceiver> resultReceiver = new MyResultReceiver();
 
     // mimic a binder thread that a shell subscriber runs on. it would block.
-    std::thread reader([&resultReceiver, &fds_config, &fds_data, &shellClient] {
-        shellClient->startNewSubscription(fds_config[0], fds_data[1], resultReceiver, -1);
+    std::thread reader([&shellClient, &fds_config, &fds_data] {
+        shellClient->startNewSubscription(fds_config[0], fds_data[1], /*timeoutSec=*/-1);
     });
     reader.detach();
 
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index db09ee9..6958218 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -14,6 +14,11 @@
 
 #include "statsd_test_util.h"
 
+#include <aidl/android/util/StatsEventParcel.h>
+
+using aidl::android::util::StatsEventParcel;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -581,7 +586,7 @@
 
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
                                               const StatsdConfig& config, const ConfigKey& key,
-                                              const sp<IPullAtomCallback>& puller,
+                                              const shared_ptr<IPullAtomCallback>& puller,
                                               const int32_t atomTag) {
     sp<UidMap> uidMap = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -590,11 +595,13 @@
                                                 puller);
     }
     sp<AlarmMonitor> anomalyAlarmMonitor =
-        new AlarmMonitor(1,  [](const sp<IStatsCompanionService>&, int64_t){},
-                [](const sp<IStatsCompanionService>&){});
+        new AlarmMonitor(1,
+                         [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                         [](const shared_ptr<IStatsCompanionService>&){});
     sp<AlarmMonitor> periodicAlarmMonitor =
-        new AlarmMonitor(1,  [](const sp<IStatsCompanionService>&, int64_t){},
-                [](const sp<IStatsCompanionService>&){});
+        new AlarmMonitor(1,
+                         [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                         [](const shared_ptr<IStatsCompanionService>&){});
     sp<StatsLogProcessor> processor =
             new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
                                   timeBaseNs, [](const ConfigKey&) { return true; },
@@ -948,10 +955,10 @@
     }
 }
 
-binder::Status FakeSubsystemSleepCallback::onPullAtom(
-        int atomTag, const sp<IPullAtomResultReceiver>& resultReceiver) {
+Status FakeSubsystemSleepCallback::onPullAtom(int atomTag,
+        const shared_ptr<IPullAtomResultReceiver>& resultReceiver) {
     // Convert stats_events into StatsEventParcels.
-    std::vector<android::util::StatsEventParcel> parcels;
+    std::vector<StatsEventParcel> parcels;
     for (int i = 1; i < 3; i++) {
         AStatsEvent* event = AStatsEvent_obtain();
         AStatsEvent_setAtomId(event, atomTag);
@@ -965,7 +972,7 @@
         size_t size;
         uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
 
-        android::util::StatsEventParcel p;
+        StatsEventParcel p;
         // vector.assign() creates a copy, but this is inevitable unless
         // stats_event.h/c uses a vector as opposed to a buffer.
         p.buffer.assign(buffer, buffer + size);
@@ -973,7 +980,7 @@
         AStatsEvent_write(event);
     }
     resultReceiver->pullFinished(atomTag, /*success=*/true, parcels);
-    return binder::Status::ok();
+    return Status::ok();
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 576a491..c8326ee 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -14,9 +14,9 @@
 
 #pragma once
 
-#include <android/os/BnPullAtomCallback.h>
-#include <android/os/IPullAtomCallback.h>
-#include <android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomResultReceiver.h>
 #include <gtest/gtest.h>
 
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
@@ -31,8 +31,12 @@
 namespace os {
 namespace statsd {
 
+using ::aidl::android::os::BnPullAtomCallback;
+using ::aidl::android::os::IPullAtomCallback;
+using ::aidl::android::os::IPullAtomResultReceiver;
 using android::util::ProtoReader;
 using google::protobuf::RepeatedPtrField;
+using Status = ::ndk::ScopedAStatus;
 
 const int SCREEN_STATE_ATOM_ID = android::util::SCREEN_STATE_CHANGED;
 const int UID_PROCESS_STATE_ATOM_ID = android::util::UID_PROCESS_STATE_CHANGED;
@@ -230,7 +234,7 @@
 // Create a statsd log event processor upon the start time in seconds, config and key.
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
                                               const StatsdConfig& config, const ConfigKey& key,
-                                              const sp<IPullAtomCallback>& puller = nullptr,
+                                              const shared_ptr<IPullAtomCallback>& puller = nullptr,
                                               const int32_t atomTag = 0 /*for puller only*/);
 
 // Util function to sort the log events by timestamp.
@@ -285,8 +289,8 @@
 
 class FakeSubsystemSleepCallback : public BnPullAtomCallback {
 public:
-    binder::Status onPullAtom(int atomTag,
-                              const sp<IPullAtomResultReceiver>& resultReceiver) override;
+    Status onPullAtom(int atomTag,
+                      const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override;
 };
 
 template <typename T>
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index ace13513..25729ab 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -18,6 +18,7 @@
 
 
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
@@ -25,6 +26,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
@@ -32,6 +34,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
@@ -83,9 +86,11 @@
     @IntDef(prefix = { "GESTURE_" }, value = {
             GESTURE_2_FINGER_SINGLE_TAP,
             GESTURE_2_FINGER_DOUBLE_TAP,
+            GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_TRIPLE_TAP,
             GESTURE_3_FINGER_SINGLE_TAP,
             GESTURE_3_FINGER_DOUBLE_TAP,
+            GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_TRIPLE_TAP,
             GESTURE_DOUBLE_TAP,
             GESTURE_DOUBLE_TAP_AND_HOLD,
@@ -114,6 +119,7 @@
             GESTURE_3_FINGER_SWIPE_RIGHT,
             GESTURE_3_FINGER_SWIPE_UP,
             GESTURE_4_FINGER_DOUBLE_TAP,
+            GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_4_FINGER_SINGLE_TAP,
             GESTURE_4_FINGER_SWIPE_DOWN,
             GESTURE_4_FINGER_SWIPE_LEFT,
@@ -175,12 +181,18 @@
         switch (eventType) {
             case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
             case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
+            case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
             case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
             case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
+            case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
             case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
             case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+            case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
             case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
             case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b65f68e..b7a35f7 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -29,6 +29,7 @@
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
@@ -39,6 +40,7 @@
 import android.os.Message;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -411,6 +413,15 @@
     /** The user has performed a four-finger triple tap gesture on the touch screen. */
     public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
 
+    /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40;
+
+    /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+
+    /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
+
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
@@ -591,8 +602,12 @@
             "screenshot_hardwareBuffer";
 
     /** @hide */
-    public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID =
-            "screenshot_colorSpaceId";
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
+            "screenshot_colorSpace";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
+            "screenshot_timestamp";
 
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
@@ -1911,6 +1926,8 @@
      *                  default display.
      * @param executor Executor on which to run the callback.
      * @param callback The callback invoked when the taking screenshot is done.
+     *                 The {@link AccessibilityService.ScreenshotResult} will be null for an
+     *                 invalid display.
      *
      * @return {@code true} if the taking screenshot accepted, {@code false} if not.
      */
@@ -1932,14 +1949,11 @@
                 }
                 final HardwareBuffer hardwareBuffer =
                         result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
-                final int colorSpaceId =
-                        result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID);
-                ColorSpace colorSpace = null;
-                if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
-                    colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
-                }
+                final ParcelableColorSpace colorSpace =
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE);
                 ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
-                        colorSpace, System.currentTimeMillis());
+                        colorSpace.getColorSpace(),
+                        result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
                 sendScreenshotResult(executor, callback, screenshot);
             }));
         } catch (RemoteException re) {
@@ -2352,41 +2366,38 @@
     }
 
     /**
-     * Class including hardwareBuffer, colorSpace, and timestamp to be the result for
+     * Can be used to construct a bitmap of the screenshot or any other operations for
      * {@link AccessibilityService#takeScreenshot} API.
-     * <p>
-     * <strong>Note:</strong> colorSpace would be null if the name of this colorSpace isn't at
-     * {@link ColorSpace.Named}.
-     * </p>
      */
     public static final class ScreenshotResult {
         private final @NonNull HardwareBuffer mHardwareBuffer;
-        private final @Nullable ColorSpace mColorSpace;
+        private final @NonNull ColorSpace mColorSpace;
         private final long mTimestamp;
 
         private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
-                @Nullable ColorSpace colorSpace, long timestamp) {
+                @NonNull ColorSpace colorSpace, long timestamp) {
             Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
+            Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
             mHardwareBuffer = hardwareBuffer;
             mColorSpace = colorSpace;
             mTimestamp = timestamp;
         }
 
         /**
-         * Gets the colorSpace identifying a specific organization of colors of the screenshot.
+         * Gets the {@link ColorSpace} identifying a specific organization of colors of the
+         * screenshot.
          *
-         * @return the colorSpace or {@code null} if the name of colorSpace isn't at
-         * {@link ColorSpace.Named}
+         * @return the color space
          */
-        @Nullable
+        @NonNull
         public ColorSpace getColorSpace() {
             return mColorSpace;
         }
 
         /**
-         * Gets the hardwareBuffer representing a memory buffer of the screenshot.
+         * Gets the {@link HardwareBuffer} representing a memory buffer of the screenshot.
          *
-         * @return the hardwareBuffer
+         * @return the hardware buffer
          */
         @NonNull
         public HardwareBuffer getHardwareBuffer() {
@@ -2396,7 +2407,8 @@
         /**
          * Gets the timestamp of taking the screenshot.
          *
-         * @return the timestamp from {@link System#currentTimeMillis()}
+         * @return milliseconds of non-sleep uptime before screenshot since boot and it's from
+         * {@link SystemClock#uptimeMillis()}
          */
         public long getTimestamp() {
             return mTimestamp;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 82c7635..c1e2195 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -551,11 +551,6 @@
     private int mHtmlDescriptionRes;
 
     /**
-     * Non localized html description of the accessibility service.
-     */
-    private String mNonLocalizedHtmlDescription;
-
-    /**
      * Creates a new instance.
      */
     public AccessibilityServiceInfo() {
@@ -683,10 +678,6 @@
                     com.android.internal.R.styleable.AccessibilityService_htmlDescription);
             if (peekedValue != null) {
                 mHtmlDescriptionRes = peekedValue.resourceId;
-                final CharSequence nonLocalizedHtmlDescription = peekedValue.coerceToString();
-                if (nonLocalizedHtmlDescription != null) {
-                    mNonLocalizedHtmlDescription = nonLocalizedHtmlDescription.toString().trim();
-                }
             }
             asAttributes.recycle();
         } catch (NameNotFoundException e) {
@@ -919,7 +910,7 @@
     @Nullable
     public String loadHtmlDescription(@NonNull PackageManager packageManager) {
         if (mHtmlDescriptionRes == 0) {
-            return mNonLocalizedHtmlDescription;
+            return null;
         }
 
         final ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
@@ -1017,7 +1008,6 @@
         parcel.writeInt(mAnimatedImageRes);
         parcel.writeInt(mHtmlDescriptionRes);
         parcel.writeString(mNonLocalizedDescription);
-        parcel.writeString(mNonLocalizedHtmlDescription);
     }
 
     private void initFromParcel(Parcel parcel) {
@@ -1039,7 +1029,6 @@
         mAnimatedImageRes = parcel.readInt();
         mHtmlDescriptionRes = parcel.readInt();
         mNonLocalizedDescription = parcel.readString();
-        mNonLocalizedHtmlDescription = parcel.readString();
     }
 
     @Override
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2319dd2..5df67bc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -126,6 +126,7 @@
 import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.autofill.AutofillPopupWindow;
 import android.view.autofill.IAutofillWindowPresenter;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
 import android.widget.AdapterView;
@@ -1056,7 +1057,10 @@
         } catch (RemoteException re) {
             re.rethrowFromSystemServer();
         }
-        // TODO(b/147750355): Pass locusId and bundle to the Content Capture.
+        // If locusId is not null pass it to the Content Capture.
+        if (locusId != null) {
+            setLocusContextToContentCapture(locusId, bundle);
+        }
     }
 
     /** Return the application that owns this activity. */
@@ -1209,6 +1213,19 @@
         }
     }
 
+    private void setLocusContextToContentCapture(LocusId locusId, @Nullable Bundle bundle) {
+        final ContentCaptureManager cm = getContentCaptureManager();
+        if (cm == null) return;
+
+        ContentCaptureContext.Builder contentCaptureContextBuilder =
+                new ContentCaptureContext.Builder(locusId);
+        if (bundle != null) {
+            contentCaptureContextBuilder.setExtras(bundle);
+        }
+        cm.getMainContentCaptureSession().setContentCaptureContext(
+                contentCaptureContextBuilder.build());
+    }
+
     @Override
     protected void attachBaseContext(Context newBase) {
         super.attachBaseContext(newBase);
@@ -1890,6 +1907,7 @@
             if (!mAutoFillIgnoreFirstResumePause) {
                 View focus = getCurrentFocus();
                 if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
+                    // TODO(b/148815880): Bring up keyboard if resumed from inline authentication.
                     // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest#
                     // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial
                     // window visibility after recreation is INVISIBLE in onResume() and next frame
@@ -8450,7 +8468,7 @@
     /** @hide */
     @Override
     public final void autofillClientAuthenticate(int authenticationId, IntentSender intent,
-            Intent fillInIntent) {
+            Intent fillInIntent, boolean authenticateInline) {
         try {
             startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
                     authenticationId, fillInIntent, 0, 0, null);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 367c2f2..f6bbc68 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,6 +16,10 @@
 
 package android.app;
 
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__DEFAULT;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -48,6 +52,8 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -63,6 +69,7 @@
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.MessageSamplingConfig;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.ZygoteInit;
 import com.android.internal.util.ArrayUtils;
@@ -141,6 +148,13 @@
     @UnsupportedAppUsage
     final IAppOpsService mService;
 
+    /**
+     * Service for the application context, to be used by static methods via
+     * {@link #getService()}
+     */
+    @GuardedBy("sLock")
+    static IAppOpsService sService;
+
     @GuardedBy("mModeWatchers")
     private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers =
             new ArrayMap<>();
@@ -159,6 +173,50 @@
     @GuardedBy("sLock")
     private static @Nullable AppOpsCollector sNotedAppOpsCollector;
 
+    /**
+     * Additional collector that collect accesses and forwards a few of them them via
+     * {@link IAppOpsService#reportRuntimeAppOpAccessMessageAndGetConfig}.
+     */
+    private static AppOpsCollector sMessageCollector =
+            new AppOpsCollector() {
+                @Override
+                public void onNoted(@NonNull SyncNotedAppOp op) {
+                    reportStackTraceIfNeeded(op);
+                }
+
+                @Override
+                public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp) {
+                    // collected directly in AppOpsService
+                }
+
+                @Override
+                public void onSelfNoted(@NonNull SyncNotedAppOp op) {
+                    reportStackTraceIfNeeded(op);
+                }
+
+                private void reportStackTraceIfNeeded(@NonNull SyncNotedAppOp op) {
+                    if (sConfig.getSampledOpCode() == OP_NONE
+                            && sConfig.getExpirationTimeSinceBootMillis()
+                            >= SystemClock.elapsedRealtime()) {
+                        return;
+                    }
+
+                    MessageSamplingConfig config = sConfig;
+                    if (leftCircularDistance(strOpToOp(op.getOp()), config.getSampledOpCode(),
+                            _NUM_OP) <= config.getAcceptableLeftDistance()
+                            || config.getExpirationTimeSinceBootMillis()
+                            < SystemClock.elapsedRealtime()) {
+                        String stackTrace = getFormattedStackTrace();
+                        try {
+                            sConfig = getService().reportRuntimeAppOpAccessMessageAndGetConfig(
+                                    ActivityThread.currentOpPackageName(), op, stackTrace);
+                        } catch (RemoteException e) {
+                            e.rethrowFromSystemServer();
+                        }
+                    }
+                }
+            };
+
     static IBinder sClientId;
 
     /**
@@ -550,7 +608,6 @@
     })
     public @interface OpFlags {}
 
-
     /** @hide */
     public static final String getFlagName(@OpFlags int flag) {
         switch (flag) {
@@ -569,6 +626,18 @@
         }
     }
 
+    /**
+     * Strategies used for message sampling
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RUNTIME_APP_OPS_ACCESS__SAMPLING_STRATEGY__"}, value = {
+            RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__DEFAULT,
+            RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM,
+            RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED
+    })
+    public @interface SamplingStrategy {}
+
     private static final int UID_STATE_OFFSET = 31;
     private static final int FLAGS_MASK = 0xFFFFFFFF;
 
@@ -2225,6 +2294,10 @@
         }
     }
 
+    /** Config used to control app ops access messages sampling */
+    private static MessageSamplingConfig sConfig =
+            new MessageSamplingConfig(OP_NONE, 0, 0);
+
     /** @hide */
     public static final String KEY_HISTORICAL_OPS = "historical_ops";
 
@@ -7268,6 +7341,17 @@
         }
     }
 
+    /** @hide */
+    private static IAppOpsService getService() {
+        synchronized (sLock) {
+            if (sService == null) {
+                sService = IAppOpsService.Stub.asInterface(
+                        ServiceManager.getService(Context.APP_OPS_SERVICE));
+            }
+            return sService;
+        }
+    }
+
     /**
      * @deprecated use {@link #startOp(String, int, String, String, String)} instead
      */
@@ -7614,6 +7698,7 @@
                 sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(op, featureId));
             }
         }
+        sMessageCollector.onSelfNoted(new SyncNotedAppOp(op, featureId));
     }
 
     /**
@@ -7764,6 +7849,10 @@
                         }
                     }
                 }
+                for (int code = notedAppOps.nextSetBit(0); code != -1;
+                        code = notedAppOps.nextSetBit(code + 1)) {
+                    sMessageCollector.onNoted(new SyncNotedAppOp(code, featureId));
+                }
             }
         }
     }
@@ -7824,9 +7913,7 @@
      * @hide
      */
     public static boolean isCollectingNotedAppOps() {
-        synchronized (sLock) {
-            return sNotedAppOpsCollector != null;
-        }
+        return sNotedAppOpsCollector != null;
     }
 
     /**
@@ -7960,10 +8047,13 @@
 
         StringBuilder sb = new StringBuilder();
         for (int i = firstInteresting; i <= lastInteresting; i++) {
-            sb.append(trace[i]);
-            if (i != lastInteresting) {
+            if (i != firstInteresting) {
                 sb.append('\n');
             }
+            if (sb.length() + trace[i].toString().length() > 600) {
+                break;
+            }
+            sb.append(trace[i]);
         }
 
         return sb.toString();
@@ -8091,6 +8181,22 @@
     }
 
     /**
+     * Pulls current AppOps access report and picks package and op to watch for next access report
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
+    public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
+        try {
+            return mService.collectRuntimeAppOpAccessMessage();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all supported operation names.
      * @hide
      */
@@ -8299,4 +8405,12 @@
 
         return AppOpsManager.MODE_DEFAULT;
     }
+
+    /**
+     * Calculate left circular distance for two numbers modulo size.
+     * @hide
+     */
+    public static int leftCircularDistance(int from, int to, int size) {
+        return (to + size - from) % size;
+    }
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 180507c..3c475c1 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -315,11 +315,6 @@
     void positionTaskInStack(int taskId, int stackId, int position);
     void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
             in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
-    /**
-     * Dismisses split-screen multi-window mode.
-     * {@param toTop} If true the current primary split-screen stack will be placed or left on top.
-     */
-    void dismissSplitScreenMode(boolean toTop);
 
     /**
      * Dismisses PiP
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9aa6b87..526c0b3 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -162,6 +162,7 @@
     void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment);
     void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments);
     void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
+    void unsnoozeNotificationFromSystemListener(in INotificationListener token, String key);
 
     ComponentName getEffectsSuppressor();
     boolean matchesCallFilter(in Bundle extras);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 06288c0..37bdda0 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -204,4 +204,12 @@
      * @param frozen if true, Recents Tasks list is currently frozen, false otherwise
      */
     void onRecentTaskListFrozenChanged(boolean frozen);
+
+    /**
+     * Called when a task gets or loses focus.
+     *
+     * @param taskId id of the task.
+     * @param {@code true} if the task got focus, {@code false} if it lost it.
+     */
+    void onTaskFocusChanged(int taskId, boolean focused);
 }
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index f26e628..d16120d 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -21,6 +21,7 @@
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -51,6 +52,7 @@
         private String mTitle;
         private String mText;
         private Icon mIcon;
+        private String mConversationId;
 
         private HistoricalNotification() {}
 
@@ -94,6 +96,10 @@
             return mPackage + "|" + mUid + "|" + mPostedTimeMs;
         }
 
+        public String getConversationId() {
+            return mConversationId;
+        }
+
         @Override
         public String toString() {
             return "HistoricalNotification{" +
@@ -104,6 +110,7 @@
                     ", mTitle='" + mTitle + '\'' +
                     ", mText='" + mText + '\'' +
                     ", mIcon=" + mIcon +
+                    ", mConversationId=" + mConversationId +
                     '}';
         }
 
@@ -123,6 +130,7 @@
                     Objects.equals(getChannelId(), that.getChannelId()) &&
                     Objects.equals(getTitle(), that.getTitle()) &&
                     Objects.equals(getText(), that.getText()) &&
+                    Objects.equals(getConversationId(), that.getConversationId()) &&
                     iconsAreSame;
         }
 
@@ -130,7 +138,7 @@
         public int hashCode() {
             return Objects.hash(getPackage(), getChannelName(), getChannelId(), getUid(),
                     getUserId(),
-                    getPostedTimeMs(), getTitle(), getText(), getIcon());
+                    getPostedTimeMs(), getTitle(), getText(), getIcon(), getConversationId());
         }
 
         public static final class Builder {
@@ -143,6 +151,7 @@
             private String mTitle;
             private String mText;
             private Icon mIcon;
+            private String mConversationId;
 
             public Builder() {}
 
@@ -191,6 +200,11 @@
                 return this;
             }
 
+            public Builder setConversationId(String conversationId) {
+                mConversationId = conversationId;
+                return this;
+            }
+
             public HistoricalNotification build() {
                 HistoricalNotification n = new HistoricalNotification();
                 n.mPackage = mPackage;
@@ -202,6 +216,7 @@
                 n.mTitle = mTitle;
                 n.mText = mText;
                 n.mIcon = mIcon;
+                n.mConversationId = mConversationId;
                 return n;
             }
         }
@@ -299,6 +314,9 @@
             mStringsToWrite.add(notification.getPackage());
             mStringsToWrite.add(notification.getChannelName());
             mStringsToWrite.add(notification.getChannelId());
+            if (!TextUtils.isEmpty(notification.getConversationId())) {
+                mStringsToWrite.add(notification.getConversationId());
+            }
         }
     }
 
@@ -423,9 +441,17 @@
             channelIdIndex = -1;
         }
 
+        final int conversationIdIndex;
+        if (!TextUtils.isEmpty(notification.getConversationId())) {
+            conversationIdIndex = findStringIndex(notification.getConversationId());
+        } else {
+            conversationIdIndex = -1;
+        }
+
         p.writeInt(packageIndex);
         p.writeInt(channelNameIndex);
         p.writeInt(channelIdIndex);
+        p.writeInt(conversationIdIndex);
         p.writeInt(notification.getUid());
         p.writeInt(notification.getUserId());
         p.writeLong(notification.getPostedTimeMs());
@@ -461,6 +487,13 @@
             notificationOut.setChannelId(null);
         }
 
+        final int conversationIdIndex = p.readInt();
+        if (conversationIdIndex >= 0) {
+            notificationOut.setConversationId(mStringPool[conversationIdIndex]);
+        } else {
+            notificationOut.setConversationId(null);
+        }
+
         notificationOut.setUid(p.readInt());
         notificationOut.setUserId(p.readInt());
         notificationOut.setPostedTimeMs(p.readLong());
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/android/app/RuntimeAppOpAccessMessage.aidl
similarity index 74%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to core/java/android/app/RuntimeAppOpAccessMessage.aidl
index fcacd52..68e8819 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/core/java/android/app/RuntimeAppOpAccessMessage.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.view.animation;
+package android.app;
 
-public interface NativeInterpolatorFactory {
-    long createNativeInterpolator();
-}
+parcelable RuntimeAppOpAccessMessage;
diff --git a/core/java/android/app/RuntimeAppOpAccessMessage.java b/core/java/android/app/RuntimeAppOpAccessMessage.java
new file mode 100644
index 0000000..a81b8e7
--- /dev/null
+++ b/core/java/android/app/RuntimeAppOpAccessMessage.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * Message for noted runtime permission access.
+ * @hide
+ */
+@Immutable
+@TestApi
+@SystemApi
+/*@DataClass(genConstructor = false)
+@DataClass.Suppress("getOpCode")*/
+public final class RuntimeAppOpAccessMessage implements Parcelable {
+    /** Uid of package for which runtime app op access message was collected */
+    private final @IntRange(from = 0L) int mUid;
+    /** Op code of operation access which was collected */
+    private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+    /** Name of package for which runtime app op access message was collected */
+    private final @NonNull String mPackageName;
+    /** Feature of package for which runtime app op access message was collected */
+    private final @Nullable String mFeatureId;
+    /** Message collected (including stacktrace for synchronous ops) */
+    private final @NonNull String mMessage;
+    /** Sampling strategy used to collect this message. */
+    private final @AppOpsManager.SamplingStrategy int mSamplingStrategy;
+
+    public @NonNull String getOp() {
+        return AppOpsManager.opToPublicName(mOpCode);
+    }
+
+    /**
+     * Creates a new RuntimeAppOpAccessMessage.
+     *
+     * @param uid
+     *   Uid of package for which runtime app op access message was collected
+     * @param opCode
+     *   Op code of operation access which was collected
+     * @param packageName
+     *   Name of package for which runtime app op access message was collected
+     * @param featureId
+     *   Feature of package for which runtime app op access message was collected
+     * @param message
+     *   Message collected (including stacktrace for synchronous ops)
+     * @param samplingStrategy
+     *   Sampling strategy used to collect this message.
+     */
+    @DataClass.Generated.Member
+    public RuntimeAppOpAccessMessage(
+            @IntRange(from = 0L) int uid,
+            @IntRange(from = 0L) int opCode,
+            @NonNull String packageName,
+            @Nullable String featureId,
+            @NonNull String message,
+            @AppOpsManager.SamplingStrategy int samplingStrategy) {
+        this.mUid = uid;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mUid,
+                "from", 0L);
+        this.mOpCode = opCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mOpCode,
+                "from", 0L,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mFeatureId = featureId;
+        this.mMessage = message;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMessage);
+        this.mSamplingStrategy = samplingStrategy;
+        com.android.internal.util.AnnotationValidations.validate(
+                AppOpsManager.SamplingStrategy.class, null, mSamplingStrategy);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/RuntimeAppOpAccessMessage.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Uid of package for which runtime app op access message was collected
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0L) int getUid() {
+        return mUid;
+    }
+
+    /**
+     * Name of package for which runtime app op access message was collected
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Feature of package for which runtime app op access message was collected
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getFeatureId() {
+        return mFeatureId;
+    }
+
+    /**
+     * Message collected (including stacktrace for synchronous ops)
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Sampling strategy used to collect this message.
+     */
+    @DataClass.Generated.Member
+    public @AppOpsManager.SamplingStrategy int getSamplingStrategy() {
+        return mSamplingStrategy;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mFeatureId != null) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeInt(mUid);
+        dest.writeInt(mOpCode);
+        dest.writeString(mPackageName);
+        if (mFeatureId != null) dest.writeString(mFeatureId);
+        dest.writeString(mMessage);
+        dest.writeInt(mSamplingStrategy);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ RuntimeAppOpAccessMessage(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int uid = in.readInt();
+        int opCode = in.readInt();
+        String packageName = in.readString();
+        String featureId = (flg & 0x8) == 0 ? null : in.readString();
+        String message = in.readString();
+        int samplingStrategy = in.readInt();
+
+        this.mUid = uid;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mUid,
+                "from", 0L);
+        this.mOpCode = opCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mOpCode,
+                "from", 0L,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mFeatureId = featureId;
+        this.mMessage = message;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMessage);
+        this.mSamplingStrategy = samplingStrategy;
+        com.android.internal.util.AnnotationValidations.validate(
+                AppOpsManager.SamplingStrategy.class, null, mSamplingStrategy);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<RuntimeAppOpAccessMessage> CREATOR
+            = new Parcelable.Creator<RuntimeAppOpAccessMessage>() {
+        @Override
+        public RuntimeAppOpAccessMessage[] newArray(int size) {
+            return new RuntimeAppOpAccessMessage[size];
+        }
+
+        @Override
+        public RuntimeAppOpAccessMessage createFromParcel(@NonNull Parcel in) {
+            return new RuntimeAppOpAccessMessage(in);
+        }
+    };
+
+    /*@DataClass.Generated(
+            time = 1581517099127L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/android/app/RuntimeAppOpAccessMessage.java",
+            inputSignatures = "private final @android.annotation.IntRange(from=0L) int mUid\nprivate final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mOpCode\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.app.AppOpsManager.SamplingStrategy int mSamplingStrategy\npublic @android.annotation.NonNull java.lang.String getOp()\nclass RuntimeAppOpAccessMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false)")*/
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/android/app/SyncNotedAppOp.aidl
similarity index 74%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to core/java/android/app/SyncNotedAppOp.aidl
index fcacd52..ab062d2 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/core/java/android/app/SyncNotedAppOp.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.view.animation;
+package android.app;
 
-public interface NativeInterpolatorFactory {
-    long createNativeInterpolator();
-}
+parcelable SyncNotedAppOp;
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index 065d5de..aa11b95 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -19,8 +19,10 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
 
 /**
  * Description of an app-op that was noted for the current process.
@@ -32,48 +34,154 @@
  * itself}.
  */
 @Immutable
-public final class SyncNotedAppOp {
-    private final int mOpCode;
+/*@DataClass(
+        genEqualsHashCode = true,
+        genConstructor = false
+)
+@DataClass.Suppress("getOpCode")*/
+public final class SyncNotedAppOp implements Parcelable {
+
+    /** op code of synchronous appop noted */
+    private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+    /** featureId of synchronous appop noted */
     private final @Nullable String mFeatureId;
 
     /**
+     * Creates a new SyncNotedAppOp.
+     *
+     * @param opCode
+     *   op code of synchronous appop noted
+     * @param featureId
+     *   featureId of synchronous appop noted
+     */
+    public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String featureId) {
+        this.mOpCode = opCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mOpCode,
+                "from", 0,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mFeatureId = featureId;
+    }
+
+    /**
      * @return The op that was noted.
      */
     public @NonNull String getOp() {
         return AppOpsManager.opToPublicName(mOpCode);
     }
 
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/SyncNotedAppOp.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
     /**
-     * @return The {@link android.content.Context#createFeatureContext Feature} in the app
+     * featureId of synchronous appop noted
      */
+    @DataClass.Generated.Member
     public @Nullable String getFeatureId() {
         return mFeatureId;
     }
 
-    /**
-     * Create a new sync op description
-     *
-     * @param opCode The op that was noted
-     *
-     * @hide
-     */
-    public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
-            @Nullable String featureId) {
-        mOpCode = opCode;
-        mFeatureId = featureId;
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(SyncNotedAppOp other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        SyncNotedAppOp that = (SyncNotedAppOp) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mOpCode == that.mOpCode
+                && java.util.Objects.equals(mFeatureId, that.mFeatureId);
     }
 
     @Override
-    public boolean equals(Object other) {
-        if (!(other instanceof SyncNotedAppOp)) {
-            return false;
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mOpCode;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureId);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mFeatureId != null) flg |= 0x2;
+        dest.writeByte(flg);
+        dest.writeInt(mOpCode);
+        if (mFeatureId != null) dest.writeString(mFeatureId);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ SyncNotedAppOp(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int opCode = in.readInt();
+        String featureId = (flg & 0x2) == 0 ? null : in.readString();
+
+        this.mOpCode = opCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mOpCode,
+                "from", 0,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mFeatureId = featureId;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<SyncNotedAppOp> CREATOR
+            = new Parcelable.Creator<SyncNotedAppOp>() {
+        @Override
+        public SyncNotedAppOp[] newArray(int size) {
+            return new SyncNotedAppOp[size];
         }
 
-        return mOpCode == ((SyncNotedAppOp) other).mOpCode;
-    }
+        @Override
+        public SyncNotedAppOp createFromParcel(@NonNull android.os.Parcel in) {
+            return new SyncNotedAppOp(in);
+        }
+    };
 
-    @Override
-    public int hashCode() {
-        return mOpCode;
-    }
+    /*@DataClass.Generated(
+            time = 1579188889960L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\npublic @android.annotation.NonNull java.lang.String getOp()\npublic @android.annotation.SystemApi int getOpCode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genConstructor=false)")*/
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8b07418..9b73060 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -132,7 +132,7 @@
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
 import android.net.wifi.WifiFrameworkInitializer;
-import android.net.wifi.wificond.WifiCondManager;
+import android.net.wifi.wificond.WifiNl80211Manager;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -761,11 +761,11 @@
                 return new EthernetManager(ctx.getOuterContext(), service);
             }});
 
-        registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class,
-                new CachedServiceFetcher<WifiCondManager>() {
+        registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
+                new CachedServiceFetcher<WifiNl80211Manager>() {
                     @Override
-                    public WifiCondManager createService(ContextImpl ctx) {
-                        return new WifiCondManager(ctx.getOuterContext());
+                    public WifiNl80211Manager createService(ContextImpl ctx) {
+                        return new WifiNl80211Manager(ctx.getOuterContext());
                     }
                 });
 
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 343b386..da0aadb 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -199,4 +199,8 @@
     @Override
     public void onRecentTaskListFrozenChanged(boolean frozen) {
     }
+
+    @Override
+    public void onTaskFocusChanged(int taskId, boolean focused) {
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 48eab92..40b81dd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4718,15 +4718,40 @@
     public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
 
     /**
-     * Keyguard features that when set on a managed profile that doesn't have its own challenge will
-     * affect the profile's parent user. These can also be set on the managed profile's parent
+     * Keyguard features that when set on a non-organization-owned managed profile that doesn't
+     * have its own challenge will affect the profile's parent user. These can also be set on the
+     * managed profile's parent {@link DevicePolicyManager} instance to explicitly control the
+     * parent user.
+     *
+     * <p>
+     * Organization-owned managed profile supports disabling additional keyguard features on the
+     * parent user as defined in {@link #ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY}.
+     *
+     * @hide
+     */
+    public static final int NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
+            DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
+            | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+
+    /**
+     * Keyguard features that when set by the profile owner of an organization-owned managed
+     * profile will affect the profile's parent user if set on the managed profile's parent
      * {@link DevicePolicyManager} instance.
      *
      * @hide
      */
+    public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
+            KEYGUARD_DISABLE_SECURE_CAMERA;
+
+    /**
+     * Keyguard features that when set on a normal or organization-owned managed profile, have
+     * the potential to affect the profile's parent user.
+     *
+     * @hide
+     */
     public static final int PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
-            DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
-            | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+            DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+                    | DevicePolicyManager.ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY;
 
     /**
      * @deprecated This method does not actually modify the storage encryption of the device.
@@ -6115,11 +6140,20 @@
      * <li>{@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} which affects notifications generated
      * by applications in the managed profile.
      * </ul>
+     * <p>
+     * From version {@link android.os.Build.VERSION_CODES#R} the profile owner of an
+     * organization-owned managed profile can set:
+     * <ul>
+     * <li>{@link #KEYGUARD_DISABLE_SECURE_CAMERA} which affects the parent user when called on the
+     * parent profile.
+     * </ul>
      * {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, {@link #KEYGUARD_DISABLE_FINGERPRINT},
-     * {@link #KEYGUARD_DISABLE_FACE} and {@link #KEYGUARD_DISABLE_IRIS} can also be
-     * set on the {@link DevicePolicyManager} instance returned by
-     * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
-     * profile.
+     * {@link #KEYGUARD_DISABLE_FACE}, {@link #KEYGUARD_DISABLE_IRIS} and
+     * {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can also be set on the {@link DevicePolicyManager}
+     * instance returned by {@link #getParentProfileInstance(ComponentName)} in order to set
+     * restrictions on the parent profile. {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can only be set
+     * on the parent profile instance if the calling device admin is the profile owner of an
+     * organization-owned managed profile.
      * <p>
      * Requests to disable other features on a managed profile will be ignored.
      * <p>
@@ -8789,11 +8823,11 @@
      * @throws SecurityException if caller is not a device owner or a profile owner of an
      *                           organization-owned managed profile.
      */
-    public void setLockdownAdminConfiguredNetworks(@NonNull ComponentName admin, boolean lockdown) {
-        throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+    public void setConfiguredNetworksLockdownState(@NonNull ComponentName admin, boolean lockdown) {
+        throwIfParentInstance("setConfiguredNetworksLockdownState");
         if (mService != null) {
             try {
-                mService.setLockdownAdminConfiguredNetworks(admin, lockdown);
+                mService.setConfiguredNetworksLockdownState(admin, lockdown);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8809,11 +8843,11 @@
      * @throws SecurityException if caller is not a device owner or a profile owner of an
      *                           organization-owned managed profile.
      */
-    public boolean isLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
-        throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+    public boolean hasLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
+        throwIfParentInstance("hasLockdownAdminConfiguredNetworks");
         if (mService != null) {
             try {
-                return mService.isLockdownAdminConfiguredNetworks(admin);
+                return mService.hasLockdownAdminConfiguredNetworks(admin);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -11546,13 +11580,16 @@
     }
 
     /**
-     * Sets the set of package names that are allowed to request user consent for cross-profile
-     * communication.
+     * Sets the set of admin-whitelisted package names that are allowed to request user consent for
+     * cross-profile communication.
      *
      * <p>Assumes that the caller is a profile owner and is the given {@code admin}.
      *
      * <p>Previous calls are overridden by each subsequent call to this method.
      *
+     * <p>Note that other apps may be able to request user consent for cross-profile communication
+     * if they have been explicitly whitelisted by the OEM.
+     *
      * <p>When previously-set cross-profile packages are missing from {@code packageNames}, the
      * app-op for {@code INTERACT_ACROSS_PROFILES} will be reset for those packages. This will not
      * occur for packages that are whitelisted by the OEM.
@@ -11574,15 +11611,18 @@
 
     /**
      * Returns the set of package names that the admin has previously set as allowed to request user
-     * consent for cross-profile communication, via {@link
-     * #setCrossProfilePackages(ComponentName, Set)}.
+     * consent for cross-profile communication, via {@link #setCrossProfilePackages(ComponentName,
+     * Set)}.
      *
      * <p>Assumes that the caller is a profile owner and is the given {@code admin}.
      *
+     * <p>Note that other apps not included in the returned set may be able to request user consent
+     * for cross-profile communication if they have been explicitly whitelisted by the OEM.
+     *
      * @param admin the {@link DeviceAdminReceiver} this request is associated with
      * @return the set of package names the admin has previously set as allowed to request user
-     * consent for cross-profile communication, via {@link
-     * #setCrossProfilePackages(ComponentName, Set)}
+     * consent for cross-profile communication, via {@link #setCrossProfilePackages(ComponentName,
+     * Set)}
      */
     public @NonNull Set<String> getCrossProfilePackages(@NonNull ComponentName admin) {
         throwIfParentInstance("getCrossProfilePackages");
@@ -11600,18 +11640,17 @@
      * Returns the combined set of the following:
      * <ul>
      * <li>The package names that the admin has previously set as allowed to request user consent
-     * for cross-profile communication, via {@link
-     * #setCrossProfilePackages(ComponentName, Set)}.</li>
+     * for cross-profile communication, via {@link #setCrossProfilePackages(ComponentName,
+     * Set)}.</li>
      * <li>The default package names set by the OEM that are allowed to request user consent for
-     * cross-profile communication without being explicitly enabled by the admin, via
-     * {@link com.android.internal.R.array#cross_profile_apps} and
-     * {@link com.android.internal.R.array#vendor_cross_profile_apps}.</li>
+     * cross-profile communication without being explicitly enabled by the admin, via {@link
+     * com.android.internal.R.array#cross_profile_apps} and {@link com.android.internal.R.array
+     * #vendor_cross_profile_apps}.</li>
      * </ul>
      *
      * @return the combined set of whitelisted package names set via
-     * {@link #setCrossProfilePackages(ComponentName, Set)},
-     * {@link com.android.internal.R.array#cross_profile_apps},
-     * and {@link com.android.internal.R.array#vendor_cross_profile_apps}.
+     * {@link #setCrossProfilePackages(ComponentName, Set)}, {@link com.android.internal.R.array
+     * #cross_profile_apps}, and {@link com.android.internal.R.array#vendor_cross_profile_apps}.
      *
      * @hide
      */
@@ -11634,9 +11673,9 @@
 
     /**
      * Returns the default package names set by the OEM that are allowed to request user consent for
-     * cross-profile communication without being explicitly enabled by the admin, via
-     * {@link com.android.internal.R.array#cross_profile_apps} and
-     * {@link com.android.internal.R.array#vendor_cross_profile_apps}.
+     * cross-profile communication without being explicitly enabled by the admin, via {@link
+     * com.android.internal.R.array#cross_profile_apps} and {@link com.android.internal.R.array
+     * #vendor_cross_profile_apps}.
      *
      * @hide
      */
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
index ed74779..954db04 100644
--- a/core/java/android/app/admin/FactoryResetProtectionPolicy.java
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -53,17 +53,17 @@
 
     private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
             "factory_reset_protection_account";
-    private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
-            "factory_reset_protection_disabled";
+    private static final String KEY_FACTORY_RESET_PROTECTION_ENABLED =
+            "factory_reset_protection_enabled";
     private static final String ATTR_VALUE = "value";
 
     private final List<String> mFactoryResetProtectionAccounts;
-    private final boolean mFactoryResetProtectionDisabled;
+    private final boolean mFactoryResetProtectionEnabled;
 
     private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
-            boolean factoryResetProtectionDisabled) {
+            boolean factoryResetProtectionEnabled) {
         mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
-        mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+        mFactoryResetProtectionEnabled = factoryResetProtectionEnabled;
     }
 
     /**
@@ -74,10 +74,10 @@
     }
 
     /**
-     * Return whether factory reset protection for the device is disabled or not.
+     * Return whether factory reset protection for the device is enabled or not.
      */
-    public boolean isFactoryResetProtectionDisabled() {
-        return mFactoryResetProtectionDisabled;
+    public boolean isFactoryResetProtectionEnabled() {
+        return mFactoryResetProtectionEnabled;
     }
 
     /**
@@ -85,12 +85,13 @@
      */
     public static class Builder {
         private List<String> mFactoryResetProtectionAccounts;
-        private boolean mFactoryResetProtectionDisabled;
+        private boolean mFactoryResetProtectionEnabled;
 
         /**
          * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
          */
         public Builder() {
+            mFactoryResetProtectionEnabled = true;
         };
 
         /**
@@ -113,18 +114,19 @@
         }
 
         /**
-         * Sets whether factory reset protection is disabled or not.
+         * Sets whether factory reset protection is enabled or not.
          * <p>
          * Once disabled, factory reset protection will not kick in all together when the device
          * goes through untrusted factory reset. This applies to both the consumer unlock flow and
-         * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+         * the admin account overrides via {@link #setFactoryResetProtectionAccounts}. By default,
+         * factory reset protection is enabled.
          *
-         * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+         * @param factoryResetProtectionEnabled Whether the policy is enabled or not.
          * @return the same Builder instance.
          */
         @NonNull
-        public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
-            mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+        public Builder setFactoryResetProtectionEnabled(boolean factoryResetProtectionEnabled) {
+            mFactoryResetProtectionEnabled = factoryResetProtectionEnabled;
             return this;
         }
 
@@ -136,7 +138,7 @@
         @NonNull
         public FactoryResetProtectionPolicy build() {
             return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
-                    mFactoryResetProtectionDisabled);
+                    mFactoryResetProtectionEnabled);
         }
     }
 
@@ -144,7 +146,7 @@
     public String toString() {
         return "FactoryResetProtectionPolicy{"
                 + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
-                + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+                + ", mFactoryResetProtectionEnabled=" + mFactoryResetProtectionEnabled
                 + '}';
     }
 
@@ -155,7 +157,7 @@
         for (String account: mFactoryResetProtectionAccounts) {
             dest.writeString(account);
         }
-        dest.writeBoolean(mFactoryResetProtectionDisabled);
+        dest.writeBoolean(mFactoryResetProtectionEnabled);
     }
 
     @Override
@@ -173,10 +175,10 @@
                     for (int i = 0; i < accountsCount; i++) {
                         factoryResetProtectionAccounts.add(in.readString());
                     }
-                    boolean factoryResetProtectionDisabled = in.readBoolean();
+                    boolean factoryResetProtectionEnabled = in.readBoolean();
 
                     return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
-                            factoryResetProtectionDisabled);
+                            factoryResetProtectionEnabled);
                 }
 
                 @Override
@@ -195,8 +197,8 @@
     @Nullable
     public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
         try {
-            boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
-                    parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+            boolean factoryResetProtectionEnabled = Boolean.parseBoolean(
+                    parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_ENABLED));
 
             List<String> factoryResetProtectionAccounts = new ArrayList<>();
             int outerDepth = parser.getDepth();
@@ -214,7 +216,7 @@
             }
 
             return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
-                    factoryResetProtectionDisabled);
+                    factoryResetProtectionEnabled);
         } catch (XmlPullParserException | IOException e) {
             Log.w(LOG_TAG, "Reading from xml failed", e);
         }
@@ -225,8 +227,8 @@
      * @hide
      */
     public void writeToXml(@NonNull XmlSerializer out) throws IOException {
-        out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
-                Boolean.toString(mFactoryResetProtectionDisabled));
+        out.attribute(null, KEY_FACTORY_RESET_PROTECTION_ENABLED,
+                Boolean.toString(mFactoryResetProtectionEnabled));
         for (String account : mFactoryResetProtectionAccounts) {
             out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
             out.attribute(null, ATTR_VALUE, account);
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8ef25bd..0aed39c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -266,8 +266,8 @@
     void setSystemSetting(in ComponentName who, in String setting, in String value);
     void setSecureSetting(in ComponentName who, in String setting, in String value);
 
-    void setLockdownAdminConfiguredNetworks(in ComponentName who, boolean lockdown);
-    boolean isLockdownAdminConfiguredNetworks(in ComponentName who);
+    void setConfiguredNetworksLockdownState(in ComponentName who, boolean lockdown);
+    boolean hasLockdownAdminConfiguredNetworks(in ComponentName who);
 
     void setLocationEnabled(in ComponentName who, boolean locationEnabled);
     void requestSetLocationProviderAllowed(in ComponentName who, in String provider, boolean providerAllowed);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index ea66fd47..db4f1de 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -90,6 +90,7 @@
      * The name of the dialer role.
      *
      * @see Intent#ACTION_DIAL
+     * @see android.telecom.InCallService
      */
     public static final String ROLE_DIALER = "android.app.role.DIALER";
 
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index ab71e73..0f999ad 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -41,6 +41,43 @@
     /** @hide */
     public static final String INSTANT_APP_CLASS_NAME = "android.instant_class";
 
+    /** @hide */
+    public static final String OBFUSCATED_NOTIFICATION_CHANNEL_ID = "unknown_channel_id";
+
+    /**
+     * Flag: indicates to not obfuscate or hide any usage event data when being queried.
+     * @hide
+     */
+    public static final int SHOW_ALL_EVENT_DATA = 0x00000000;
+
+    /**
+     * Flag: indicates to obfuscate package and class names for instant apps when querying usage
+     * events.
+     * @hide
+     */
+    public static final int OBFUSCATE_INSTANT_APPS = 0x00000001;
+
+    /**
+     * Flag: indicates to hide all {@link Event#SHORTCUT_INVOCATION} events when querying usage
+     * events.
+     * @hide
+     */
+    public static final int HIDE_SHORTCUT_EVENTS = 0x00000002;
+
+    /**
+     * Flag: indicates to obfuscate the notification channel id for all notification events,
+     * such as {@link Event#NOTIFICATION_SEEN} and {@link Event#NOTIFICATION_INTERRUPTION} events,
+     * when querying usage events.
+     * @hide
+     */
+    public static final int OBFUSCATE_NOTIFICATION_EVENTS = 0x00000004;
+
+    /**
+     * Flag: indicates to hide all {@link Event#LOCUS_ID_SET} events when querying usage events.
+     * @hide
+     */
+    public static final int HIDE_LOCUS_EVENTS = 0x00000008;
+
     /**
      * An event representing a state change for a component.
      */
@@ -627,6 +664,13 @@
             return ret;
         }
 
+        /** @hide */
+        public Event getObfuscatedNotificationEvent() {
+            final Event ret = new Event(this);
+            ret.mNotificationChannelId = OBFUSCATED_NOTIFICATION_CHANNEL_ID;
+            return ret;
+        }
+
         /**
          * Returns the locusId for this event if the event is of type {@link #LOCUS_ID_SET},
          * otherwise it returns null.
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 5668944..2c701b4 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -599,7 +599,8 @@
     /**
      * Returns whether the specified app is currently considered inactive. This will be true if the
      * app hasn't been used directly or indirectly for a period of time defined by the system. This
-     * could be of the order of several hours or days.
+     * could be of the order of several hours or days. Apps are not considered inactive when the
+     * device is charging.
      * @param packageName The package name of the app to query
      * @return whether the app is currently considered inactive
      */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 49f62f4..ae12de0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4064,16 +4064,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control
-     * daemon.
+     * {@link android.net.wifi.wificond.WifiNl80211Manager} for handling management of the
+     * Wi-Fi nl802.11 daemon (wificond).
      *
      * @see #getSystemService(String)
-     * @see android.net.wifi.WifiCondManager
+     * @see android.net.wifi.wificond.WifiNl80211Manager
      * @hide
      */
     @SystemApi
     @SuppressLint("ServiceName")
-    public static final String WIFI_COND_SERVICE = "wificond";
+    public static final String WIFI_NL80211_SERVICE = "wifinl80211";
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
@@ -5098,10 +5098,11 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
-     * AppSearchManager for indexing and querying app data managed
-     * by the system.
+     * {@link android.app.appsearch.AppSearchManager} for
+     * indexing and querying app data managed by the system.
      *
      * @see #getSystemService(String)
+     * @hide
      */
     public static final String APP_SEARCH_SERVICE = "app_search";
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0f88c90..2e591ca 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6767,7 +6767,7 @@
                     this.mClipData = new ClipData(o.mClipData);
                 }
             } else {
-                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
+                if (o.mExtras != null && !o.mExtras.isDefinitelyEmpty()) {
                     this.mExtras = Bundle.STRIPPED;
                 }
 
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
index 475f019..9d37299 100644
--- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -16,6 +16,10 @@
 
 package android.content.integrity;
 
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
 import java.util.Map;
 
 /**
@@ -25,7 +29,29 @@
  *
  * @hide
  */
-public class InstallerAllowedByManifestFormula extends IntegrityFormula {
+public class InstallerAllowedByManifestFormula extends IntegrityFormula implements Parcelable {
+
+    public static final String INSTALLER_CERTIFICATE_NOT_EVALUATED = "";
+
+    public InstallerAllowedByManifestFormula() {
+    }
+
+    private InstallerAllowedByManifestFormula(Parcel in) {
+    }
+
+    @NonNull
+    public static final Creator<InstallerAllowedByManifestFormula> CREATOR =
+            new Creator<InstallerAllowedByManifestFormula>() {
+                @Override
+                public InstallerAllowedByManifestFormula createFromParcel(Parcel in) {
+                    return new InstallerAllowedByManifestFormula(in);
+                }
+
+                @Override
+                public InstallerAllowedByManifestFormula[] newArray(int size) {
+                    return new InstallerAllowedByManifestFormula[size];
+                }
+            };
 
     @Override
     public int getTag() {
@@ -54,10 +80,30 @@
     private static boolean installerInAllowedInstallersFromManifest(
             AppInstallMetadata appInstallMetadata,
             Map<String, String> allowedInstallersAndCertificates) {
-        return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName())
-                && appInstallMetadata.getInstallerCertificates()
-                .contains(
-                        allowedInstallersAndCertificates
-                        .get(appInstallMetadata.getInstallerName()));
+        String installerPackage = appInstallMetadata.getInstallerName();
+
+        if (!allowedInstallersAndCertificates.containsKey(installerPackage)) {
+            return false;
+        }
+
+        // If certificate is not specified in the manifest, we do not check it.
+        if (!allowedInstallersAndCertificates.get(installerPackage)
+                .equals(INSTALLER_CERTIFICATE_NOT_EVALUATED)) {
+            return appInstallMetadata.getInstallerCertificates()
+                    .contains(
+                            allowedInstallersAndCertificates
+                                    .get(appInstallMetadata.getInstallerName()));
+        }
+
+        return true;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
     }
 }
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index ac4c907..c5e5c8a 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -214,6 +214,8 @@
                 return LongAtomicFormula.CREATOR.createFromParcel(in);
             case BOOLEAN_ATOMIC_FORMULA_TAG:
                 return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+            case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
+                return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
             default:
                 throw new IllegalArgumentException("Unknown formula tag " + tag);
         }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index d251ba9..9d1c677 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -702,6 +702,13 @@
      */
     public static final int PRIVATE_FLAG_ODM = 1 << 30;
 
+    /**
+     * Value for {@link #privateFlags}: If {@code true} this app allows heap tagging.
+     * {@link com.android.server.am.ProcessList#NATIVE_HEAP_POINTER_TAGGING}
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING = 1 << 31;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
             PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
@@ -733,6 +740,7 @@
             PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE,
             PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE,
             PRIVATE_FLAG_ODM,
+            PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlags {}
@@ -1878,6 +1886,15 @@
         return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0;
     }
 
+    /**
+     * If {@code true} this app allows heap pointer tagging.
+     *
+     * @hide
+     */
+    public boolean allowsNativeHeapPointerTagging() {
+        return (privateFlags & PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) != 0;
+    }
+
     private boolean isAllowedToUseHiddenApis() {
         if (isSignedWithPlatformKey()) {
             return true;
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 50841c3..eb1da67 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -47,8 +47,15 @@
 public class CrossProfileApps {
 
     /**
-     * Broadcast signalling that the receiving app's ability to interact across profiles has
-     * changed, as defined by the return value of {@link #canInteractAcrossProfiles()}.
+     * Broadcast signalling that the receiving app's permission to interact across profiles has
+     * changed. This includes the user, admin, or OEM changing their consent such that the
+     * permission for the app to interact across profiles has changed.
+     *
+     * <p>This broadcast is not sent when other circumstances result in a change to being able to
+     * interact across profiles in practice, such as the profile being turned off or removed, apps
+     * being uninstalled, etc. The methods {@link #canInteractAcrossProfiles()} and {@link
+     * #canRequestInteractAcrossProfiles()} can be used by apps prior to attempting to interact
+     * across profiles or attempting to request user consent to interact across profiles.
      *
      * <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true}
      * can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be
@@ -99,8 +106,11 @@
     /**
      * Starts the specified activity of the caller package in the specified profile.
      *
-     * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
-     * permission and both the caller and target user profiles must be in the same profile group.
+     * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES},
+     * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}, or {@code
+     * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. Both the caller and
+     * target user profiles must be in the same profile group. The target user must be a valid user
+     * returned from {@link #getTargetUserProfiles()}.
      *
      * @param intent The intent to launch. A component in the caller package must be specified.
      * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by
@@ -219,10 +229,11 @@
     }
 
     /**
-     * Returns whether the calling package can request to interact across profiles.
+     * Returns whether the calling package can request user consent to interact across profiles.
      *
-     * <p>The package's current ability to interact across profiles can be checked with
-     * {@link #canInteractAcrossProfiles()}.
+     * <p>If {@code true}, user consent can be obtained via {@link
+     * #createRequestInteractAcrossProfilesIntent()}. The package can then listen to {@link
+     * #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts.
      *
      * <p>Specifically, returns whether the following are all true:
      * <ul>
@@ -235,6 +246,10 @@
      * </li>
      * </ul>
      *
+     * <p>Note that user consent could already be granted if given a return value of {@code true}.
+     * The package's current ability to interact across profiles can be checked with {@link
+     * #canInteractAcrossProfiles()}.
+     *
      * @return true if the calling package can request to interact across profiles.
      */
     public boolean canRequestInteractAcrossProfiles() {
@@ -247,10 +262,7 @@
 
     /**
      * Returns whether the calling package can interact across profiles.
-     *
-     * <p>The package's current ability to request to interact across profiles can be checked with
-     * {@link #canRequestInteractAcrossProfiles()}.
-     *
+
      * <p>Specifically, returns whether the following are all true:
      * <ul>
      * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
@@ -262,6 +274,11 @@
      * </li>
      * </ul>
      *
+     * <p>If {@code false}, the package's current ability to request user consent to interact across
+     * profiles can be checked with {@link #canRequestInteractAcrossProfiles()}. If {@code true},
+     * user consent can be obtained via {@link #createRequestInteractAcrossProfilesIntent()}. The
+     * package can then listen to {@link #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts.
+     *
      * @return true if the calling package can interact across profiles.
      * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
      * calling UID.
@@ -276,11 +293,15 @@
 
     /**
      * Returns an {@link Intent} to open the settings page that allows the user to decide whether
-     * the calling app can interact across profiles. The current state is given by
-     * {@link #canInteractAcrossProfiles()}.
+     * the calling app can interact across profiles.
      *
      * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
      *
+     * <p>Note that the user may already have given consent and the app may already be able to
+     * interact across profiles, even if {@link #canRequestInteractAcrossProfiles()} is {@code
+     * true}. The current ability to interact across profiles is given by {@link
+     * #canInteractAcrossProfiles()}.
+     *
      * @return an {@link Intent} to open the settings page that allows the user to decide whether
      * the app can interact across profiles
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c78d30d..7b484b7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -38,7 +38,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.StorageStatsManager;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1988,10 +1988,11 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
-     * {@link #hasSystemFeature}: The device supports a Context Hub.
+     * {@link #hasSystemFeature}: The device supports a Context Hub, used to expose the
+     * functionalities in {@link android.hardware.location.ContextHubManager}.
      */
     @SdkConstant(SdkConstantType.FEATURE)
-    public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub";
+    public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
 
     /** {@hide} */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -3385,6 +3386,14 @@
     public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 1 << 18;
 
     /**
+     * Permission flag: Whether permission was revoked by auto-revoke.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 20;
+
+    /**
      * Permission flags: Reserved for use by the permission controller.
      *
      * @hide
@@ -3437,7 +3446,8 @@
             | FLAG_PERMISSION_REVOKED_COMPAT
             | FLAG_PERMISSION_ONE_TIME
             | FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED
-            | FLAG_PERMISSION_AUTO_REVOKE_USER_SET;
+            | FLAG_PERMISSION_AUTO_REVOKE_USER_SET
+            | FLAG_PERMISSION_AUTO_REVOKED;
 
     /**
      * Injected activity in app that forwards user to setting activity of that app.
@@ -3581,7 +3591,7 @@
      * @hide
      */
     @ChangeId
-    @Disabled
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     public static final long FILTER_APPLICATION_QUERY = 135549675L;
 
     /** {@hide} */
@@ -4262,7 +4272,8 @@
             FLAG_PERMISSION_REVOKED_COMPAT,
             FLAG_PERMISSION_ONE_TIME,
             FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED,
-            FLAG_PERMISSION_AUTO_REVOKE_USER_SET
+            FLAG_PERMISSION_AUTO_REVOKE_USER_SET,
+            FLAG_PERMISSION_AUTO_REVOKED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionFlags {}
@@ -7401,6 +7412,7 @@
             case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME";
             case FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED: return "AUTO_REVOKE_IF_UNUSED";
             case FLAG_PERMISSION_AUTO_REVOKE_USER_SET: return "AUTO_REVOKE_USER_SET";
+            case FLAG_PERMISSION_AUTO_REVOKED: return "AUTO_REVOKED";
             default: return Integer.toString(flag);
         }
     }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 637e64d..da44f70 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3710,6 +3710,11 @@
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
         }
 
+        if (sa.getBoolean(
+                R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)) {
+            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING;
+        }
+
         ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
         ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0);
 
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index da17ff3..b93c3a2 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -397,6 +397,8 @@
 
     boolean hasComponentClassName(String className);
 
+    boolean hasPreserveLegacyExternalStorage();
+
     // App Info
 
     boolean hasRequestedLegacyExternalStorage();
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 548d82a..905794b 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -2098,6 +2098,13 @@
                     R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
                     parsingPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q));
 
+            parsingPackage.setAllowNativeHeapPointerTagging(sa.getBoolean(
+                    R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true));
+
+            parsingPackage.setPreserveLegacyExternalStorage(sa.getBoolean(
+                            R.styleable.AndroidManifestApplication_preserveLegacyExternalStorage,
+                            false));
+
             parsingPackage
                     .setMaxAspectRatio(
                             sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0))
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 0df9500..180714a 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -289,6 +289,7 @@
     private String[] usesLibraryFiles;
     private List<SharedLibraryInfo> usesLibraryInfos;
     private String zygotePreloadName;
+    private boolean preserveLegacyExternalStorage;
 
     @VisibleForTesting
     public PackageImpl(
@@ -1509,6 +1510,22 @@
     }
 
     @Override
+    public PackageImpl setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging) {
+        this.privateFlags = allowNativeHeapPointerTagging
+                ? this.privateFlags | ApplicationInfo
+                        .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
+                : this.privateFlags & ~ApplicationInfo
+                        .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING;
+        return this;
+    }
+
+    @Override
+    public PackageImpl setPreserveLegacyExternalStorage(boolean preserveLegacyExternalStorage) {
+        this.preserveLegacyExternalStorage = preserveLegacyExternalStorage;
+        return this;
+    }
+
+    @Override
     public PackageImpl setUsesNonSdkApi(boolean usesNonSdkApi) {
         this.privateFlags = usesNonSdkApi
                 ? this.privateFlags | ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API
@@ -2989,6 +3006,11 @@
         return queriesProviders;
     }
 
+    @Override
+    public boolean hasPreserveLegacyExternalStorage() {
+        return preserveLegacyExternalStorage;
+    }
+
     private static void internStringArrayList(List<String> list) {
         if (list != null) {
             final int N = list.size();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index a2fe064..74a2640 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -191,6 +191,10 @@
 
     ParsingPackage setRequestLegacyExternalStorage(boolean requestLegacyExternalStorage);
 
+    ParsingPackage setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging);
+  
+    ParsingPackage setPreserveLegacyExternalStorage(boolean preserveLegacyExternalStorage);
+
     ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion);
 
     ParsingPackage setSplitHasCode(int splitIndex, boolean splitHasCode);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 471e83c..cb809da 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -62,6 +62,7 @@
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
@@ -112,7 +113,7 @@
     static final String TAG = "Resources";
 
     private static final Object sSync = new Object();
-    private final Object mLock = new Object();
+    private final Object mUpdateLock = new Object();
 
     // Used by BridgeResources in layoutlib
     @UnsupportedAppUsage
@@ -139,6 +140,7 @@
     @UnsupportedAppUsage
     final ClassLoader mClassLoader;
 
+    @GuardedBy("mUpdateLock")
     private UpdateCallbacks mCallbacks = null;
 
     /**
@@ -2375,6 +2377,7 @@
      *
      * <p>Loaders are listed in increasing precedence order. A loader will override the resources
      * and assets of loaders listed before itself.
+     * @hide
      */
     @NonNull
     public List<ResourcesLoader> getLoaders() {
@@ -2382,87 +2385,81 @@
     }
 
     /**
-     * Appends a loader to the end of the loader list. If the loader is already present in the
-     * loader list, the list will not be modified.
-     *
-     * @param loader the loader to add
-     */
-    public void addLoader(@NonNull ResourcesLoader loader) {
-        synchronized (mLock) {
-            checkCallbacksRegistered();
-
-            final List<ResourcesLoader> loaders = new ArrayList<>(
-                    mResourcesImpl.getAssets().getLoaders());
-            if (loaders.contains(loader)) {
-                return;
-            }
-
-            loaders.add(loader);
-            mCallbacks.onLoadersChanged(this, loaders);
-            loader.registerOnProvidersChangedCallback(this, mCallbacks);
-        }
-    }
-
-    /**
-     * Removes a loader from the loaders. If the loader is not present in the loader list, the list
+     * Adds a loader to the list of loaders. If the loader is already present in the list, the list
      * will not be modified.
      *
-     * @param loader the loader to remove
+     * @param loaders the loaders to add
      */
-    public void removeLoader(@NonNull ResourcesLoader loader) {
-        synchronized (mLock) {
+    public void addLoaders(@NonNull ResourcesLoader... loaders) {
+        synchronized (mUpdateLock) {
             checkCallbacksRegistered();
+            final List<ResourcesLoader> newLoaders =
+                    new ArrayList<>(mResourcesImpl.getAssets().getLoaders());
+            final ArraySet<ResourcesLoader> loaderSet = new ArraySet<>(newLoaders);
 
-            final List<ResourcesLoader> loaders = new ArrayList<>(
-                    mResourcesImpl.getAssets().getLoaders());
-            if (!loaders.remove(loader)) {
+            for (int i = 0; i < loaders.length; i++) {
+                final ResourcesLoader loader = loaders[i];
+                if (!loaderSet.contains(loader)) {
+                    newLoaders.add(loader);
+                }
+            }
+
+            if (loaderSet.size() == newLoaders.size()) {
                 return;
             }
 
-            mCallbacks.onLoadersChanged(this, loaders);
-            loader.unregisterOnProvidersChangedCallback(this);
+            mCallbacks.onLoadersChanged(this, newLoaders);
+            for (int i = loaderSet.size(), n = newLoaders.size(); i < n; i++) {
+                newLoaders.get(i).registerOnProvidersChangedCallback(this, mCallbacks);
+            }
         }
     }
 
     /**
-     * Sets the list of loaders.
+     * Removes loaders from the list of loaders. If the loader is not present in the list, the list
+     * will not be modified.
      *
-     * @param loaders the new loaders
+     * @param loaders the loaders to remove
      */
-    public void setLoaders(@NonNull List<ResourcesLoader> loaders) {
-        synchronized (mLock) {
+    public void removeLoaders(@NonNull ResourcesLoader... loaders) {
+        synchronized (mUpdateLock) {
             checkCallbacksRegistered();
-
+            final ArraySet<ResourcesLoader> removedLoaders = new ArraySet<>(loaders);
+            final List<ResourcesLoader> newLoaders = new ArrayList<>();
             final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
-            int index = 0;
-            boolean modified = loaders.size() != oldLoaders.size();
-            final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>();
-            for (final ResourcesLoader loader : loaders) {
-                if (!seenLoaders.add(loader)) {
-                    throw new IllegalArgumentException("Loader " + loader + " present twice");
-                }
 
-                if (!modified && oldLoaders.get(index++) != loader) {
-                    modified = true;
+            for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+                final ResourcesLoader loader = oldLoaders.get(i);
+                if (!removedLoaders.contains(loader)) {
+                    newLoaders.add(loader);
                 }
             }
 
-            if (!modified) {
+            if (oldLoaders.size() == newLoaders.size()) {
                 return;
             }
 
-            mCallbacks.onLoadersChanged(this, loaders);
-            for (int i = 0, n = oldLoaders.size(); i < n; i++) {
-                oldLoaders.get(i).unregisterOnProvidersChangedCallback(this);
-            }
-            for (ResourcesLoader newLoader : loaders) {
-                newLoader.registerOnProvidersChangedCallback(this, mCallbacks);
+            mCallbacks.onLoadersChanged(this, newLoaders);
+            for (int i = 0; i < loaders.length; i++) {
+                loaders[i].unregisterOnProvidersChangedCallback(this);
             }
         }
     }
 
-    /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */
+    /**
+     * Removes all {@link ResourcesLoader ResourcesLoader(s)}.
+     * @hide
+     */
+    @VisibleForTesting
     public void clearLoaders() {
-        setLoaders(Collections.emptyList());
+        synchronized (mUpdateLock) {
+            checkCallbacksRegistered();
+            final List<ResourcesLoader> newLoaders = Collections.emptyList();
+            final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+            mCallbacks.onLoadersChanged(this, newLoaders);
+            for (ResourcesLoader loader : oldLoaders) {
+                loader.unregisterOnProvidersChangedCallback(this);
+            }
+        }
     }
 }
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
index 69dacee..58fec60 100644
--- a/core/java/android/content/res/loader/ResourcesLoader.java
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -40,8 +40,8 @@
  * of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
  * objects that use the loader.
  *
- * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence
- * order. A loader will override the resources and assets of loaders listed before itself.
+ * <p>Loaders must be added to Resources objects in increasing precedence order. A loader will
+ * override the resources and assets of loaders added before itself.
  *
  * <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
  * provider will override the resources and assets of providers listed before itself.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index ef28e6c..ac36188 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -75,6 +75,18 @@
     /**
      * @hide
      */
+    public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title";
+    /**
+     * @hide
+     */
+    public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle";
+    /**
+     * @hide
+     */
+    public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description";
+    /**
+     * @hide
+     */
     public static final String KEY_NEGATIVE_TEXT = "negative_text";
     /**
      * @hide
@@ -221,6 +233,30 @@
         }
 
         /**
+         * Sets an optional title, subtitle, and/or description that will override other text when
+         * the user is authenticating with PIN/pattern/password. Currently for internal use only.
+         * @return This builder.
+         * @hide
+         */
+        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+        @NonNull
+        public Builder setTextForDeviceCredential(
+                @Nullable CharSequence title,
+                @Nullable CharSequence subtitle,
+                @Nullable CharSequence description) {
+            if (title != null) {
+                mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_TITLE, title);
+            }
+            if (subtitle != null) {
+                mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_SUBTITLE, subtitle);
+            }
+            if (description != null) {
+                mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_DESCRIPTION, description);
+            }
+            return this;
+        }
+
+        /**
          * Required: Sets the text, executor, and click listener for the negative button on the
          * prompt. This is typically a cancel button, but may be also used to show an alternative
          * method for authentication, such as a screen that asks for a backup password.
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 25b84c5..65f45d8 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -80,7 +80,7 @@
      * Display category: Presentation displays.
      * <p>
      * This category can be used to identify secondary displays that are suitable for
-     * use as presentation displays such as HDMI or Wireless displays.  Applications
+     * use as presentation displays such as external or wireless displays.  Applications
      * may automatically project their content to presentation displays to provide
      * richer second screen experiences.
      * </p>
@@ -100,7 +100,7 @@
      * When this flag is set, the virtual display is public.
      * </p><p>
      * A public virtual display behaves just like most any other display that is connected
-     * to the system such as an HDMI or Wireless display.  Applications can open
+     * to the system such as an external or wireless display.  Applications can open
      * windows on the display and the system may mirror the contents of other displays
      * onto it.
      * </p><p>
@@ -364,7 +364,7 @@
                     addAllDisplaysLocked(mTempDisplays, displayIds);
                 } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
-                    addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI);
+                    addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
                 }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index db16d24..1ed791d 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -54,7 +54,7 @@
  */
 @SystemApi
 @SystemService(Context.CONTEXTHUB_SERVICE)
-@RequiresFeature(PackageManager.FEATURE_CONTEXTHUB)
+@RequiresFeature(PackageManager.FEATURE_CONTEXT_HUB)
 public final class ContextHubManager {
     private static final String TAG = "ContextHubManager";
 
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java
new file mode 100644
index 0000000..edae06a
--- /dev/null
+++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice;
+
+import static android.inputmethodservice.InputMethodService.DEBUG;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+
+import java.lang.ref.WeakReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Maintains an active inline suggestion session.
+ *
+ * <p>
+ * Each session corresponds to one inline suggestion request, but there may be multiple callbacks
+ * with the inline suggestions response.
+ */
+class InlineSuggestionSession {
+
+    private static final String TAG = InlineSuggestionSession.class.getSimpleName();
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final IInlineSuggestionsRequestCallback mCallback;
+    @NonNull
+    private final InlineSuggestionsResponseCallbackImpl mResponseCallback;
+    @NonNull
+    private final Supplier<String> mClientPackageNameSupplier;
+    @NonNull
+    private final Supplier<InlineSuggestionsRequest> mRequestSupplier;
+    @NonNull
+    private final Supplier<IBinder> mHostInputTokenSupplier;
+    @NonNull
+    private final Consumer<InlineSuggestionsResponse> mResponseConsumer;
+
+    private volatile boolean mInvalidated = false;
+
+    InlineSuggestionSession(@NonNull ComponentName componentName,
+            @NonNull IInlineSuggestionsRequestCallback callback,
+            @NonNull Supplier<String> clientPackageNameSupplier,
+            @NonNull Supplier<InlineSuggestionsRequest> requestSupplier,
+            @NonNull Supplier<IBinder> hostInputTokenSupplier,
+            @NonNull Consumer<InlineSuggestionsResponse> responseConsumer) {
+        mComponentName = componentName;
+        mCallback = callback;
+        mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
+        mClientPackageNameSupplier = clientPackageNameSupplier;
+        mRequestSupplier = requestSupplier;
+        mHostInputTokenSupplier = hostInputTokenSupplier;
+        mResponseConsumer = responseConsumer;
+
+        makeInlineSuggestionsRequest();
+    }
+
+    /**
+     * This needs to be called before creating a new session, such that the later response callbacks
+     * will be discarded.
+     */
+    void invalidateSession() {
+        mInvalidated = true;
+    }
+
+    /**
+     * Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current
+     * Autofill Session through
+     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
+     */
+    private void makeInlineSuggestionsRequest() {
+        try {
+            final InlineSuggestionsRequest request = mRequestSupplier.get();
+            if (request == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+                }
+                mCallback.onInlineSuggestionsUnsupported();
+            } else {
+                request.setHostInputToken(mHostInputTokenSupplier.get());
+                mCallback.onInlineSuggestionsRequest(request, mResponseCallback);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+        }
+    }
+
+    private void handleOnInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+        if (mInvalidated) {
+            if (DEBUG) {
+                Log.d(TAG, "handleOnInlineSuggestionsResponse() called on invalid session");
+            }
+            return;
+        }
+        // TODO(b/149522488): checking the current focused input field to make sure we don't send
+        //  inline responses for previous input field
+        if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())) {
+            if (DEBUG) {
+                Log.d(TAG, "handleOnInlineSuggestionsResponse() called on the wrong package name");
+            }
+            return;
+        }
+        mResponseConsumer.accept(response);
+    }
+
+    /**
+     * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+     */
+    static final class InlineSuggestionsResponseCallbackImpl
+            extends IInlineSuggestionsResponseCallback.Stub {
+        private final WeakReference<InlineSuggestionSession> mInlineSuggestionSession;
+
+        private InlineSuggestionsResponseCallbackImpl(
+                InlineSuggestionSession inlineSuggestionSession) {
+            mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession);
+        }
+
+        @Override
+        public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
+                throws RemoteException {
+            final InlineSuggestionSession session = mInlineSuggestionSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        InlineSuggestionSession::handleOnInlineSuggestionsResponse, session,
+                        response));
+            }
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 49e1d5e..b1aa67e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -22,8 +22,6 @@
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.AnyThread;
@@ -51,7 +49,6 @@
 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.os.SystemClock;
@@ -102,13 +99,11 @@
 import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
-import com.android.internal.view.IInlineSuggestionsResponseCallback;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.util.Collections;
 
 /**
@@ -450,7 +445,7 @@
     final int[] mTmpLocation = new int[2];
 
     @Nullable
-    private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
+    private InlineSuggestionSession mInlineSuggestionSession;
 
     private boolean mAutomotiveHideNavBarForKeyboard;
     private boolean mIsAutomotive;
@@ -465,8 +460,6 @@
      */
     private IBinder mCurShowInputToken;
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
-
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
         onComputeInsets(mTmpInsets);
         if (isExtractViewShown()) {
@@ -538,7 +531,7 @@
             if (DEBUG) {
                 Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
             }
-            handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+            handleOnCreateInlineSuggestionsRequest(componentName, cb);
         }
 
         /**
@@ -770,39 +763,9 @@
         return false;
     }
 
-    /**
-     * Sends an {@link InlineSuggestionsRequest} obtained from
-     * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
-     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
-     */
-    private void makeInlineSuggestionsRequest() {
-        if (mInlineSuggestionsRequestInfo == null) {
-            Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
-            return;
-        }
-
-        final IInlineSuggestionsRequestCallback requestCallback =
-                mInlineSuggestionsRequestInfo.mCallback;
-        try {
-            final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
-            if (request == null) {
-                Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
-                requestCallback.onInlineSuggestionsUnsupported();
-            } else {
-                final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
-                        new InlineSuggestionsResponseCallbackImpl(this,
-                                mInlineSuggestionsRequestInfo.mComponentName,
-                                mInlineSuggestionsRequestInfo.mFocusedId);
-                requestCallback.onInlineSuggestionsRequest(request,
-                        inlineSuggestionsResponseCallback);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
-        }
-    }
-
+    @MainThread
     private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
-            @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
+            @NonNull IInlineSuggestionsRequestCallback callback) {
         if (!mInputStarted) {
             try {
                 Log.w(TAG, "onStartInput() not called yet");
@@ -813,24 +776,32 @@
             return;
         }
 
-        mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
-                callback);
-
-        makeInlineSuggestionsRequest();
+        if (mInlineSuggestionSession != null) {
+            mInlineSuggestionSession.invalidateSession();
+        }
+        mInlineSuggestionSession = new InlineSuggestionSession(componentName, callback,
+                this::getEditorInfoPackageName, this::onCreateInlineSuggestionsRequest,
+                this::getHostInputToken, this::onInlineSuggestionsResponse);
     }
 
-    private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
-            @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
-        if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Response component=" + componentName + " differs from request component="
-                                + mInlineSuggestionsRequestInfo.mComponentName
-                                + ", ignoring response");
-            }
-            return;
+    @Nullable
+    private String getEditorInfoPackageName() {
+        if (mInputEditorInfo != null) {
+            return mInputEditorInfo.packageName;
         }
-        onInlineSuggestionsResponse(response);
+        return null;
+    }
+
+    /**
+     * Returns the {@link IBinder} input token from the host view root.
+     */
+    @Nullable
+    private IBinder getHostInputToken() {
+        ViewRootImpl viewRoot = null;
+        if (mRootView != null) {
+            viewRoot = mRootView.getViewRootImpl();
+        }
+        return viewRoot == null ? null : viewRoot.getInputToken();
     }
 
     private void notifyImeHidden() {
@@ -851,63 +822,6 @@
     }
 
     /**
-     * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
-     */
-    private static final class InlineSuggestionsResponseCallbackImpl
-            extends IInlineSuggestionsResponseCallback.Stub {
-        private final WeakReference<InputMethodService> mInputMethodService;
-
-        private final ComponentName mRequestComponentName;
-        private final AutofillId mRequestAutofillId;
-
-        private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
-                ComponentName componentName, AutofillId autofillId) {
-            mInputMethodService = new WeakReference<>(inputMethodService);
-            mRequestComponentName = componentName;
-            mRequestAutofillId = autofillId;
-        }
-
-        @Override
-        public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
-                throws RemoteException {
-            final InputMethodService service = mInputMethodService.get();
-            if (service != null) {
-                service.mHandler.sendMessage(obtainMessage(
-                        InputMethodService::handleOnInlineSuggestionsResponse, service,
-                        mRequestComponentName, mRequestAutofillId, response));
-            }
-        }
-    }
-
-    /**
-     * Information about incoming requests from Autofill Frameworks for inline suggestions.
-     */
-    private static final class InlineSuggestionsRequestInfo {
-        final ComponentName mComponentName;
-        final AutofillId mFocusedId;
-        final IInlineSuggestionsRequestCallback mCallback;
-
-        InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
-                IInlineSuggestionsRequestCallback callback) {
-            this.mComponentName = componentName;
-            this.mFocusedId = focusedId;
-            this.mCallback = callback;
-        }
-
-        /**
-         * Returns whether the cached {@link ComponentName} matches the passed in activity.
-         */
-        public boolean validate(ComponentName componentName) {
-            final boolean result = componentName.equals(mComponentName);
-            if (DEBUG && !result) {
-                Log.d(TAG, "Cached request info ComponentName=" + mComponentName
-                        + " differs from received ComponentName=" + componentName);
-            }
-            return result;
-        }
-    }
-
-    /**
      * Concrete implementation of
      * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
      * all of the standard behavior for an input method session.
diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS
new file mode 100644
index 0000000..4447197
--- /dev/null
+++ b/core/java/android/inputmethodservice/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../../../services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index fb35b4b..8afeb30 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,7 +15,9 @@
  */
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.IBinder;
@@ -23,6 +25,8 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 /**
  * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
  * activity to indicate to the system different outcomes of captive portal sign in.  This class is
@@ -76,6 +80,17 @@
     private final IBinder mBinder;
 
     /** @hide */
+    @IntDef(value = {
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED,
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED,
+        MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS,
+        MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR,
+    })
+    public @interface EventId {
+    }
+
+    /** @hide */
     public CaptivePortal(@NonNull IBinder binder) {
         mBinder = binder;
     }
@@ -153,6 +168,7 @@
      */
     @SystemApi
     @TestApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
     public void reevaluateNetwork() {
         try {
             ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
@@ -168,7 +184,7 @@
      */
     @SystemApi
     @TestApi
-    public void logEvent(int eventId, @NonNull String packageName) {
+    public void logEvent(@EventId int eventId, @NonNull String packageName) {
         try {
             ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
         } catch (RemoteException e) {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 09ec6c3..d83715c 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -51,7 +51,7 @@
  *
  * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
  * transport mode security associations and apply them to individual sockets. Applications looking
- * to create a VPN should use {@link VpnService}.
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
  *
  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
  *     Internet Protocol</a>
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 6d46c20..3c1b86b 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -548,32 +548,32 @@
         final int startIndex = getIndexAfter(end);
         for (int i = startIndex; i >= 0; i--) {
             final long curStart = bucketStart[i];
-            final long curEnd = curStart + bucketDuration;
+            long curEnd = curStart + bucketDuration;
 
             // bucket is older than request; we're finished
             if (curEnd <= start) break;
             // bucket is newer than request; keep looking
             if (curStart >= end) continue;
 
-            // include full value for active buckets, otherwise only fractional
-            final boolean activeBucket = curStart < now && curEnd > now;
-            final long overlap;
-            if (activeBucket) {
-                overlap = bucketDuration;
-            } else {
-                final long overlapEnd = curEnd < end ? curEnd : end;
-                final long overlapStart = curStart > start ? curStart : start;
-                overlap = overlapEnd - overlapStart;
-            }
+            // the active bucket is shorter then a normal completed bucket
+            if (curEnd > now) curEnd = now;
+            // usually this is simply bucketDuration
+            final long bucketSpan = curEnd - curStart;
+            // prevent division by zero
+            if (bucketSpan <= 0) continue;
+
+            final long overlapEnd = curEnd < end ? curEnd : end;
+            final long overlapStart = curStart > start ? curStart : start;
+            final long overlap = overlapEnd - overlapStart;
             if (overlap <= 0) continue;
 
             // integer math each time is faster than floating point
-            if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
-            if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
-            if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
-            if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
-            if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
-            if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
+            if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketSpan;
+            if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketSpan;
+            if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketSpan;
+            if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketSpan;
+            if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketSpan;
+            if (operations != null) entry.operations += operations[i] * overlap / bucketSpan;
         }
         return entry;
     }
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 6453af8..81deba4 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -365,12 +365,16 @@
     }
 
     /**
+     * This method returns true when the parcel is 'definitely' empty.
+     * That is, it may return false for an empty parcel. But will never return true for a non-empty
+     * one.
+     *
      * @hide this should probably be the implementation of isEmpty().  To do that we
      * need to ensure we always use the special empty parcel form when the bundle is
      * empty.  (This may already be the case, but to be safe we'll do this later when
      * we aren't trying to stabilize.)
      */
-    public boolean maybeIsEmpty() {
+    public boolean isDefinitelyEmpty() {
         if (isParcelled()) {
             return isEmptyParcel();
         } else {
@@ -402,6 +406,9 @@
         if (other == null) {
             return false;
         }
+        if (isDefinitelyEmpty() && other.isDefinitelyEmpty()) {
+            return true;
+        }
         if (isParcelled() != other.isParcelled()) {
             // Big kind-of here!
             return false;
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
index 6450a67..6516917 100644
--- a/core/java/android/os/incremental/V4Signature.java
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -16,6 +16,7 @@
 
 package android.os.incremental;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -47,6 +48,15 @@
     }
 
     /**
+     * Construct a V4Signature from .idsig file.
+     */
+    public static V4Signature readFrom(byte[] bytes) throws IOException {
+        try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
+            return readFrom(stream);
+        }
+    }
+
+    /**
      * Store the V4Signature to a byte-array.
      */
     public byte[] toByteArray() {
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index bb1dafc..8d04df0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -215,6 +215,20 @@
     public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
 
     /**
+     * Activity Action: Allows the user to free up space by clearing app external cache directories.
+     * The intent doesn't automatically clear cache, but shows a dialog and lets the user decide.
+     * <p>
+     * This intent should be launched using
+     * {@link Activity#startActivityForResult(Intent, int)} so that the user
+     * knows which app is requesting to clear cache. The returned result will
+     * be {@link Activity#RESULT_OK} if the activity was launched and the user accepted to clear
+     * cache, or {@link Activity#RESULT_CANCELED} otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE";
+
+    /**
      * Extra {@link UUID} used to indicate the storage volume where an
      * application is interested in allocating or managing disk space.
      *
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 5cac5f5..e78aef5 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -118,4 +118,10 @@
      * @param userId the userId for which to reset storage
      */
     public abstract void resetUser(int userId);
+
+    /**
+     * Returns {@code true} if the immediate last installed version of an app with {@code uid} had
+     * legacy storage, {@code false} otherwise.
+     */
+    public abstract boolean hasLegacyExternalStorage(int uid);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7c4ec8e..f6072f9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -997,8 +997,9 @@
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
      * <p>
-     * Input: Optionally, in versions of Android prior to 11, the Intent's data URI can specify the
-     * application package name to directly invoke the management GUI specific to the package name.
+     * Input: Optionally, in versions of Android prior to {@link android.os.Build.VERSION_CODES#R},
+     * the Intent's data URI can specify the application package name to directly invoke the
+     * management GUI specific to the package name.
      * For example "package:com.my.app".
      * <p>
      * Output: Nothing.
@@ -1011,9 +1012,10 @@
      * Activity Action: Show screen for controlling if the app specified in the data URI of the
      * intent can draw on top of other apps.
      * <p>
-     * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android 11 can't be used to show
-     * a GUI for a specific package, permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is
-     * needed to start an activity with this intent.
+     * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android {@link
+     * android.os.Build.VERSION_CODES#R} can't be used to show a GUI for a specific package,
+     * permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is needed to start an activity
+     * with this intent.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
@@ -2053,6 +2055,19 @@
     public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS";
 
     /**
+     * Activity Action: Show screen that let user select enable (or disable) tethering.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
+
+    /**
      * Broadcast to trigger notification of asking user to enable MMS.
      * Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}.
      *
@@ -6447,6 +6462,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS =
                 "lock_screen_allow_private_notifications";
 
@@ -7865,6 +7881,7 @@
          * @hide
          */
         @UnsupportedAppUsage
+        @TestApi
         public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
 
         /**
@@ -8067,6 +8084,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS =
                 "lock_screen_show_notifications";
 
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 7032825..e8e1223 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -75,6 +75,7 @@
     private final @Nullable SaveInfo mSaveInfo;
     private final @Nullable Bundle mClientState;
     private final @Nullable RemoteViews mPresentation;
+    private final @Nullable InlinePresentation mInlinePresentation;
     private final @Nullable RemoteViews mHeader;
     private final @Nullable RemoteViews mFooter;
     private final @Nullable IntentSender mAuthentication;
@@ -95,6 +96,7 @@
         mSaveInfo = builder.mSaveInfo;
         mClientState = builder.mClientState;
         mPresentation = builder.mPresentation;
+        mInlinePresentation = builder.mInlinePresentation;
         mHeader = builder.mHeader;
         mFooter = builder.mFooter;
         mAuthentication = builder.mAuthentication;
@@ -131,6 +133,11 @@
     }
 
     /** @hide */
+    public @Nullable InlinePresentation getInlinePresentation() {
+        return mInlinePresentation;
+    }
+
+    /** @hide */
     public @Nullable RemoteViews getHeader() {
         return mHeader;
     }
@@ -219,6 +226,7 @@
         private SaveInfo mSaveInfo;
         private Bundle mClientState;
         private RemoteViews mPresentation;
+        private InlinePresentation mInlinePresentation;
         private RemoteViews mHeader;
         private RemoteViews mFooter;
         private IntentSender mAuthentication;
@@ -318,6 +326,67 @@
         }
 
         /**
+         * Triggers a custom UI before before autofilling the screen with any data set in this
+         * response.
+         *
+         * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+         * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+         * for examples.
+         *
+         * <p>This method is similar to
+         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, but also accepts
+         * an {@link InlinePresentation} presentation which is required for authenticating through
+         * the inline autofill flow.
+         *
+         * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
+         * not work with {@link InlinePresentation}.</p>
+         *
+         * @param authentication Intent to an activity with your authentication flow.
+         * @param presentation The presentation to visualize the response.
+         * @param inlinePresentation The inlinePresentation to visualize the response inline.
+         * @param ids id of Views that when focused will display the authentication UI.
+         *
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException if any of the following occurs:
+         * <ul>
+         *   <li>{@code ids} is {@code null}</li>
+         *   <li>{@code ids} is empty</li>
+         *   <li>{@code ids} contains a {@code null} element</li>
+         *   <li>both {@code authentication} and {@code presentation} are {@code null}</li>
+         *   <li>both {@code authentication} and {@code presentation} are non-{@code null}</li>
+         *   <li>both {@code authentication} and {@code inlinePresentation} are {@code null}</li>
+         *   <li>both {@code authentication} and {@code inlinePresentation} are
+         *   non-{@code null}</li>
+         * </ul>
+         *
+         * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+         * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+         *
+         * @see android.app.PendingIntent#getIntentSender()
+         */
+        @NonNull
+        public Builder setAuthentication(@NonNull AutofillId[] ids,
+                @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+                @Nullable InlinePresentation inlinePresentation) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            if (mHeader != null || mFooter != null) {
+                throw new IllegalStateException("Already called #setHeader() or #setFooter()");
+            }
+
+            if (authentication == null ^ (presentation == null && inlinePresentation == null)) {
+                throw new IllegalArgumentException("authentication and presentation "
+                        + "(dropdown or inline), must be both non-null or null");
+            }
+            mAuthentication = authentication;
+            mPresentation = presentation;
+            mInlinePresentation = inlinePresentation;
+            mAuthenticationIds = assertValid(ids);
+            return this;
+        }
+
+        /**
          * Specifies views that should not trigger new
          * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
          * FillCallback)} requests.
@@ -644,6 +713,8 @@
                         break;
                     }
                 }
+            } else if (mInlinePresentation != null) {
+                mSupportsInlineSuggestions = true;
             }
 
             mDestroyed = true;
@@ -691,6 +762,9 @@
         if (mPresentation != null) {
             builder.append(", hasPresentation");
         }
+        if (mInlinePresentation != null) {
+            builder.append(", hasInlinePresentation");
+        }
         if (mHeader != null) {
             builder.append(", hasHeader");
         }
@@ -740,6 +814,7 @@
         parcel.writeParcelableArray(mAuthenticationIds, flags);
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeParcelable(mPresentation, flags);
+        parcel.writeParcelable(mInlinePresentation, flags);
         parcel.writeParcelable(mHeader, flags);
         parcel.writeParcelable(mFooter, flags);
         parcel.writeParcelable(mUserData, flags);
@@ -774,8 +849,10 @@
                     AutofillId.class);
             final IntentSender authentication = parcel.readParcelable(null);
             final RemoteViews presentation = parcel.readParcelable(null);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
             if (authenticationIds != null) {
-                builder.setAuthentication(authenticationIds, authentication, presentation);
+                builder.setAuthentication(authenticationIds, authentication, presentation,
+                        inlinePresentation);
             }
             final RemoteViews header = parcel.readParcelable(null);
             if (header != null) {
diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
index decdcf5..c389b1a 100644
--- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
@@ -16,6 +16,7 @@
 
 package android.service.autofill;
 
+import android.os.IBinder;
 import android.service.autofill.IInlineSuggestionUiCallback;
 import android.service.autofill.InlinePresentation;
 
@@ -25,6 +26,7 @@
  * @hide
  */
 oneway interface IInlineSuggestionRenderService {
-    void renderSuggestion(in IInlineSuggestionUiCallback callback, in InlinePresentation presentation,
-                     int width, int height);
+    void renderSuggestion(in IInlineSuggestionUiCallback callback,
+                          in InlinePresentation presentation, int width, int height,
+                          in IBinder hostInputToken);
 }
diff --git a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
index a55a2ce..210f95f 100644
--- a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.service.autofill;
 
+import android.os.IBinder;
 import android.view.SurfaceControl;
 
 /**
@@ -24,6 +25,8 @@
  * @hide
  */
 oneway interface IInlineSuggestionUiCallback {
-    void autofill();
+    void onAutofill();
     void onContent(in SurfaceControl surface);
+    void onError();
+    void onTransferTouchFocusToImeWindow(in IBinder sourceInputToken, int displayId);
 }
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index 2593aab..4aafb63 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -24,11 +24,16 @@
 import android.app.Service;
 import android.app.slice.Slice;
 import android.content.Intent;
+import android.graphics.PixelFormat;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.WindowManager;
 
 /**
  * A service that renders an inline presentation given the {@link InlinePresentation} containing
@@ -55,8 +60,40 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
 
     private void handleRenderSuggestion(IInlineSuggestionUiCallback callback,
-            InlinePresentation presentation, int width, int height) {
-        //TODO(b/146453086): implementation in ExtService
+            InlinePresentation presentation, int width, int height, IBinder hostInputToken) {
+        if (hostInputToken == null) {
+            try {
+                callback.onError();
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException calling onError()");
+            }
+            return;
+        }
+        final SurfaceControlViewHost host = new SurfaceControlViewHost(this, this.getDisplay(),
+                hostInputToken);
+        final SurfaceControl surface = host.getSurfacePackage().getSurfaceControl();
+
+        final View suggestionView = onRenderSuggestion(presentation, width, height);
+
+        final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(this, callback);
+        suggestionRoot.addView(suggestionView);
+        suggestionRoot.setOnClickListener((v) -> {
+            try {
+                callback.onAutofill();
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException calling onAutofill()");
+            }
+        });
+
+        WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
+        host.addView(suggestionRoot, lp);
+        try {
+            callback.onContent(surface);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling onContent(" + surface + ")");
+        }
     }
 
     @Override
@@ -66,11 +103,12 @@
             return new IInlineSuggestionRenderService.Stub() {
                 @Override
                 public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
-                        @NonNull InlinePresentation presentation, int width, int height) {
+                        @NonNull InlinePresentation presentation, int width, int height,
+                        @Nullable IBinder hostInputToken) {
                     mHandler.sendMessage(obtainMessage(
                             InlineSuggestionRenderService::handleRenderSuggestion,
                             InlineSuggestionRenderService.this, callback, presentation,
-                            width, height));
+                            width, height, hostInputToken));
                 }
             }.asBinder();
         }
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/core/java/android/service/autofill/InlineSuggestionRoot.java
similarity index 65%
rename from services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
rename to core/java/android/service/autofill/InlineSuggestionRoot.java
index e813dae..bdcc253 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
+++ b/core/java/android/service/autofill/InlineSuggestionRoot.java
@@ -14,39 +14,39 @@
  * limitations under the License.
  */
 
-package com.android.server.autofill.ui;
+package android.service.autofill;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.os.RemoteException;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.widget.FrameLayout;
 
-import com.android.server.LocalServices;
-import com.android.server.wm.WindowManagerInternal;
-
 /**
  * This class is the root view for an inline suggestion. It is responsible for
  * detecting the click on the item and to also transfer input focus to the IME
  * window if we detect the user is scrolling.
+ *
+ * @hide
  */
- // TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus
 @SuppressLint("ViewConstructor")
-class InlineSuggestionRoot extends FrameLayout {
-    private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName();
+public class InlineSuggestionRoot extends FrameLayout {
+    private static final String TAG = "InlineSuggestionRoot";
 
-    private final @NonNull Runnable mOnErrorCallback;
+    private final @NonNull IInlineSuggestionUiCallback mCallback;
     private final int mTouchSlop;
 
     private float mDownX;
     private float mDownY;
 
-    InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) {
+    public InlineSuggestionRoot(@NonNull Context context,
+            @NonNull IInlineSuggestionUiCallback callback) {
         super(context);
-        mOnErrorCallback = onErrorCallback;
+        mCallback = callback;
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         setFocusable(false);
     }
@@ -64,20 +64,15 @@
                 final float distance = MathUtils.dist(mDownX, mDownY,
                         event.getX(), event.getY());
                 if (distance > mTouchSlop) {
-                    transferTouchFocusToImeWindow();
+                    try {
+                        mCallback.onTransferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
+                                getContext().getDisplayId());
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException transferring touch focus to IME");
+                    }
                 }
             } break;
         }
         return super.onTouchEvent(event);
     }
-
-    private void transferTouchFocusToImeWindow() {
-        final WindowManagerInternal windowManagerInternal = LocalServices.getService(
-                WindowManagerInternal.class);
-        if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
-                getContext().getDisplayId())) {
-            Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME");
-            mOnErrorCallback.run();
-        }
-    }
 }
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 79852d3..672b501 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -89,6 +89,8 @@
 
     private SparseArray<AutofillProxy> mAutofillProxies;
 
+    private AutofillProxy mAutofillProxyForLastRequest;
+
     // Used for metrics / debug only
     private ComponentName mServiceComponentName;
 
@@ -158,6 +160,38 @@
     }
 
     /**
+     * The child class of the service can call this method to initiate an Autofill flow.
+     *
+     * <p> The request would be respected only if the previous augmented autofill request was
+     * made for the same {@code activityComponent} and {@code autofillId}, and the field is
+     * currently on focus.
+     *
+     * <p> The request would start a new autofill flow. It doesn't guarantee that the
+     * {@link AutofillManager} will proceed with the request.
+     *
+     * @param activityComponent the client component for which the autofill is requested for
+     * @param autofillId        the client field id for which the autofill is requested for
+     * @return true if the request makes the {@link AutofillManager} start a new Autofill flow,
+     * false otherwise.
+     */
+    public final boolean requestAutofill(@NonNull ComponentName activityComponent,
+            @NonNull AutofillId autofillId) {
+        // TODO(b/149531989): revisit this. The request should start a new autofill session
+        //  rather than reusing the existing session.
+        final AutofillProxy proxy = mAutofillProxyForLastRequest;
+        if (proxy == null || !proxy.mComponentName.equals(activityComponent)
+                || !proxy.mFocusedId.equals(autofillId)) {
+            return false;
+        }
+        try {
+            return proxy.requestAutofill();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
      * Asks the service to handle an "augmented" autofill request.
      *
      * <p>This method is called when the "stantard" autofill service cannot handle a request, which
@@ -241,6 +275,7 @@
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
+        mAutofillProxyForLastRequest = proxy;
         onFillRequest(new FillRequest(proxy, inlineSuggestionsRequest), cancellationSignal,
                 new FillController(proxy), new FillCallback(proxy));
     }
@@ -268,6 +303,7 @@
                 proxy.destroy();
             }
             mAutofillProxies.clear();
+            mAutofillProxyForLastRequest = null;
         }
     }
 
@@ -287,6 +323,7 @@
             }
         }
         mAutofillProxies = null;
+        mAutofillProxyForLastRequest = null;
     }
 
     @Override
@@ -481,6 +518,11 @@
             mClient.requestHideFillUi(mSessionId, mFocusedId);
         }
 
+
+        private boolean requestAutofill() throws RemoteException {
+            return mClient.requestAutofill(mSessionId, mFocusedId);
+        }
+
         private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue,
                 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) {
             synchronized (mLock) {
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
index 9d210cd..f6a86d9 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
@@ -17,28 +17,11 @@
 package android.service.quickaccesswallet;
 
 import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
 
 /**
  * Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
  */
-public final class GetWalletCardsCallback {
-
-    private static final String TAG = "QAWalletCallback";
-
-    private final IQuickAccessWalletServiceCallbacks mCallback;
-    private final Handler mHandler;
-    private boolean mCalled;
-
-    /**
-     * @hide
-     */
-    GetWalletCardsCallback(IQuickAccessWalletServiceCallbacks callback, Handler handler) {
-        mCallback = callback;
-        mHandler = handler;
-    }
+public interface GetWalletCardsCallback {
 
     /**
      * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
@@ -46,11 +29,10 @@
      *
      * @param response The response contains the list of {@link WalletCard walletCards} to be shown
      *                 to the user as well as the index of the card that should initially be
-     *                 presented as the selected card.
+     *                 presented as the selected card. The list should not contain more than the
+     *                 maximum number of cards requested.
      */
-    public void onSuccess(@NonNull GetWalletCardsResponse response) {
-        mHandler.post(() -> onSuccessInternal(response));
-    }
+    void onSuccess(@NonNull GetWalletCardsResponse response);
 
     /**
      * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
@@ -60,33 +42,5 @@
      *              (Personally Identifiable Information, such as username or email address).
      * @throws IllegalStateException if this method or {@link #onSuccess} was already called.
      */
-    public void onFailure(@NonNull GetWalletCardsError error) {
-        mHandler.post(() -> onFailureInternal(error));
-    }
-
-    private void onSuccessInternal(GetWalletCardsResponse response) {
-        if (mCalled) {
-            Log.w(TAG, "already called");
-            return;
-        }
-        mCalled = true;
-        try {
-            mCallback.onGetWalletCardsSuccess(response);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error returning wallet cards", e);
-        }
-    }
-
-    private void onFailureInternal(GetWalletCardsError error) {
-        if (mCalled) {
-            Log.w(TAG, "already called");
-            return;
-        }
-        mCalled = true;
-        try {
-            mCallback.onGetWalletCardsFailure(error);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error returning failure message", e);
-        }
-    }
+    void onFailure(@NonNull GetWalletCardsError error);
 }
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
new file mode 100644
index 0000000..d2494a5
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
+ *
+ * @hide
+ */
+final class GetWalletCardsCallbackImpl implements GetWalletCardsCallback {
+
+    private static final String TAG = "QAWalletCallback";
+
+    private final IQuickAccessWalletServiceCallbacks mCallback;
+    private final GetWalletCardsRequest mRequest;
+    private final Handler mHandler;
+    private boolean mCalled;
+
+    GetWalletCardsCallbackImpl(GetWalletCardsRequest request,
+            IQuickAccessWalletServiceCallbacks callback, Handler handler) {
+        mRequest = request;
+        mCallback = callback;
+        mHandler = handler;
+    }
+
+    /**
+     * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+     * was successfully handled by the service.
+     *
+     * @param response The response contains the list of {@link WalletCard walletCards} to be shown
+     *                 to the user as well as the index of the card that should initially be
+     *                 presented as the selected card.
+     */
+    public void onSuccess(@NonNull GetWalletCardsResponse response) {
+        Log.i(TAG, "onSuccess");
+        if (isValidResponse(response)) {
+            mHandler.post(() -> onSuccessInternal(response));
+        } else {
+            Log.w(TAG, "Invalid GetWalletCards response");
+            mHandler.post(() -> onFailureInternal(new GetWalletCardsError(null, null)));
+        }
+    }
+
+    /**
+     * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+     * could not be handled by the service.
+     *
+     * @param error The error message. <b>Note: </b> this message should <b>not</b> contain PII
+     *              (Personally Identifiable Information, such as username or email address).
+     * @throws IllegalStateException if this method or {@link #onSuccess} was already called.
+     */
+    public void onFailure(@NonNull GetWalletCardsError error) {
+        mHandler.post(() -> onFailureInternal(error));
+    }
+
+    private void onSuccessInternal(GetWalletCardsResponse response) {
+        Log.i(TAG, "onSuccessInternal");
+        if (mCalled) {
+            Log.w(TAG, "already called");
+            return;
+        }
+        mCalled = true;
+        try {
+            mCallback.onGetWalletCardsSuccess(response);
+            Log.i(TAG, "onSuccessInternal: returned response");
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error returning wallet cards", e);
+        }
+    }
+
+    private void onFailureInternal(GetWalletCardsError error) {
+        if (mCalled) {
+            Log.w(TAG, "already called");
+            return;
+        }
+        mCalled = true;
+        try {
+            mCallback.onGetWalletCardsFailure(error);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error returning failure message", e);
+        }
+    }
+
+    private boolean isValidResponse(@NonNull GetWalletCardsResponse response) {
+        return response != null
+                && response.getWalletCards() != null
+                && response.getSelectedIndex() >= 0
+                && (response.getWalletCards().isEmpty() // selectedIndex may be 0 when list is empty
+                || response.getSelectedIndex() < response.getWalletCards().size())
+                && response.getWalletCards().size() < mRequest.getMaxCards()
+                && areValidCards(response.getWalletCards());
+    }
+
+    private boolean areValidCards(List<WalletCard> walletCards) {
+        for (WalletCard walletCard : walletCards) {
+            if (walletCard == null
+                    || walletCard.getCardId() == null
+                    || walletCard.getCardImage() == null
+                    || TextUtils.isEmpty(walletCard.getContentDescription())
+                    || walletCard.getPendingIntent() == null) {
+                return false;
+            }
+            Icon cardImage = walletCard.getCardImage();
+            if (cardImage.getType() == Icon.TYPE_BITMAP
+                    && walletCard.getCardImage().getBitmap().getConfig()
+                    != Bitmap.Config.HARDWARE) {
+                Log.w(TAG, "WalletCard bitmaps should be hardware bitmaps");
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
index 996622a..0551e27 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -35,9 +35,13 @@
     /**
      * Construct a new response.
      *
-     * @param walletCards   The list of wallet cards.
+     * @param walletCards   The list of wallet cards. The list may be empty but must NOT be larger
+     *                      than {@link GetWalletCardsRequest#getMaxCards()}. The list may not
+     *                      contain null values.
      * @param selectedIndex The index of the card that should be presented as the initially
-     *                      'selected' card
+     *                      'selected' card. The index must be greater than or equal to zero and
+     *                      less than the size of the list of walletCards (unless the list is empty
+     *                      in which case the value may be 0).
      */
     public GetWalletCardsResponse(@NonNull List<WalletCard> walletCards, int selectedIndex) {
         this.mWalletCards = walletCards;
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index cfc6d57..be9ab11 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -18,16 +18,16 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.content.Intent;
 
-import java.util.function.Consumer;
-
 /**
  * Facilitates accessing cards from the {@link QuickAccessWalletService}.
  *
  * @hide
  */
+@TestApi
 public interface QuickAccessWalletClient {
 
     /**
@@ -40,17 +40,65 @@
     }
 
     /**
-     * @return true if the {@link QuickAccessWalletService} is available.
+     * @return true if the {@link QuickAccessWalletService} is available. This means that the
+     * default NFC payment application has an exported service that can provide cards to the Quick
+     * Access Wallet. However, it does not mean that (1) the call will necessarily be successful,
+     * nor does it mean that cards may be displayed at this time. Addition checks are required:
+     * <ul>
+     *     <li>If {@link #isWalletFeatureAvailable()} is false, cards should not be displayed
+     *     <li>If the device is locked and {@link #isWalletFeatureAvailableWhenDeviceLocked} is
+     *     false, cards should not be displayed while the device remains locked. (A message
+     *     prompting the user to unlock to view cards may be appropriate).</li>
+     * </ul>
      */
     boolean isWalletServiceAvailable();
 
     /**
+     * Wallet cards should not be displayed if:
+     * <ul>
+     *     <li>The wallet service is unavailable</li>
+     *     <li>The device is not provisioned, ie user setup is incomplete</li>
+     *     <li>If the wallet feature has been disabled by the user</li>
+     *     <li>If the phone has been put into lockdown mode</li>
+     * </ul>
+     * <p>
+     * Quick Access Wallet implementers should call this method before calling
+     * {@link #getWalletCards} to ensure that cards may be displayed.
+     */
+    boolean isWalletFeatureAvailable();
+
+    /**
+     * Wallet cards may not be displayed on the lock screen if the user has opted to hide
+     * notifications or sensitive content on the lock screen.
+     * <ul>
+     *     <li>The device is not provisioned, ie user setup is incomplete</li>
+     *     <li>If the wallet feature has been disabled by the user</li>
+     *     <li>If the phone has been put into lockdown mode</li>
+     * </ul>
+     *
+     * <p>
+     * Quick Access Wallet implementers should call this method before calling
+     * {@link #getWalletCards} if the device is currently locked.
+     *
+     * @return true if cards may be displayed on the lock screen.
+     */
+    boolean isWalletFeatureAvailableWhenDeviceLocked();
+
+    /**
      * Get wallet cards from the {@link QuickAccessWalletService}.
      */
     void getWalletCards(
             @NonNull GetWalletCardsRequest request,
-            @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
-            @NonNull Consumer<GetWalletCardsError> onFailureListener);
+            @NonNull OnWalletCardsRetrievedCallback callback);
+
+    /**
+     * Callback for getWalletCards
+     */
+    interface OnWalletCardsRetrievedCallback {
+        void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response);
+
+        void onWalletCardRetrievalError(@NonNull GetWalletCardsError error);
+    }
 
     /**
      * Notify the {@link QuickAccessWalletService} service that a wallet card was selected.
@@ -65,12 +113,24 @@
     /**
      * Unregister event listener.
      */
-    void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+    void addWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
 
     /**
      * Unregister event listener
      */
-    void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+    void removeWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
+
+    /**
+     * A listener for {@link WalletServiceEvent walletServiceEvents}
+     */
+    interface WalletServiceEventListener {
+        void onWalletServiceEvent(@NonNull WalletServiceEvent event);
+    }
+
+    /**
+     * Unregister all event listeners and disconnect from the service.
+     */
+    void disconnect();
 
     /**
      * The manifest entry for the QuickAccessWalletService may also publish information about the
@@ -78,12 +138,12 @@
      * application.
      */
     @Nullable
-    Intent getWalletActivity();
+    Intent createWalletIntent();
 
     /**
      * The manifest entry for the {@link QuickAccessWalletService} may publish the activity that
      * hosts the settings
      */
     @Nullable
-    Intent getSettingsActivity();
+    Intent createWalletSettingsIntent();
 }
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 17c287f..37a8703 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -27,79 +28,225 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.widget.LockPatternUtils;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Queue;
 import java.util.UUID;
-import java.util.function.Consumer;
 
 /**
+ * Implements {@link QuickAccessWalletClient}. The client connects, performs requests, waits for
+ * responses, and disconnects automatically after a short period of time. The client may
  * @hide
  */
-@SuppressWarnings("AndroidJdkLibsChecker")
-class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Callback,
-        ServiceConnection {
+public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, ServiceConnection {
 
     private static final String TAG = "QAWalletSClient";
     private final Handler mHandler;
     private final Context mContext;
     private final Queue<ApiCaller> mRequestQueue;
-    private final Map<Consumer<WalletServiceEvent>, String> mEventListeners;
+    private final Map<WalletServiceEventListener, String> mEventListeners;
     private boolean mIsConnected;
+    /**
+     * Timeout for active service connections (1 minute)
+     */
+    private static final long SERVICE_CONNECTION_TIMEOUT_MS = 60 * 1000;
     @Nullable
     private IQuickAccessWalletService mService;
 
-
     @Nullable
     private final QuickAccessWalletServiceInfo mServiceInfo;
 
-    private static final int MSG_CONNECT = 1;
-    private static final int MSG_CONNECTED = 2;
-    private static final int MSG_EXECUTE = 3;
-    private static final int MSG_DISCONNECT = 4;
+    private static final int MSG_TIMEOUT_SERVICE = 5;
 
     QuickAccessWalletClientImpl(@NonNull Context context) {
         mContext = context.getApplicationContext();
         mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context);
-        mHandler = new Handler(Looper.getMainLooper(), this);
+        mHandler = new Handler(Looper.getMainLooper());
         mRequestQueue = new LinkedList<>();
         mEventListeners = new HashMap<>(1);
     }
 
     @Override
-    public boolean handleMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_CONNECT:
-                connectInternal();
-                break;
-            case MSG_CONNECTED:
-                onConnectedInternal((IQuickAccessWalletService) msg.obj);
-                break;
-            case MSG_EXECUTE:
-                executeInternal((ApiCaller) msg.obj);
-                break;
-            case MSG_DISCONNECT:
-                disconnectInternal();
-                break;
-            default:
-                Log.w(TAG, "Unknown what: " + msg.what);
-                return false;
+    public boolean isWalletServiceAvailable() {
+        boolean available = mServiceInfo != null;
+        Log.i(TAG, "isWalletServiceAvailable: " + available);
+        return available;
+    }
+
+    @Override
+    public boolean isWalletFeatureAvailable() {
+        int currentUser = ActivityManager.getCurrentUser();
+        return checkUserSetupComplete()
+                && checkSecureSetting(Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED)
+                && !new LockPatternUtils(mContext).isUserInLockdown(currentUser);
+    }
+
+    @Override
+    public boolean isWalletFeatureAvailableWhenDeviceLocked() {
+        return checkSecureSetting(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS)
+                && checkSecureSetting(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+    }
+
+    @Override
+    public void getWalletCards(
+            @NonNull GetWalletCardsRequest request,
+            @NonNull OnWalletCardsRetrievedCallback callback) {
+
+        Log.i(TAG, "getWalletCards");
+
+        if (!isWalletServiceAvailable()) {
+            callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null));
+            return;
         }
-        return true;
+
+        BaseCallbacks serviceCallback = new BaseCallbacks() {
+            @Override
+            public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
+                mHandler.post(() -> callback.onWalletCardsRetrieved(response));
+            }
+
+            @Override
+            public void onGetWalletCardsFailure(GetWalletCardsError error) {
+                mHandler.post(() -> callback.onWalletCardRetrievalError(error));
+            }
+        };
+
+        executeApiCall(new ApiCaller("onWalletCardsRequested") {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                service.onWalletCardsRequested(request, serviceCallback);
+            }
+
+            @Override
+            public void onApiError() {
+                serviceCallback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
+            }
+        });
+    }
+
+    @Override
+    public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
+        Log.i(TAG, "selectWalletCard");
+        if (!isWalletServiceAvailable()) {
+            return;
+        }
+        executeApiCall(new ApiCaller("onWalletCardSelected") {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                service.onWalletCardSelected(request);
+            }
+        });
+    }
+
+    @Override
+    public void notifyWalletDismissed() {
+        if (!isWalletServiceAvailable()) {
+            return;
+        }
+        Log.i(TAG, "notifyWalletDismissed");
+        executeApiCall(new ApiCaller("onWalletDismissed") {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                service.onWalletDismissed();
+            }
+        });
+    }
+
+    @Override
+    public void addWalletServiceEventListener(WalletServiceEventListener listener) {
+        if (!isWalletServiceAvailable()) {
+            return;
+        }
+        Log.i(TAG, "registerWalletServiceEventListener");
+        BaseCallbacks callback = new BaseCallbacks() {
+            @Override
+            public void onWalletServiceEvent(WalletServiceEvent event) {
+                Log.i(TAG, "onWalletServiceEvent");
+                mHandler.post(() -> listener.onWalletServiceEvent(event));
+            }
+        };
+
+        executeApiCall(new ApiCaller("registerListener") {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                String listenerId = UUID.randomUUID().toString();
+                WalletServiceEventListenerRequest request =
+                        new WalletServiceEventListenerRequest(listenerId);
+                mEventListeners.put(listener, listenerId);
+                service.registerWalletServiceEventListener(request, callback);
+            }
+        });
+    }
+
+    @Override
+    public void removeWalletServiceEventListener(WalletServiceEventListener listener) {
+        if (!isWalletServiceAvailable()) {
+            return;
+        }
+        Log.i(TAG, "unregisterWalletServiceEventListener");
+        executeApiCall(new ApiCaller("unregisterListener") {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                String listenerId = mEventListeners.remove(listener);
+                if (listenerId == null) {
+                    return;
+                }
+                WalletServiceEventListenerRequest request =
+                        new WalletServiceEventListenerRequest(listenerId);
+                service.unregisterWalletServiceEventListener(request);
+            }
+        });
+    }
+
+    @Override
+    public void disconnect() {
+        Log.i(TAG, "disconnect");
+        mHandler.post(() -> disconnectInternal(true));
+    }
+
+    @Override
+    @Nullable
+    public Intent createWalletIntent() {
+        if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
+            return null;
+        }
+        return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
+                .setComponent(
+                        new ComponentName(
+                                mServiceInfo.getComponentName().getPackageName(),
+                                mServiceInfo.getWalletActivity()));
+    }
+
+    @Override
+    @Nullable
+    public Intent createWalletSettingsIntent() {
+        if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
+            return null;
+        }
+        return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
+                .setComponent(
+                        new ComponentName(
+                                mServiceInfo.getComponentName().getPackageName(),
+                                mServiceInfo.getSettingsActivity()));
     }
 
     private void connect() {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
+        Log.i(TAG, "connect");
+        mHandler.post(this::connectInternal);
     }
 
     private void connectInternal() {
+        Log.i(TAG, "connectInternal");
         if (mServiceInfo == null) {
             Log.w(TAG, "Wallet service unavailable");
             return;
@@ -113,15 +260,18 @@
         intent.setComponent(mServiceInfo.getComponentName());
         int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
         mContext.bindService(intent, this, flags);
+        resetServiceConnectionTimeout();
     }
 
     private void onConnectedInternal(IQuickAccessWalletService service) {
+        Log.i(TAG, "onConnectedInternal");
         if (!mIsConnected) {
             Log.w(TAG, "onConnectInternal but connection closed");
             mService = null;
             return;
         }
         mService = service;
+        Log.i(TAG, "onConnectedInternal success: request queue size " + mRequestQueue.size());
         for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
             try {
                 apiCaller.performApiCall(mService);
@@ -135,15 +285,33 @@
         }
     }
 
-    private void disconnect() {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
+    /**
+     * Resets the idle timeout for this connection by removing any pending timeout messages and
+     * posting a new delayed message.
+     */
+    private void resetServiceConnectionTimeout() {
+        Log.i(TAG, "resetServiceConnectionTimeout");
+        mHandler.removeMessages(MSG_TIMEOUT_SERVICE);
+        mHandler.postDelayed(
+                () -> disconnectInternal(true),
+                MSG_TIMEOUT_SERVICE,
+                SERVICE_CONNECTION_TIMEOUT_MS);
     }
 
-    private void disconnectInternal() {
+    private void disconnectInternal(boolean clearEventListeners) {
+        Log.i(TAG, "disconnectInternal: " + clearEventListeners);
         if (!mIsConnected) {
             Log.w(TAG, "already disconnected");
             return;
         }
+        if (clearEventListeners && !mEventListeners.isEmpty()) {
+            Log.i(TAG, "disconnectInternal: clear event listeners");
+            for (WalletServiceEventListener listener : mEventListeners.keySet()) {
+                removeWalletServiceEventListener(listener);
+            }
+            mHandler.post(() -> disconnectInternal(false));
+            return;
+        }
         mIsConnected = false;
         mContext.unbindService(/*conn=*/this);
         mService = null;
@@ -151,181 +319,81 @@
         mRequestQueue.clear();
     }
 
-    private void execute(ApiCaller apiCaller) {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, apiCaller));
+    private void executeApiCall(ApiCaller apiCaller) {
+        Log.i(TAG, "execute: " + apiCaller.mDesc);
+        mHandler.post(() -> executeInternal(apiCaller));
     }
 
     private void executeInternal(ApiCaller apiCall) {
+        Log.i(TAG, "executeInternal: " + apiCall.mDesc);
         if (mIsConnected && mService != null) {
             try {
                 apiCall.performApiCall(mService);
+                Log.i(TAG, "executeInternal success: " + apiCall.mDesc);
+                resetServiceConnectionTimeout();
             } catch (RemoteException e) {
-                Log.w(TAG, "executeInternal error", e);
+                Log.w(TAG, "executeInternal error: " + apiCall.mDesc, e);
                 apiCall.onApiError();
                 disconnect();
             }
         } else {
+            Log.i(TAG, "executeInternal: queued" + apiCall.mDesc);
             mRequestQueue.add(apiCall);
             connect();
         }
     }
 
-    public boolean isWalletServiceAvailable() {
-        return mServiceInfo != null;
-    }
-
     private abstract static class ApiCaller {
+        private final String mDesc;
+
+        private ApiCaller(String desc) {
+            this.mDesc = desc;
+        }
+
         abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
 
         void onApiError() {
-            Log.w(TAG, "api error");
+            Log.w(TAG, "api error: " + mDesc);
         }
     }
 
-    public void getWalletCards(
-            @NonNull GetWalletCardsRequest request,
-            @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
-            @NonNull Consumer<GetWalletCardsError> onFailureListener) {
-
-        BaseCallbacks callback = new BaseCallbacks() {
-            @Override
-            public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
-                mHandler.post(() -> onSuccessListener.accept(response));
-            }
-
-            @Override
-            public void onGetWalletCardsFailure(GetWalletCardsError error) {
-                mHandler.post(() -> onFailureListener.accept(error));
-            }
-        };
-
-        execute(new ApiCaller() {
-            @Override
-            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
-                service.onWalletCardsRequested(request, callback);
-            }
-
-            @Override
-            public void onApiError() {
-                callback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
-            }
-        });
-    }
-
-    public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
-        execute(new ApiCaller() {
-            @Override
-            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
-                service.onWalletCardSelected(request);
-            }
-        });
-    }
-
-    public void notifyWalletDismissed() {
-        execute(new ApiCaller() {
-            @Override
-            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
-                service.onWalletDismissed();
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
-            }
-        });
-    }
-
-    @Override
-    public void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener) {
-
-        BaseCallbacks callback = new BaseCallbacks() {
-            @Override
-            public void onWalletServiceEvent(WalletServiceEvent event) {
-                Log.i(TAG, "onWalletServiceEvent");
-                mHandler.post(() -> listener.accept(event));
-            }
-        };
-
-        execute(new ApiCaller() {
-            @Override
-            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
-                String listenerId = UUID.randomUUID().toString();
-                WalletServiceEventListenerRequest request =
-                        new WalletServiceEventListenerRequest(listenerId);
-                mEventListeners.put(listener, listenerId);
-                service.registerWalletServiceEventListener(request, callback);
-            }
-        });
-    }
-
-    @Override
-    public void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener) {
-        execute(new ApiCaller() {
-            @Override
-            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
-                String listenerId = mEventListeners.get(listener);
-                if (listenerId == null) {
-                    return;
-                }
-                WalletServiceEventListenerRequest request =
-                        new WalletServiceEventListenerRequest(listenerId);
-                service.unregisterWalletServiceEventListener(request);
-            }
-        });
-    }
-
-    @Override
-    @Nullable
-    public Intent getWalletActivity() {
-        if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
-            return null;
-        }
-        return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
-                .setComponent(
-                        new ComponentName(
-                                mServiceInfo.getComponentName().getPackageName(),
-                                mServiceInfo.getWalletActivity()));
-    }
-
-    @Override
-    @Nullable
-    public Intent getSettingsActivity() {
-        if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
-            return null;
-        }
-        return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
-                .setComponent(
-                        new ComponentName(
-                                mServiceInfo.getComponentName().getPackageName(),
-                                mServiceInfo.getSettingsActivity()));
-    }
-
-    /**
-     * Connection to the {@link QuickAccessWalletService}
-     */
-
-
-    @Override
+    @Override // ServiceConnection
     public void onServiceConnected(ComponentName name, IBinder binder) {
+        Log.i(TAG, "onServiceConnected: " + name);
         IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, service));
+        mHandler.post(() -> onConnectedInternal(service));
     }
 
-    @Override
+    @Override // ServiceConnection
     public void onServiceDisconnected(ComponentName name) {
         // Do not disconnect, as we may later be re-connected
         Log.w(TAG, "onServiceDisconnected");
     }
 
-    @Override
+    @Override // ServiceConnection
     public void onBindingDied(ComponentName name) {
         // This is a recoverable error but the client will need to reconnect.
         Log.w(TAG, "onBindingDied");
         disconnect();
     }
 
-    @Override
+    @Override // ServiceConnection
     public void onNullBinding(ComponentName name) {
         Log.w(TAG, "onNullBinding");
         disconnect();
     }
 
+    private boolean checkSecureSetting(String name) {
+        return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) == 1;
+    }
+
+    private boolean checkUserSetupComplete() {
+        return Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0,
+                UserHandle.USER_CURRENT) == 1;
+    }
+
     private static class BaseCallbacks extends IQuickAccessWalletServiceCallbacks.Stub {
         public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
             throw new IllegalStateException();
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index d968405..aba5611 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -45,8 +45,9 @@
  *   android.permission.BIND_NFC_SERVICE permission.
  *   <li>The user explicitly selected the application as the default payment application in
  *   the Tap &amp; pay settings screen.
- *   <li>The application requires the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
- *   permission in its manifest.
+ *   <li>The QuickAccessWalletService requires that the binding application hold the
+ *   {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE} permission, which only the System
+ *   Service can hold.
  *   <li>The user explicitly enables it using Android Settings (the
  *       {@link Settings#ACTION_QUICK_ACCESS_WALLET_SETTINGS} intent can be used to launch it).
  * </ol>
@@ -73,6 +74,11 @@
  *   a new card is selected, the Android System will notify the service through
  *   {@link #onWalletCardSelected} and will provide the {@link WalletCard#getCardId() cardId} of the
  *   card that is now selected.
+ *   <li>If the user commences an NFC payment, the service may send a {@link WalletServiceEvent}
+ *   to the System indicating that the wallet application now needs to show the activity associated
+ *   with making a payment. Sending a {@link WalletServiceEvent} of type
+ *   {@link WalletServiceEvent#TYPE_NFC_PAYMENT_STARTED} should cause the quick access wallet UI
+ *   to be dismissed.
  *   <li>When the wallet is dismissed, the Android System will notify the service through
  *   {@link #onWalletDismissed}.
  * </ol>
@@ -110,6 +116,7 @@
  *     android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
  *     <intent-filter>
  *         <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+ *         <category android:name="android.intent.category.DEFAULT"/>
  *     </intent-filter>
  *     <meta-data android:name="android.quickaccesswallet"
  *          android:resource="@xml/quickaccesswallet_configuration" />;
@@ -166,29 +173,6 @@
             "android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
 
     /**
-     * Broadcast Action: Sent by the wallet application to dismiss the Quick Access Wallet.
-     * <p>
-     * The Quick Access Wallet may be shown in a system window on top of other Activities. If the
-     * user selects a payment card from the Quick Access Wallet and then holds their phone to an NFC
-     * terminal, the wallet application will need to show a payment Activity. But if the Quick
-     * Access Wallet is still being shown, it may obscure the payment Activity. To avoid this, the
-     * wallet application can send a broadcast to the Android System with this action to request
-     * that the Quick Access Wallet be dismissed.
-     * <p>
-     * This broadcast must use the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
-     * permission to ensure that it is only delivered to System UI. Furthermore, your application
-     * must require the {@code android.permission.DISMISS_QUICK_ACCESS_WALLET}
-     * <p>
-     * <pre class="prettyprint">
-     * context.sendBroadcast(
-     *     new Intent(ACTION_DISMISS_WALLET), Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE);
-     * </pre>
-     */
-    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_DISMISS_WALLET =
-            "android.service.quickaccesswallet.action.DISMISS_WALLET";
-
-    /**
      * Name under which a QuickAccessWalletService component publishes information about itself.
      * This meta-data should reference an XML resource containing a
      * <code>&lt;{@link
@@ -202,8 +186,20 @@
     public static final String SERVICE_META_DATA = "android.quickaccesswallet";
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    /**
+     * The service currently only supports one listener at a time. Multiple connections that
+     * register different listeners will clobber the listener. This field may only be accessed from
+     * the main thread.
+     */
     @Nullable
     private String mEventListenerId;
+
+    /**
+     * The service currently only supports one listener at a time. Multiple connections that
+     * register different listeners will clobber the listener. This field may only be accessed from
+     * the main thread.
+     */
     @Nullable
     private IQuickAccessWalletServiceCallbacks mEventListener;
 
@@ -240,7 +236,8 @@
     private void onWalletCardsRequestedInternal(
             GetWalletCardsRequest request,
             IQuickAccessWalletServiceCallbacks callback) {
-        onWalletCardsRequested(request, new GetWalletCardsCallback(callback, mHandler));
+        onWalletCardsRequested(request,
+                new GetWalletCardsCallbackImpl(request, callback, mHandler));
     }
 
     @Override
@@ -250,14 +247,14 @@
             // Binding to the QuickAccessWalletService is protected by the
             // android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE permission, which is defined in
             // R. Pre-R devices can have other side-loaded applications that claim this permission.
-            // This ensures that the service is only available when properly permission protected.
+            // Ensures that the service is only enabled when properly permission protected.
             Log.w(TAG, "Warning: binding on pre-R device");
         }
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mInterface.asBinder();
+        if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+            Log.w(TAG, "Wrong action");
+            return null;
         }
-        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
-        return null;
+        return mInterface.asBinder();
     }
 
     /**
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 8793f28..23173a8 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -77,8 +77,9 @@
         }
 
         if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) {
-            Log.w(TAG, String.format("QuickAccessWalletService from %s does not have permission %s",
-                    serviceInfo.packageName, Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
+            Log.w(TAG, String.format("%s.%s does not require permission %s",
+                    serviceInfo.packageName, serviceInfo.name,
+                    Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
             return null;
         }
 
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index c3b1a4b..e6ae0ab 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -174,22 +174,25 @@
          * @param cardId             The card id must be non-null and unique within the list of
          *                           cards returned. <b>Note:
          *                           </b> this card ID should <b>not</b> contain PII (Personally
-         *                           Identifiable Information, * such as username or email
-         *                           address).
+         *                           Identifiable Information, such as username or email address).
          * @param cardImage          The visual representation of the card. If the card image Icon
          *                           is a bitmap, it should have a width of {@link
          *                           GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
          *                           GetWalletCardsRequest#getCardHeightPx()}. If the card image
          *                           does not have these dimensions, it may appear distorted when it
-         *                           is scaled to fit these dimensions on screen.
+         *                           is scaled to fit these dimensions on screen. Bitmaps should be
+         *                           of type {@link android.graphics.Bitmap.Config#HARDWARE} for
+         *                           performance reasons.
          * @param contentDescription The content description of the card image. This field is
-         *                           required.
+         *                           required and may not be null or empty.
          *                           <b>Note: </b> this message should <b>not</b> contain PII
          *                           (Personally Identifiable Information, such as username or email
          *                           address).
          * @param pendingIntent      If the user performs a click on the card, this PendingIntent
          *                           will be sent. If the device is locked, the wallet will first
-         *                           request device unlock before sending the pending intent.
+         *                           request device unlock before sending the pending intent. It is
+         *                           recommended that the pending intent be immutable (use {@link
+         *                           PendingIntent#FLAG_IMMUTABLE}).
          */
         public Builder(@NonNull String cardId,
                 @NonNull Icon cardImage,
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e65bd9f..d273500 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -301,6 +301,13 @@
     public static final int LISTEN_USER_MOBILE_DATA_STATE                  = 0x00080000;
 
     /**
+     *  Listen for display info changed event.
+     *
+     *  @see #onDisplayInfoChanged
+     */
+    public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
+
+    /**
      *  Listen for changes to the phone capability.
      *
      *  @see #onPhoneCapabilityChanged
@@ -848,6 +855,21 @@
     }
 
     /**
+     * Callback invoked when the display info has changed on the registered subscription.
+     * <p> The {@link DisplayInfo} contains status information shown to the user based on
+     * carrier policy.
+     *
+     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+     *
+     * @param displayInfo The display information.
+     */
+    @RequiresPermission((android.Manifest.permission.READ_PHONE_STATE))
+    public void onDisplayInfoChanged(@NonNull DisplayInfo displayInfo) {
+        // default implementation empty
+    }
+
+    /**
      * Callback invoked when the current emergency number list has changed on the registered
      * subscription.
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
@@ -1226,6 +1248,15 @@
                             () -> psl.onUserMobileDataStateChanged(enabled)));
         }
 
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onDisplayInfoChanged(displayInfo)));
+        }
+
         public void onOemHookRawEvent(byte[] rawData) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4024db1..2c077bb 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -589,6 +589,24 @@
     }
 
     /**
+     * Notify display info changed.
+     *
+     * @param slotIndex The SIM slot index for which display info has changed. Can be
+     * derived from {@code subscriptionId} except when {@code subscriptionId} is invalid, such as
+     * when the device is in emergency-only mode.
+     * @param subscriptionId Subscription id for which display network info has changed.
+     * @param displayInfo The display info.
+     */
+    public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
+                                         @NonNull DisplayInfo displayInfo) {
+        try {
+            sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, displayInfo);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
      * Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}.
      *
      * @param subId for which ims call disconnect.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index afeb6c3..44e7ae6 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -79,6 +79,7 @@
 
         DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
         DEFAULT_FLAGS.put(SETTINGS_SCHEDULES_FLAG, "false");
+        DEFAULT_FLAGS.put("settings_contextual_home2", "false");
     }
 
     /**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f61217d..d1edf58 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -67,7 +67,7 @@
  * </ul>
  * </p><p>
  * A logical display does not necessarily represent a particular physical display device
- * such as the built-in screen or an external monitor.  The contents of a logical
+ * such as the internal display or an external display.  The contents of a logical
  * display may be presented on one or more physical displays according to the devices
  * that are currently attached and whether mirroring has been enabled.
  * </p>
@@ -104,8 +104,7 @@
     private int mCachedAppHeightCompat;
 
     /**
-     * The default Display id, which is the id of the built-in primary display
-     * assuming there is one.
+     * The default Display id, which is the id of the primary display assuming there is one.
      */
     public static final int DEFAULT_DISPLAY = 0;
 
@@ -188,7 +187,7 @@
      * Display flag: Indicates that the display is a presentation display.
      * <p>
      * This flag identifies secondary displays that are suitable for
-     * use as presentation displays such as HDMI or Wireless displays.  Applications
+     * use as presentation displays such as external or wireless displays.  Applications
      * may automatically project their content to presentation displays to provide
      * richer second screen experiences.
      * </p>
@@ -257,17 +256,17 @@
     public static final int TYPE_UNKNOWN = 0;
 
     /**
-     * Display type: Built-in display.
+     * Display type: Physical display connected through an internal port.
      * @hide
      */
-    public static final int TYPE_BUILT_IN = 1;
+    public static final int TYPE_INTERNAL = 1;
 
     /**
-     * Display type: HDMI display.
+     * Display type: Physical display connected through an external port.
      * @hide
      */
     @UnsupportedAppUsage
-    public static final int TYPE_HDMI = 2;
+    public static final int TYPE_EXTERNAL = 2;
 
     /**
      * Display type: WiFi display.
@@ -562,8 +561,8 @@
      * @return The display type.
      *
      * @see #TYPE_UNKNOWN
-     * @see #TYPE_BUILT_IN
-     * @see #TYPE_HDMI
+     * @see #TYPE_INTERNAL
+     * @see #TYPE_EXTERNAL
      * @see #TYPE_WIFI
      * @see #TYPE_OVERLAY
      * @see #TYPE_VIRTUAL
@@ -1251,10 +1250,10 @@
         switch (type) {
             case TYPE_UNKNOWN:
                 return "UNKNOWN";
-            case TYPE_BUILT_IN:
-                return "BUILT_IN";
-            case TYPE_HDMI:
-                return "HDMI";
+            case TYPE_INTERNAL:
+                return "INTERNAL";
+            case TYPE_EXTERNAL:
+                return "EXTERNAL";
             case TYPE_WIFI:
                 return "WIFI";
             case TYPE_OVERLAY:
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 37b9eb3..c62e934 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -90,13 +90,11 @@
 
     /**
      * The user has started a gesture (e.g. on the soft keyboard).
-     * @hide
      */
     public static final int GESTURE_START = 12;
 
     /**
      * The user has finished a gesture (e.g. on the soft keyboard).
-     * @hide
      */
     public static final int GESTURE_END = 13;
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2548068..9cd6050e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -882,6 +882,9 @@
         } else {
             hideDirectly(types);
         }
+        if (mViewRoot.mView == null) {
+            return;
+        }
         mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
         mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
             @Override
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 06cb519..31fdfb7 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -16,528 +16,52 @@
 
 package android.view;
 
-import android.animation.Animator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
 import android.graphics.CanvasProperty;
 import android.graphics.Paint;
-import android.graphics.RecordingCanvas;
-import android.graphics.RenderNode;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.SparseIntArray;
-
-import com.android.internal.util.VirtualRefBasePtr;
-import com.android.internal.view.animation.FallbackLUTInterpolator;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-
-import java.util.ArrayList;
 
 /**
  * @hide
  */
-public class RenderNodeAnimator extends Animator {
-    // Keep in sync with enum RenderProperty in Animator.h
-    public static final int TRANSLATION_X = 0;
-    public static final int TRANSLATION_Y = 1;
-    public static final int TRANSLATION_Z = 2;
-    public static final int SCALE_X = 3;
-    public static final int SCALE_Y = 4;
-    public static final int ROTATION = 5;
-    public static final int ROTATION_X = 6;
-    public static final int ROTATION_Y = 7;
-    public static final int X = 8;
-    public static final int Y = 9;
-    public static final int Z = 10;
-    public static final int ALPHA = 11;
-    // The last value in the enum, used for array size initialization
-    public static final int LAST_VALUE = ALPHA;
+public class RenderNodeAnimator extends android.graphics.animation.RenderNodeAnimator
+        implements android.graphics.animation.RenderNodeAnimator.ViewListener {
 
-    // Keep in sync with enum PaintFields in Animator.h
-    public static final int PAINT_STROKE_WIDTH = 0;
-
-    /**
-     * Field for the Paint alpha channel, which should be specified as a value
-     * between 0 and 255.
-     */
-    public static final int PAINT_ALPHA = 1;
-
-    // ViewPropertyAnimator uses a mask for its values, we need to remap them
-    // to the enum values here. RenderPropertyAnimator can't use the mask values
-    // directly as internally it uses a lookup table so it needs the values to
-    // be sequential starting from 0
-    private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
-        put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
-        put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
-        put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
-        put(ViewPropertyAnimator.SCALE_X, SCALE_X);
-        put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
-        put(ViewPropertyAnimator.ROTATION, ROTATION);
-        put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
-        put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
-        put(ViewPropertyAnimator.X, X);
-        put(ViewPropertyAnimator.Y, Y);
-        put(ViewPropertyAnimator.Z, Z);
-        put(ViewPropertyAnimator.ALPHA, ALPHA);
-    }};
-
-    private VirtualRefBasePtr mNativePtr;
-
-    private Handler mHandler;
-    private RenderNode mTarget;
     private View mViewTarget;
-    private int mRenderProperty = -1;
-    private float mFinalValue;
-    private TimeInterpolator mInterpolator;
 
-    private static final int STATE_PREPARE = 0;
-    private static final int STATE_DELAYED = 1;
-    private static final int STATE_RUNNING = 2;
-    private static final int STATE_FINISHED = 3;
-    private int mState = STATE_PREPARE;
-
-    private long mUnscaledDuration = 300;
-    private long mUnscaledStartDelay = 0;
-    // If this is true, we will run any start delays on the UI thread. This is
-    // the safe default, and is necessary to ensure start listeners fire at
-    // the correct time. Animators created by RippleDrawable (the
-    // CanvasProperty<> ones) do not have this expectation, and as such will
-    // set this to false so that the renderthread handles the startdelay instead
-    private final boolean mUiThreadHandlesDelay;
-    private long mStartDelay = 0;
-    private long mStartTime;
-
-    @UnsupportedAppUsage
-    public static int mapViewPropertyToRenderProperty(int viewProperty) {
-        return sViewPropertyAnimatorMap.get(viewProperty);
-    }
-
-    @UnsupportedAppUsage
     public RenderNodeAnimator(int property, float finalValue) {
-        mRenderProperty = property;
-        mFinalValue = finalValue;
-        mUiThreadHandlesDelay = true;
-        init(nCreateAnimator(property, finalValue));
+        super(property, finalValue);
     }
 
-    @UnsupportedAppUsage
     public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
-        init(nCreateCanvasPropertyFloatAnimator(
-                property.getNativeContainer(), finalValue));
-        mUiThreadHandlesDelay = false;
+        super(property, finalValue);
     }
 
-    /**
-     * Creates a new render node animator for a field on a Paint property.
-     *
-     * @param property The paint property to target
-     * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
-     *            {@link #PAINT_STROKE_WIDTH}
-     * @param finalValue The target value for the property
-     */
-    @UnsupportedAppUsage
     public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
-        init(nCreateCanvasPropertyPaintAnimator(
-                property.getNativeContainer(), paintField, finalValue));
-        mUiThreadHandlesDelay = false;
+        super(property, paintField, finalValue);
     }
 
     public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
-        init(nCreateRevealAnimator(x, y, startRadius, endRadius));
-        mUiThreadHandlesDelay = true;
-    }
-
-    private void init(long ptr) {
-        mNativePtr = new VirtualRefBasePtr(ptr);
-    }
-
-    private void checkMutable() {
-        if (mState != STATE_PREPARE) {
-            throw new IllegalStateException("Animator has already started, cannot change it now!");
-        }
-        if (mNativePtr == null) {
-            throw new IllegalStateException("Animator's target has been destroyed "
-                    + "(trying to modify an animation after activity destroy?)");
-        }
-    }
-
-    static boolean isNativeInterpolator(TimeInterpolator interpolator) {
-        return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
-    }
-
-    private void applyInterpolator() {
-        if (mInterpolator == null || mNativePtr == null) return;
-
-        long ni;
-        if (isNativeInterpolator(mInterpolator)) {
-            ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
-        } else {
-            long duration = nGetDuration(mNativePtr.get());
-            ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
-        }
-        nSetInterpolator(mNativePtr.get(), ni);
+        super(x, y, startRadius, endRadius);
     }
 
     @Override
-    public void start() {
-        if (mTarget == null) {
-            throw new IllegalStateException("Missing target!");
-        }
-
-        if (mState != STATE_PREPARE) {
-            throw new IllegalStateException("Already started!");
-        }
-
-        mState = STATE_DELAYED;
-        if (mHandler == null) {
-            mHandler = new Handler(true);
-        }
-        applyInterpolator();
-
-        if (mNativePtr == null) {
-            // It's dead, immediately cancel
-            cancel();
-        } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
-            nSetStartDelay(mNativePtr.get(), mStartDelay);
-            doStart();
-        } else {
-            getHelper().addDelayedAnimation(this);
-        }
-    }
-
-    private void doStart() {
+    public void onAlphaAnimationStart(float finalAlpha) {
         // Alpha is a special snowflake that has the canonical value stored
         // in mTransformationInfo instead of in RenderNode, so we need to update
         // it with the final value here.
-        if (mRenderProperty == RenderNodeAnimator.ALPHA) {
-            mViewTarget.ensureTransformationInfo();
-            mViewTarget.setAlphaInternal(mFinalValue);
-        }
-
-        moveToRunningState();
-
-        if (mViewTarget != null) {
-            // Kick off a frame to start the process
-            mViewTarget.invalidateViewProperty(true, false);
-        }
-    }
-
-    private void moveToRunningState() {
-        mState = STATE_RUNNING;
-        if (mNativePtr != null) {
-            nStart(mNativePtr.get());
-        }
-        notifyStartListeners();
-    }
-
-    private void notifyStartListeners() {
-        final ArrayList<AnimatorListener> listeners = cloneListeners();
-        final int numListeners = listeners == null ? 0 : listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            listeners.get(i).onAnimationStart(this);
-        }
+        mViewTarget.ensureTransformationInfo();
+        mViewTarget.setAlphaInternal(finalAlpha);
     }
 
     @Override
-    public void cancel() {
-        if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
-            if (mState == STATE_DELAYED) {
-                getHelper().removeDelayedAnimation(this);
-                moveToRunningState();
-            }
-
-            final ArrayList<AnimatorListener> listeners = cloneListeners();
-            final int numListeners = listeners == null ? 0 : listeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                listeners.get(i).onAnimationCancel(this);
-            }
-
-            end();
-        }
-    }
-
-    @Override
-    public void end() {
-        if (mState != STATE_FINISHED) {
-            if (mState < STATE_RUNNING) {
-                getHelper().removeDelayedAnimation(this);
-                doStart();
-            }
-            if (mNativePtr != null) {
-                nEnd(mNativePtr.get());
-                if (mViewTarget != null) {
-                    // Kick off a frame to flush the state change
-                    mViewTarget.invalidateViewProperty(true, false);
-                }
-            } else {
-                // It's already dead, jump to onFinish
-                onFinished();
-            }
-        }
-    }
-
-    @Override
-    public void pause() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void resume() {
-        throw new UnsupportedOperationException();
+    public void invalidateParent(boolean forceRedraw) {
+        mViewTarget.invalidateViewProperty(true, false);
     }
 
     /** @hide */
-    @UnsupportedAppUsage
-    public void setTarget(View view) {
+    public void setTarget(@NonNull View view) {
         mViewTarget = view;
+        setViewListener(this);
         setTarget(mViewTarget.mRenderNode);
     }
-
-    /** Sets the animation target to the owning view of the RecordingCanvas */
-    public void setTarget(RecordingCanvas canvas) {
-        setTarget(canvas.mNode);
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public void setTarget(DisplayListCanvas canvas) {
-        setTarget((RecordingCanvas) canvas);
-    }
-
-    private void setTarget(RenderNode node) {
-        checkMutable();
-        if (mTarget != null) {
-            throw new IllegalStateException("Target already set!");
-        }
-        nSetListener(mNativePtr.get(), this);
-        mTarget = node;
-        mTarget.addAnimator(this);
-    }
-
-    @UnsupportedAppUsage
-    public void setStartValue(float startValue) {
-        checkMutable();
-        nSetStartValue(mNativePtr.get(), startValue);
-    }
-
-    @Override
-    public void setStartDelay(long startDelay) {
-        checkMutable();
-        if (startDelay < 0) {
-            throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
-        }
-        mUnscaledStartDelay = startDelay;
-        mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
-    }
-
-    @Override
-    public long getStartDelay() {
-        return mUnscaledStartDelay;
-    }
-
-    @UnsupportedAppUsage
-    @Override
-    public RenderNodeAnimator setDuration(long duration) {
-        checkMutable();
-        if (duration < 0) {
-            throw new IllegalArgumentException("duration must be positive; " + duration);
-        }
-        mUnscaledDuration = duration;
-        nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
-        return this;
-    }
-
-    @Override
-    public long getDuration() {
-        return mUnscaledDuration;
-    }
-
-    @Override
-    public long getTotalDuration() {
-        return mUnscaledDuration + mUnscaledStartDelay;
-    }
-
-    @Override
-    public boolean isRunning() {
-        return mState == STATE_DELAYED || mState == STATE_RUNNING;
-    }
-
-    @Override
-    public boolean isStarted() {
-        return mState != STATE_PREPARE;
-    }
-
-    @Override
-    public void setInterpolator(TimeInterpolator interpolator) {
-        checkMutable();
-        mInterpolator = interpolator;
-    }
-
-    @Override
-    public TimeInterpolator getInterpolator() {
-        return mInterpolator;
-    }
-
-    protected void onFinished() {
-        if (mState == STATE_PREPARE) {
-            // Unlikely but possible, the native side has been destroyed
-            // before we have started.
-            releaseNativePtr();
-            return;
-        }
-        if (mState == STATE_DELAYED) {
-            getHelper().removeDelayedAnimation(this);
-            notifyStartListeners();
-        }
-        mState = STATE_FINISHED;
-
-        final ArrayList<AnimatorListener> listeners = cloneListeners();
-        final int numListeners = listeners == null ? 0 : listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            listeners.get(i).onAnimationEnd(this);
-        }
-
-        // Release the native object, as it has a global reference to us. This
-        // breaks the cyclic reference chain, and allows this object to be
-        // GC'd
-        releaseNativePtr();
-    }
-
-    private void releaseNativePtr() {
-        if (mNativePtr != null) {
-            mNativePtr.release();
-            mNativePtr = null;
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private ArrayList<AnimatorListener> cloneListeners() {
-        ArrayList<AnimatorListener> listeners = getListeners();
-        if (listeners != null) {
-            listeners = (ArrayList<AnimatorListener>) listeners.clone();
-        }
-        return listeners;
-    }
-
-    public long getNativeAnimator() {
-        return mNativePtr.get();
-    }
-
-    /**
-     * @return true if the animator was started, false if still delayed
-     */
-    private boolean processDelayed(long frameTimeMs) {
-        if (mStartTime == 0) {
-            mStartTime = frameTimeMs;
-        } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
-            doStart();
-            return true;
-        }
-        return false;
-    }
-
-    private static DelayedAnimationHelper getHelper() {
-        DelayedAnimationHelper helper = sAnimationHelper.get();
-        if (helper == null) {
-            helper = new DelayedAnimationHelper();
-            sAnimationHelper.set(helper);
-        }
-        return helper;
-    }
-
-    private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
-            new ThreadLocal<DelayedAnimationHelper>();
-
-    private static class DelayedAnimationHelper implements Runnable {
-
-        private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
-        private final Choreographer mChoreographer;
-        private boolean mCallbackScheduled;
-
-        public DelayedAnimationHelper() {
-            mChoreographer = Choreographer.getInstance();
-        }
-
-        public void addDelayedAnimation(RenderNodeAnimator animator) {
-            mDelayedAnims.add(animator);
-            scheduleCallback();
-        }
-
-        public void removeDelayedAnimation(RenderNodeAnimator animator) {
-            mDelayedAnims.remove(animator);
-        }
-
-        private void scheduleCallback() {
-            if (!mCallbackScheduled) {
-                mCallbackScheduled = true;
-                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
-            }
-        }
-
-        @Override
-        public void run() {
-            long frameTimeMs = mChoreographer.getFrameTime();
-            mCallbackScheduled = false;
-
-            int end = 0;
-            for (int i = 0; i < mDelayedAnims.size(); i++) {
-                RenderNodeAnimator animator = mDelayedAnims.get(i);
-                if (!animator.processDelayed(frameTimeMs)) {
-                    if (end != i) {
-                        mDelayedAnims.set(end, animator);
-                    }
-                    end++;
-                }
-            }
-            while (mDelayedAnims.size() > end) {
-                mDelayedAnims.remove(mDelayedAnims.size() - 1);
-            }
-
-            if (mDelayedAnims.size() > 0) {
-                scheduleCallback();
-            }
-        }
-    }
-
-    // Called by native
-    @UnsupportedAppUsage
-    private static void callOnFinished(RenderNodeAnimator animator) {
-        if (animator.mHandler != null) {
-            animator.mHandler.post(animator::onFinished);
-        } else {
-            new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
-        }
-    }
-
-    @Override
-    public Animator clone() {
-        throw new IllegalStateException("Cannot clone this animator");
-    }
-
-    @Override
-    public void setAllowRunningAsynchronously(boolean mayRunAsync) {
-        checkMutable();
-        nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
-    }
-
-    private static native long nCreateAnimator(int property, float finalValue);
-    private static native long nCreateCanvasPropertyFloatAnimator(
-            long canvasProperty, float finalValue);
-    private static native long nCreateCanvasPropertyPaintAnimator(
-            long canvasProperty, int paintField, float finalValue);
-    private static native long nCreateRevealAnimator(
-            int x, int y, float startRadius, float endRadius);
-
-    private static native void nSetStartValue(long nativePtr, float startValue);
-    private static native void nSetDuration(long nativePtr, long duration);
-    private static native long nGetDuration(long nativePtr);
-    private static native void nSetStartDelay(long nativePtr, long startDelay);
-    private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
-    private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
-    private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
-
-    private static native void nStart(long animPtr);
-    private static native void nEnd(long animPtr);
 }
diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java
deleted file mode 100644
index d222e07..0000000
--- a/core/java/android/view/RenderNodeAnimatorSetHelper.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.view;
-
-import android.animation.TimeInterpolator;
-import android.graphics.RecordingCanvas;
-import android.graphics.RenderNode;
-
-import com.android.internal.view.animation.FallbackLUTInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
-/**
- * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet
- * that are internal or package private to android.view package.
- *
- * @hide
- */
-public class RenderNodeAnimatorSetHelper {
-
-    /** checkstyle @hide */
-    public static RenderNode getTarget(RecordingCanvas recordingCanvas) {
-        return recordingCanvas.mNode;
-    }
-
-    /** checkstyle @hide */
-    public static long createNativeInterpolator(TimeInterpolator interpolator, long
-            duration) {
-        if (interpolator == null) {
-            // create LinearInterpolator
-            return NativeInterpolatorFactoryHelper.createLinearInterpolator();
-        } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
-            return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator();
-        } else {
-            return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
-        }
-    }
-
-}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index fe9e36e..cf48c52 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -321,14 +321,12 @@
     public static final int FX_SURFACE_NORMAL   = 0x00000000;
 
     /**
-     * Surface creation flag: Creates a Dim surface.
-     * Everything behind this surface is dimmed by the amount specified
-     * in {@link Transaction#setAlpha(SurfaceControl, float)}.  It is an error to lock a Dim
-     * surface, since it doesn't have a backing store.
+     * Surface creation flag: Creates a effect surface which
+     * represents a solid color and or shadows.
      *
      * @hide
      */
-    public static final int FX_SURFACE_DIM = 0x00020000;
+    public static final int FX_SURFACE_EFFECT = 0x00020000;
 
     /**
      * Surface creation flag: Creates a container surface.
@@ -739,11 +737,11 @@
          */
         public Builder setColorLayer() {
             unsetBufferSize();
-            return setFlags(FX_SURFACE_DIM, FX_SURFACE_MASK);
+            return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK);
         }
 
         private boolean isColorLayerSet() {
-            return  (mFlags & FX_SURFACE_DIM) == FX_SURFACE_DIM;
+            return  (mFlags & FX_SURFACE_EFFECT) == FX_SURFACE_EFFECT;
         }
 
         /**
@@ -1285,12 +1283,15 @@
      * @hide
      */
     public static final class DisplayInfo {
+        public boolean isInternal;
         public float density;
         public boolean secure;
 
         @Override
         public String toString() {
-            return "DisplayInfo{density=" + density + ", secure=" + secure + "}";
+            return "DisplayInfo{isInternal=" + isInternal
+                    + ", density=" + density
+                    + ", secure=" + secure + "}";
         }
     }
 
@@ -2075,6 +2076,7 @@
 
         private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
         Runnable mFreeNativeResources;
+        private static final float[] INVALID_COLOR = {-1, -1, -1};
 
         /**
          * @hide
@@ -2530,8 +2532,9 @@
         }
 
         /**
-         * Sets a color for the Surface.
-         * @param color A float array with three values to represent r, g, b in range [0..1]
+         * Fills the surface with the specified color.
+         * @param color A float array with three values to represent r, g, b in range [0..1]. An
+         * invalid color will remove the color fill.
          * @hide
          */
         @UnsupportedAppUsage
@@ -2542,6 +2545,16 @@
         }
 
         /**
+         * Removes color fill.
+        * @hide
+        */
+        public Transaction unsetColor(SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
+            return this;
+        }
+
+        /**
          * Sets the security of the surface.  Setting the flag is equivalent to creating the
          * Surface with the {@link #SECURE} flag.
          * @hide
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index bf84819..680a878 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
 
 /**
  * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
@@ -40,6 +41,7 @@
     private WindowlessWindowManager mWm;
 
     private SurfaceControl mSurfaceControl;
+    private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
 
     /**
      * Package encapsulating a Surface hierarchy which contains interactive view
@@ -49,15 +51,18 @@
      */
     public static final class SurfacePackage implements Parcelable {
         private final SurfaceControl mSurfaceControl;
-        // TODO: Accessibility ID goes here
+        private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
 
-        SurfacePackage(SurfaceControl sc) {
+        SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection) {
             mSurfaceControl = sc;
+            mAccessibilityEmbeddedConnection = connection;
         }
 
         private SurfacePackage(Parcel in) {
             mSurfaceControl = new SurfaceControl();
             mSurfaceControl.readFromParcel(in);
+            mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
+                    in.readStrongBinder());
         }
 
         /**
@@ -69,6 +74,16 @@
             return mSurfaceControl;
         }
 
+        /**
+         * Gets an accessibility embedded connection interface for this SurfaceControlViewHost.
+         *
+         * @return {@link IAccessibilityEmbeddedConnection} interface.
+         * @hide
+         */
+        public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
+            return mAccessibilityEmbeddedConnection;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -77,6 +92,7 @@
         @Override
         public void writeToParcel(@NonNull Parcel out, int flags) {
             mSurfaceControl.writeToParcel(out, flags);
+            out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
         }
 
         public static final @NonNull Creator<SurfacePackage> CREATOR
@@ -95,6 +111,7 @@
             @NonNull WindowlessWindowManager wwm) {
         mWm = wwm;
         mViewRoot = new ViewRootImpl(c, d, mWm);
+        mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
     }
 
     /**
@@ -118,6 +135,7 @@
         mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
                 mSurfaceControl, hostToken);
         mViewRoot = new ViewRootImpl(context, display, mWm);
+        mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
     }
 
     /**
@@ -128,8 +146,8 @@
      * are linked.
      */
     public @Nullable SurfacePackage getSurfacePackage() {
-        if (mSurfaceControl != null) {
-            return new SurfacePackage(mSurfaceControl);
+        if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
+            return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection);
         } else {
             return null;
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index d5ed36b..47ffd3e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -28,6 +28,7 @@
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
@@ -38,12 +39,14 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceControlViewHost;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
 
 import com.android.internal.view.SurfaceCallbackHelper;
 
@@ -203,8 +206,12 @@
     private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
     private int mParentSurfaceGenerationId;
 
-    // The token of embedded windowless view hierarchy.
-    private IBinder mEmbeddedViewHierarchy;
+    private RemoteAccessibilityEmbeddedConnection mRemoteAccessibilityEmbeddedConnection;
+
+    private final Matrix mScreenMatrixForEmbeddedHierarchy = new Matrix();
+    private final Matrix mTmpMatrix = new Matrix();
+    private final float[] mMatrixValues = new float[9];
+
     SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
     public SurfaceView(Context context) {
@@ -923,6 +930,7 @@
                     }
 
                     mTmpTransaction.apply();
+                    updateScreenMatrixForEmbeddedHierarchy();
 
                     if (sizeChanged || creating) {
                         redrawNeeded = true;
@@ -1510,6 +1518,7 @@
     @Override
     public void surfaceDestroyed() {
         setWindowStopped(true);
+        setRemoteAccessibilityEmbeddedConnection(null, null);
     }
 
     /**
@@ -1568,31 +1577,133 @@
 
     private void reparentSurfacePackage(SurfaceControl.Transaction t,
             SurfaceControlViewHost.SurfacePackage p) {
-        // TODO: Link accessibility IDs here.
+        initEmbeddedHierarchyForAccessibility(p);
         final SurfaceControl sc = p.getSurfaceControl();
         t.reparent(sc, mSurfaceControl).show(sc);
     }
 
-    /**
-     * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
-     * hierarchy.
-     *
-     * @param token IBinder token.
-     * @hide
-     */
-    public void setEmbeddedViewHierarchy(IBinder token) {
-        mEmbeddedViewHierarchy = token;
-    }
-
     /** @hide */
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
-        if (mEmbeddedViewHierarchy == null) {
+        final RemoteAccessibilityEmbeddedConnection wrapper =
+                getRemoteAccessibilityEmbeddedConnection();
+        if (wrapper == null) {
             return;
         }
         // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
         // leashed child would return the root node in the embedded hierarchy
-        info.addChild(mEmbeddedViewHierarchy);
+        info.addChild(wrapper.getLeashToken());
+    }
+
+    private void initEmbeddedHierarchyForAccessibility(SurfaceControlViewHost.SurfacePackage p) {
+        final IAccessibilityEmbeddedConnection connection = p.getAccessibilityEmbeddedConnection();
+        final RemoteAccessibilityEmbeddedConnection wrapper =
+                getRemoteAccessibilityEmbeddedConnection();
+
+        // Do nothing if package is embedding the same view hierarchy.
+        if (wrapper != null && wrapper.getConnection().equals(connection)) {
+            return;
+        }
+
+        // If this SurfaceView embeds a different view hierarchy, unlink the previous one first.
+        setRemoteAccessibilityEmbeddedConnection(null, null);
+
+        try {
+            final IBinder leashToken = connection.associateEmbeddedHierarchy(
+                    getViewRootImpl().mLeashToken, getAccessibilityViewId());
+            setRemoteAccessibilityEmbeddedConnection(connection, leashToken);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error while associateEmbeddedHierarchy " + e);
+        }
+        updateScreenMatrixForEmbeddedHierarchy();
+    }
+
+    private void setRemoteAccessibilityEmbeddedConnection(
+            IAccessibilityEmbeddedConnection connection, IBinder leashToken) {
+        try {
+            if (mRemoteAccessibilityEmbeddedConnection != null) {
+                mRemoteAccessibilityEmbeddedConnection.getConnection()
+                        .disassociateEmbeddedHierarchy();
+                mRemoteAccessibilityEmbeddedConnection.unlinkToDeath();
+                mRemoteAccessibilityEmbeddedConnection = null;
+            }
+            if (connection != null && leashToken != null) {
+                mRemoteAccessibilityEmbeddedConnection =
+                        new RemoteAccessibilityEmbeddedConnection(connection, leashToken);
+                mRemoteAccessibilityEmbeddedConnection.linkToDeath();
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error while setRemoteEmbeddedConnection " + e);
+        }
+    }
+
+    private RemoteAccessibilityEmbeddedConnection getRemoteAccessibilityEmbeddedConnection() {
+        return mRemoteAccessibilityEmbeddedConnection;
+    }
+
+    private void updateScreenMatrixForEmbeddedHierarchy() {
+        mTmpMatrix.reset();
+        mTmpMatrix.setTranslate(mScreenRect.left, mScreenRect.top);
+        mTmpMatrix.postScale(mScreenRect.width() / (float) mSurfaceWidth,
+                mScreenRect.height() / (float) mSurfaceHeight);
+
+        // If the screen matrix is identity or doesn't change, do nothing.
+        if (mTmpMatrix.isIdentity() || mTmpMatrix.equals(mScreenMatrixForEmbeddedHierarchy)) {
+            return;
+        }
+
+        try {
+            final RemoteAccessibilityEmbeddedConnection wrapper =
+                    getRemoteAccessibilityEmbeddedConnection();
+            if (wrapper == null) {
+                return;
+            }
+            mTmpMatrix.getValues(mMatrixValues);
+            wrapper.getConnection().setScreenMatrix(mMatrixValues);
+            mScreenMatrixForEmbeddedHierarchy.set(mTmpMatrix);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error while setScreenMatrix " + e);
+        }
+    }
+
+    /**
+     * Wrapper of accessibility embedded connection for embedded view hierarchy.
+     */
+    private final class RemoteAccessibilityEmbeddedConnection implements IBinder.DeathRecipient {
+        private final IAccessibilityEmbeddedConnection mConnection;
+        private final IBinder mLeashToken;
+
+        RemoteAccessibilityEmbeddedConnection(IAccessibilityEmbeddedConnection connection,
+                IBinder leashToken) {
+            mConnection = connection;
+            mLeashToken = leashToken;
+        }
+
+        IAccessibilityEmbeddedConnection getConnection() {
+            return mConnection;
+        }
+
+        IBinder getLeashToken() {
+            return mLeashToken;
+        }
+
+        void linkToDeath() throws RemoteException {
+            mConnection.asBinder().linkToDeath(this, 0);
+        }
+
+        void unlinkToDeath() {
+            mConnection.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            unlinkToDeath();
+            runOnUiThread(() -> {
+                if (mRemoteAccessibilityEmbeddedConnection == this) {
+                    mRemoteAccessibilityEmbeddedConnection = null;
+                }
+            });
+        }
     }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5964e12..688517d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -223,6 +223,13 @@
      * @see #USE_NEW_INSETS_PROPERTY
      * @hide
      */
+    public static int sNewInsetsMode =
+            SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
+
+    /**
+     * @see #USE_NEW_INSETS_PROPERTY
+     * @hide
+     */
     public static final int NEW_INSETS_MODE_NONE = 0;
 
     /**
@@ -238,13 +245,6 @@
     public static final int NEW_INSETS_MODE_FULL = 2;
 
     /**
-     * @see #USE_NEW_INSETS_PROPERTY
-     * @hide
-     */
-    public static int sNewInsetsMode =
-            SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME);
-
-    /**
      * Set this system property to true to force the view hierarchy to render
      * at 60 Hz. This can be used to measure the potential framerate.
      */
@@ -655,7 +655,7 @@
 
     private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
 
-    private IAccessibilityEmbeddedConnection mEmbeddedConnection;
+    private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
 
     static final class SystemUiVisibilityInfo {
         int seq;
@@ -1705,12 +1705,13 @@
     }
 
     Surface getOrCreateBLASTSurface(int width, int height) {
-        if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
+        if (mSurfaceControl == null
+                || !mSurfaceControl.isValid()
+                || mBlastSurfaceControl == null
+                || !mBlastSurfaceControl.isValid()) {
             return null;
         }
-        if ((mBlastSurfaceControl != null)
-                && (mBlastBufferQueue == null)
-                && mBlastSurfaceControl.isValid()) {
+        if (mBlastBufferQueue == null) {
             mBlastBufferQueue = new BLASTBufferQueue(
                 mBlastSurfaceControl, width, height);
         }
@@ -9370,11 +9371,12 @@
      * Gets an accessibility embedded connection interface for this ViewRootImpl.
      * @hide
      */
-    public IAccessibilityEmbeddedConnection getEmbeddedConnection() {
-        if (mEmbeddedConnection == null) {
-            mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this);
+    public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
+        if (mAccessibilityEmbeddedConnection == null) {
+            mAccessibilityEmbeddedConnection = new AccessibilityEmbeddedConnection(
+                    ViewRootImpl.this);
         }
-        return mEmbeddedConnection;
+        return mAccessibilityEmbeddedConnection;
     }
 
     private class SendWindowContentChangedAccessibilityEvent implements Runnable {
diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
index 21d5a5b..a2bbc5c 100644
--- a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -17,19 +17,18 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
 /**
  * An interpolator where the rate of change starts and ends slowly but
  * accelerates through the middle.
  */
 @HasNativeInterpolator
 public class AccelerateDecelerateInterpolator extends BaseInterpolator
-        implements NativeInterpolatorFactory {
+        implements NativeInterpolator {
     public AccelerateDecelerateInterpolator() {
     }
 
@@ -44,6 +43,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
+        return NativeInterpolatorFactory.createAccelerateDecelerateInterpolator();
     }
 }
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
index 6c8d7b1..9d4cd32 100644
--- a/core/java/android/view/animation/AccelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -20,12 +20,12 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
 import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * An interpolator where the rate of change starts out slowly and
@@ -33,7 +33,7 @@
  *
  */
 @HasNativeInterpolator
-public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
     private final float mFactor;
     private final double mDoubleFactor;
 
@@ -85,6 +85,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
+        return NativeInterpolatorFactory.createAccelerateInterpolator(mFactor);
     }
 }
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index 7a837c3..d146394 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -20,18 +20,18 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
 import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * An interpolator where the change starts backward then flings forward.
  */
 @HasNativeInterpolator
-public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolator {
     private final float mTension;
 
     public AnticipateInterpolator() {
@@ -73,6 +73,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createAnticipateInterpolator(mTension);
+        return NativeInterpolatorFactory.createAnticipateInterpolator(mTension);
     }
 }
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 9a75134..4d6a390 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -24,11 +24,11 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * An interpolator where the change starts backward then flings forward and overshoots
@@ -36,7 +36,7 @@
  */
 @HasNativeInterpolator
 public class AnticipateOvershootInterpolator extends BaseInterpolator
-        implements NativeInterpolatorFactory {
+        implements NativeInterpolator {
     private final float mTension;
 
     public AnticipateOvershootInterpolator() {
@@ -103,6 +103,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createAnticipateOvershootInterpolator(mTension);
+        return NativeInterpolatorFactory.createAnticipateOvershootInterpolator(mTension);
     }
 }
diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java
index 909eaa4..d3f6a3f 100644
--- a/core/java/android/view/animation/BounceInterpolator.java
+++ b/core/java/android/view/animation/BounceInterpolator.java
@@ -17,17 +17,16 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
 /**
  * An interpolator where the change bounces at the end.
  */
 @HasNativeInterpolator
-public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class BounceInterpolator extends BaseInterpolator implements NativeInterpolator {
     public BounceInterpolator() {
     }
 
@@ -56,6 +55,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createBounceInterpolator();
+        return NativeInterpolatorFactory.createBounceInterpolator();
     }
 }
\ No newline at end of file
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index 72d64a1..6b1a80a 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -20,12 +20,12 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
 import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * Repeats the animation for a specified number of cycles. The
@@ -33,7 +33,7 @@
  *
  */
 @HasNativeInterpolator
-public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolator {
     public CycleInterpolator(float cycles) {
         mCycles = cycles;
     }
@@ -65,6 +65,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createCycleInterpolator(mCycles);
+        return NativeInterpolatorFactory.createCycleInterpolator(mCycles);
     }
 }
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 2d1249d..2d2f770 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -20,12 +20,12 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
 import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * An interpolator where the rate of change starts out quickly and
@@ -33,7 +33,7 @@
  *
  */
 @HasNativeInterpolator
-public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
     public DecelerateInterpolator() {
     }
 
@@ -81,6 +81,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createDecelerateInterpolator(mFactor);
+        return NativeInterpolatorFactory.createDecelerateInterpolator(mFactor);
     }
 }
diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java
index 2a047b4..f6a820c 100644
--- a/core/java/android/view/animation/LinearInterpolator.java
+++ b/core/java/android/view/animation/LinearInterpolator.java
@@ -17,17 +17,16 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
 /**
  * An interpolator where the rate of change is constant
  */
 @HasNativeInterpolator
-public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {
 
     public LinearInterpolator() {
     }
@@ -42,6 +41,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+        return NativeInterpolatorFactory.createLinearInterpolator();
     }
 }
diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java
index 306688a..e6445d7 100644
--- a/core/java/android/view/animation/OvershootInterpolator.java
+++ b/core/java/android/view/animation/OvershootInterpolator.java
@@ -20,19 +20,19 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 
 import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * An interpolator where the change flings forward and overshoots the last value
  * then comes back.
  */
 @HasNativeInterpolator
-public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolator {
     private final float mTension;
 
     public OvershootInterpolator() {
@@ -76,6 +76,6 @@
     /** @hide */
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createOvershootInterpolator(mTension);
+        return NativeInterpolatorFactory.createOvershootInterpolator(mTension);
     }
 }
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index 924437a..99d6b9c 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -20,14 +20,14 @@
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Path;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.util.AttributeSet;
 import android.util.PathParser;
 import android.view.InflateException;
 
 import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
  * An interpolator that can traverse a Path that extends from <code>Point</code>
@@ -46,7 +46,7 @@
  * </pre></blockquote></p>
  */
 @HasNativeInterpolator
-public class PathInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class PathInterpolator extends BaseInterpolator implements NativeInterpolator {
 
     // This governs how accurate the approximation of the Path is.
     private static final float PRECISION = 0.002f;
@@ -237,7 +237,7 @@
     /** @hide **/
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createPathInterpolator(mX, mY);
+        return NativeInterpolatorFactory.createPathInterpolator(mX, mY);
     }
 
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index c159f89..54bdb88 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -86,9 +86,10 @@
 import java.util.Objects;
 import java.util.Set;
 
-//TODO: use java.lang.ref.Cleaner once Android supports Java 9
 import sun.misc.Cleaner;
 
+//TODO: use java.lang.ref.Cleaner once Android supports Java 9
+
 /**
  * <p>The {@link AutofillManager} class provides ways for apps and custom views to
  * integrate with the Autofill Framework lifecycle.
@@ -547,7 +548,7 @@
          * @param fillInIntent The authentication fill-in intent.
          */
         void autofillClientAuthenticate(int authenticationId, IntentSender intent,
-                Intent fillInIntent);
+                Intent fillInIntent, boolean authenticateInline);
 
         /**
          * Tells the client this manager has state to be reset.
@@ -2070,7 +2071,7 @@
     }
 
     private void authenticate(int sessionId, int authenticationId, IntentSender intent,
-            Intent fillInIntent) {
+            Intent fillInIntent, boolean authenticateInline) {
         synchronized (mLock) {
             if (sessionId == mSessionId) {
                 final AutofillClient client = getClient();
@@ -2078,7 +2079,8 @@
                     // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill()
                     // before onAuthenticationResult()
                     mOnInvisibleCalled = false;
-                    client.autofillClientAuthenticate(authenticationId, intent, fillInIntent);
+                    client.autofillClientAuthenticate(authenticationId, intent, fillInIntent,
+                            authenticateInline);
                 }
             }
         }
@@ -3250,10 +3252,11 @@
 
         @Override
         public void authenticate(int sessionId, int authenticationId, IntentSender intent,
-                Intent fillInIntent) {
+                Intent fillInIntent, boolean authenticateInline) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
+                afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent,
+                        authenticateInline));
             }
         }
 
@@ -3363,14 +3366,8 @@
             final AutofillManager afm = mAfm.get();
             if (afm == null) return null;
 
-            final AutofillClient client = afm.getClient();
-            if (client == null) {
-                Log.w(TAG, "getViewCoordinates(" + id + "): no autofill client");
-                return null;
-            }
-            final View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+            final View view = getView(afm, id);
             if (view == null) {
-                Log.w(TAG, "getViewCoordinates(" + id + "): could not find view");
                 return null;
             }
             final Rect windowVisibleDisplayFrame = new Rect();
@@ -3411,5 +3408,42 @@
                 afm.post(() -> afm.requestHideFillUi(id, false));
             }
         }
+
+        @Override
+        public boolean requestAutofill(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm == null || afm.mSessionId != sessionId) {
+                if (sDebug) {
+                    Slog.d(TAG, "Autofill not available or sessionId doesn't match");
+                }
+                return false;
+            }
+            final View view = getView(afm, id);
+            if (view == null || !view.isFocused()) {
+                if (sDebug) {
+                    Slog.d(TAG, "View not available or is not on focus");
+                }
+                return false;
+            }
+            if (sVerbose) {
+                Log.v(TAG, "requestAutofill() by AugmentedAutofillService.");
+            }
+            afm.post(() -> afm.requestAutofill(view));
+            return true;
+        }
+
+        @Nullable
+        private View getView(@NonNull AutofillManager afm, @NonNull AutofillId id) {
+            final AutofillClient client = afm.getClient();
+            if (client == null) {
+                Log.w(TAG, "getView(" + id + "): no autofill client");
+                return null;
+            }
+            View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+            if (view == null) {
+                Log.w(TAG, "getView(" + id + "): could not find view");
+            }
+            return view;
+        }
     }
 }
diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
index 140507c..03054df 100644
--- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
@@ -50,4 +50,10 @@
       * Requests hiding the fill UI.
       */
     void requestHideFillUi(int sessionId, in AutofillId id);
+
+    /**
+      * Requests to start a new autofill flow. Returns true if the autofill request is made to
+      * {@link AutofillManager#requestAutofill(View)}.
+      */
+    boolean requestAutofill(int sessionId, in AutofillId id);
 }
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 84949c8..3903665 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -50,7 +50,7 @@
       * Authenticates a fill response or a data set.
       */
     void authenticate(int sessionId, int authenticationId, in IntentSender intent,
-            in Intent fillInIntent);
+            in Intent fillInIntent, boolean authenticateInline);
 
     /**
       * Sets the views to track. If saveOnAllViewsInvisible is set and all these view are invisible
diff --git a/core/java/android/view/inline/InlinePresentationSpec.java b/core/java/android/view/inline/InlinePresentationSpec.java
index a85b5f1..406e599 100644
--- a/core/java/android/view/inline/InlinePresentationSpec.java
+++ b/core/java/android/view/inline/InlinePresentationSpec.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcelable;
 import android.util.Size;
 
@@ -55,6 +56,7 @@
     /**
      * @hide
      */
+    @SystemApi
     public @Nullable String getStyle() {
         return mStyle;
     }
@@ -281,10 +283,10 @@
     }
 
     @DataClass.Generated(
-            time = 1577145109444L,
+            time = 1581117017522L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java",
-            inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable java.lang.String mStyle\nprivate static  java.lang.String defaultStyle()\npublic @android.annotation.Nullable java.lang.String getStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable java.lang.String mStyle\nprivate static  java.lang.String defaultStyle()\npublic @android.annotation.SystemApi @android.annotation.Nullable java.lang.String getStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index a32ea4b..57269c4 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -286,7 +286,7 @@
     };
 
     @DataClass.Generated(
-            time = 1578972138081L,
+            time = 1581377984320L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
             inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic  void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 860ce90..be9370a 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.os.IBinder;
 import android.os.Parcelable;
 import android.view.inline.InlinePresentationSpec;
 
@@ -59,6 +60,14 @@
     private @NonNull String mHostPackageName;
 
     /**
+     * The host input token of the IME that made the request. This will be set by the system for
+     * safety reasons.
+     *
+     * @hide
+     */
+    private @Nullable IBinder mHostInputToken;
+
+    /**
      * @hide
      * @see {@link #mHostPackageName}.
      */
@@ -66,6 +75,14 @@
         mHostPackageName = hostPackageName;
     }
 
+    /**
+     * @hide
+     * @see {@link #mHostInputToken}.
+     */
+    public void setHostInputToken(IBinder hostInputToken) {
+        mHostInputToken = hostInputToken;
+    }
+
     private void onConstructed() {
         Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size());
     }
@@ -78,11 +95,17 @@
         return ActivityThread.currentPackageName();
     }
 
+    private static IBinder defaultHostInputToken() {
+        return null;
+    }
+
     /** @hide */
     abstract static class BaseBuilder {
         abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value);
 
         abstract Builder setHostPackageName(@Nullable String value);
+
+        abstract Builder setHostInputToken(IBinder hostInputToken);
     }
 
 
@@ -104,7 +127,8 @@
     /* package-private */ InlineSuggestionsRequest(
             int maxSuggestionCount,
             @NonNull List<InlinePresentationSpec> presentationSpecs,
-            @NonNull String hostPackageName) {
+            @NonNull String hostPackageName,
+            @Nullable IBinder hostInputToken) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mPresentationSpecs = presentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -112,6 +136,7 @@
         this.mHostPackageName = hostPackageName;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mHostPackageName);
+        this.mHostInputToken = hostInputToken;
 
         onConstructed();
     }
@@ -145,6 +170,17 @@
         return mHostPackageName;
     }
 
+    /**
+     * The host input token of the IME that made the request. This will be set by the system for
+     * safety reasons.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable IBinder getHostInputToken() {
+        return mHostInputToken;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -154,7 +190,8 @@
         return "InlineSuggestionsRequest { " +
                 "maxSuggestionCount = " + mMaxSuggestionCount + ", " +
                 "presentationSpecs = " + mPresentationSpecs + ", " +
-                "hostPackageName = " + mHostPackageName +
+                "hostPackageName = " + mHostPackageName + ", " +
+                "hostInputToken = " + mHostInputToken +
         " }";
     }
 
@@ -173,7 +210,8 @@
         return true
                 && mMaxSuggestionCount == that.mMaxSuggestionCount
                 && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs)
-                && java.util.Objects.equals(mHostPackageName, that.mHostPackageName);
+                && java.util.Objects.equals(mHostPackageName, that.mHostPackageName)
+                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
     }
 
     @Override
@@ -186,6 +224,7 @@
         _hash = 31 * _hash + mMaxSuggestionCount;
         _hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs);
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         return _hash;
     }
 
@@ -195,9 +234,13 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
+        byte flg = 0;
+        if (mHostInputToken != null) flg |= 0x8;
+        dest.writeByte(flg);
         dest.writeInt(mMaxSuggestionCount);
         dest.writeParcelableList(mPresentationSpecs, flags);
         dest.writeString(mHostPackageName);
+        if (mHostInputToken != null) dest.writeStrongBinder(mHostInputToken);
     }
 
     @Override
@@ -211,10 +254,12 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
+        byte flg = in.readByte();
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
         in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader());
         String hostPackageName = in.readString();
+        IBinder hostInputToken = (flg & 0x8) == 0 ? null : in.readStrongBinder();
 
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mPresentationSpecs = presentationSpecs;
@@ -223,6 +268,7 @@
         this.mHostPackageName = hostPackageName;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mHostPackageName);
+        this.mHostInputToken = hostInputToken;
 
         onConstructed();
     }
@@ -251,6 +297,7 @@
         private int mMaxSuggestionCount;
         private @NonNull List<InlinePresentationSpec> mPresentationSpecs;
         private @NonNull String mHostPackageName;
+        private @Nullable IBinder mHostInputToken;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -320,10 +367,25 @@
             return this;
         }
 
+        /**
+         * The host input token of the IME that made the request. This will be set by the system for
+         * safety reasons.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        @Override
+        @NonNull Builder setHostInputToken(@Nullable IBinder value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mHostInputToken = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8; // Mark builder used
+            mBuilderFieldsSet |= 0x10; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -331,15 +393,19 @@
             if ((mBuilderFieldsSet & 0x4) == 0) {
                 mHostPackageName = defaultHostPackageName();
             }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mHostInputToken = defaultHostInputToken();
+            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mPresentationSpecs,
-                    mHostPackageName);
+                    mHostPackageName,
+                    mHostInputToken);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x8) != 0) {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -347,10 +413,10 @@
     }
 
     @DataClass.Generated(
-            time = 1578948035951L,
+            time = 1581555687721L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic  void setHostPackageName(java.lang.String)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\npublic  void setHostPackageName(java.lang.String)\npublic  void setHostInputToken(android.os.IBinder)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.os.IBinder defaultHostInputToken()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS
new file mode 100644
index 0000000..244cc30
--- /dev/null
+++ b/core/java/android/view/inputmethod/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../../../../services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 8a1a0b5..556b24c 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
-    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.Q;
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R;
 
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 941af6e..8790bbd 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@
     // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
     /** @hide */
     private static final String CHROMIUM_WEBVIEW_FACTORY =
-            "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ";
+            "com.android.webview.chromium.WebViewChromiumFactoryProviderForR";
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 46b8b77..12f245e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -5271,6 +5271,8 @@
                 if (opacity < 10 || opacity > 100) {
                     opacity = 50;
                 }
+                // Converts the opacity value from range {0..100} to {0..255}.
+                opacity = opacity * 255 / 100;
             }
             mDeltaHeight = deltaHeight;
             mDrawableOpacity = opacity;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 469ab2e..0182975 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8282,9 +8282,9 @@
         if (getKeyListener() != null && !mSingleLine && mEditor != null
                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
                         == EditorInfo.TYPE_CLASS_TEXT) {
-            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
-            if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
-                    || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
+            int multilineFlags = EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
+                    | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+            if ((mEditor.mInputType & multilineFlags) != 0) {
                 return false;
             }
         }
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 93659a4..a1c22e9 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -22,6 +22,7 @@
 import static com.android.internal.util.ArrayUtils.convertToLongArray;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AlertDialog;
@@ -53,6 +54,8 @@
 import com.android.internal.R;
 import com.android.internal.util.function.pooled.PooledLambda;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -85,6 +88,17 @@
     private boolean mEnabledOnLockScreen;
     private int mUserId;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DialogStaus.NOT_SHOWN,
+            DialogStaus.SHOWN,
+    })
+    /** Denotes the user shortcut type. */
+    private @interface DialogStaus {
+        int NOT_SHOWN = 0;
+        int SHOWN  = 1;
+    }
+
     // Visible for testing
     public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
 
@@ -163,7 +177,8 @@
                 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
         // Enable the shortcut from the lockscreen by default if the dialog has been shown
         final int dialogAlreadyShown = Settings.Secure.getIntForUser(
-                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mUserId);
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+                mUserId);
         mEnabledOnLockScreen = Settings.Secure.getIntForUser(
                 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
                 dialogAlreadyShown, mUserId) == 1;
@@ -178,7 +193,8 @@
         final ContentResolver cr = mContext.getContentResolver();
         final int userId = ActivityManager.getCurrentUser();
         final int dialogAlreadyShown = Settings.Secure.getIntForUser(
-                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+                userId);
         // Play a notification vibration
         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -205,7 +221,8 @@
             w.setAttributes(attr);
             mAlertDialog.show();
             Settings.Secure.putIntForUser(
-                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId);
+                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.SHOWN,
+                    userId);
         } else {
             playNotificationTone();
             if (mAlertDialog != null) {
@@ -251,15 +268,8 @@
     }
 
     private AlertDialog createShortcutWarningDialog(int userId) {
-        final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);
-
-        if (serviceDescription == null) {
-            return null;
-        }
-
-        final String warningMessage = String.format(
-                mContext.getString(R.string.accessibility_shortcut_toogle_warning),
-                serviceDescription);
+        final String warningMessage = mContext.getString(
+                R.string.accessibility_shortcut_toogle_warning);
         final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
                 // Use SystemUI context so we pick up any theme set in a vendor overlay
                 mFrameworkObjectProvider.getSystemUiContext())
@@ -272,11 +282,17 @@
                             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                                     Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
                                     userId);
+
+                            // If canceled, treat as if the dialog has never been shown
+                            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                                    DialogStaus.NOT_SHOWN, userId);
                         })
                 .setOnCancelListener((DialogInterface d) -> {
                     // If canceled, treat as if the dialog has never been shown
                     Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+                            Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                            DialogStaus.NOT_SHOWN, userId);
                 })
                 .create();
         return alertDialog;
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 78e8518..efcd54f 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -61,6 +61,7 @@
     private Set<Integer> mLoadedPages;
     private final UserHandle mPersonalProfileUserHandle;
     private final UserHandle mWorkProfileUserHandle;
+    private Injector mInjector;
 
     AbstractMultiProfilePagerAdapter(Context context, int currentPage,
             UserHandle personalProfileUserHandle,
@@ -70,6 +71,33 @@
         mLoadedPages = new HashSet<>();
         mPersonalProfileUserHandle = personalProfileUserHandle;
         mWorkProfileUserHandle = workProfileUserHandle;
+        UserManager userManager = context.getSystemService(UserManager.class);
+        mInjector = new Injector() {
+            @Override
+            public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
+                    int targetUserId) {
+                return AbstractMultiProfilePagerAdapter.this
+                        .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
+            }
+
+            @Override
+            public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+                return userManager.isQuietModeEnabled(workProfileUserHandle);
+            }
+
+            @Override
+            public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
+                userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+            }
+        };
+    }
+
+    /**
+     * Overrides the default {@link Injector} for testing purposes.
+     */
+    @VisibleForTesting
+    public void setInjector(Injector injector) {
+        mInjector = injector;
     }
 
     void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
@@ -252,19 +280,18 @@
 
     private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
         UserHandle listUserHandle = activeListAdapter.getUserHandle();
-        UserManager userManager = mContext.getSystemService(UserManager.class);
         if (listUserHandle == mWorkProfileUserHandle
-                && userManager.isQuietModeEnabled(mWorkProfileUserHandle)) {
+                && mInjector.isQuietModeEnabled(mWorkProfileUserHandle)) {
             showEmptyState(activeListAdapter,
                     R.drawable.ic_work_apps_off,
                     R.string.resolver_turn_on_work_apps,
                     R.string.resolver_turn_on_work_apps_explanation,
                     (View.OnClickListener) v ->
-                            userManager.requestQuietModeEnabled(false, mWorkProfileUserHandle));
+                            mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle));
             return false;
         }
         if (UserHandle.myUserId() != listUserHandle.getIdentifier()) {
-            if (!hasCrossProfileIntents(activeListAdapter.getIntents(),
+            if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
                     UserHandle.myUserId(), listUserHandle.getIdentifier())) {
                 if (listUserHandle == mPersonalProfileUserHandle) {
                     showEmptyState(activeListAdapter,
@@ -366,4 +393,26 @@
          */
         void onProfileSelected(int profileIndex);
     }
+
+    /**
+     * Describes an injector to be used for cross profile functionality. Overridable for testing.
+     */
+    @VisibleForTesting
+    public interface Injector {
+        /**
+         * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
+         * from {@code sourceUserId} to {@code targetUserId}.
+         */
+        boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
+
+        /**
+         * Returns whether the given profile is in quiet mode or not.
+         */
+        boolean isQuietModeEnabled(UserHandle workProfileUserHandle);
+
+        /**
+         * Enables or disables quiet mode for a managed profile.
+         */
+        void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
+    }
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index dabaf5a..1c1c254 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -18,6 +18,8 @@
 
 import android.app.AppOpsManager;
 import android.app.AsyncNotedAppOp;
+import android.app.SyncNotedAppOp;
+import android.app.RuntimeAppOpAccessMessage;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
 import android.os.RemoteCallback;
@@ -25,6 +27,7 @@
 import com.android.internal.app.IAppOpsActiveCallback;
 import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.MessageSamplingConfig;
 
 interface IAppOpsService {
     // These methods are also called by native code, so must
@@ -54,6 +57,9 @@
 
     // Remaining methods are only used in Java.
     int checkPackage(int uid, String packageName);
+    RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
+    MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(String packageName,
+            in SyncNotedAppOp appOp, String message);
     @UnsupportedAppUsage
     List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
     @UnsupportedAppUsage
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/com/android/internal/app/MessageSamplingConfig.aidl
similarity index 75%
rename from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
rename to core/java/com/android/internal/app/MessageSamplingConfig.aidl
index fcacd52..ab89ca2 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/core/java/com/android/internal/app/MessageSamplingConfig.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.view.animation;
+package com.android.internal.app;
 
-public interface NativeInterpolatorFactory {
-    long createNativeInterpolator();
-}
+parcelable MessageSamplingConfig;
diff --git a/core/java/com/android/internal/app/MessageSamplingConfig.java b/core/java/com/android/internal/app/MessageSamplingConfig.java
new file mode 100644
index 0000000..5300c1c
--- /dev/null
+++ b/core/java/com/android/internal/app/MessageSamplingConfig.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * Configuration for AppOps access messages sampling.
+ */
+@Immutable
+/*@DataClass*/
+public final class MessageSamplingConfig implements Parcelable {
+    /** Op code targeted during current sampling session */
+    private final @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int mSampledOpCode;
+    /** Range of ops which should be reported during current sampling session */
+    private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mAcceptableLeftDistance;
+    /** Expiration time for this sampling config */
+    private final @IntRange(from = 0L) long mExpirationTimeSinceBootMillis;
+
+
+
+
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/app/MessageSamplingConfig.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new MessageSamplingConfig.
+     *
+     * @param sampledOpCode
+     *   Op code targeted during current sampling session
+     * @param acceptableLeftDistance
+     *   Range of ops which should be reported during current sampling session
+     * @param expirationTimeSinceBootMillis
+     *   Expiration time for this sampling config
+     */
+    @DataClass.Generated.Member
+    public MessageSamplingConfig(
+            @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int sampledOpCode,
+            @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int acceptableLeftDistance,
+            @IntRange(from = 0L) long expirationTimeSinceBootMillis) {
+        this.mSampledOpCode = sampledOpCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mSampledOpCode,
+                "from", -1L,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mAcceptableLeftDistance = acceptableLeftDistance;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mAcceptableLeftDistance,
+                "from", 0L,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mExpirationTimeSinceBootMillis = expirationTimeSinceBootMillis;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mExpirationTimeSinceBootMillis,
+                "from", 0L);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Op code targeted during current sampling session
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int getSampledOpCode() {
+        return mSampledOpCode;
+    }
+
+    /**
+     * Range of ops which should be reported during current sampling session
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int getAcceptableLeftDistance() {
+        return mAcceptableLeftDistance;
+    }
+
+    /**
+     * Expiration time for this sampling config
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0L) long getExpirationTimeSinceBootMillis() {
+        return mExpirationTimeSinceBootMillis;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mSampledOpCode);
+        dest.writeInt(mAcceptableLeftDistance);
+        dest.writeLong(mExpirationTimeSinceBootMillis);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ MessageSamplingConfig(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int sampledOpCode = in.readInt();
+        int acceptableLeftDistance = in.readInt();
+        long expirationTimeSinceBootMillis = in.readLong();
+
+        this.mSampledOpCode = sampledOpCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mSampledOpCode,
+                "from", -1L,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mAcceptableLeftDistance = acceptableLeftDistance;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mAcceptableLeftDistance,
+                "from", 0L,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mExpirationTimeSinceBootMillis = expirationTimeSinceBootMillis;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mExpirationTimeSinceBootMillis,
+                "from", 0L);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<MessageSamplingConfig> CREATOR
+            = new Parcelable.Creator<MessageSamplingConfig>() {
+        @Override
+        public MessageSamplingConfig[] newArray(int size) {
+            return new MessageSamplingConfig[size];
+        }
+
+        @Override
+        public MessageSamplingConfig createFromParcel(@NonNull Parcel in) {
+            return new MessageSamplingConfig(in);
+        }
+    };
+
+    /*@DataClass.Generated(
+            time = 1580691255495L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/com/android/internal/app/MessageSamplingConfig.java",
+            inputSignatures = "private final @android.annotation.IntRange(from=-1L, to=AppOpsManager._NUM_OP - 1) int mSampledOpCode\nprivate final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mAcceptableLeftDistance\nprivate final @android.annotation.IntRange(from=0L) long mExpirationTimeSinceBootMillis\nclass MessageSamplingConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")*/
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 022573c..c2c9fff 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1278,10 +1278,9 @@
             throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
                     + "cannot be null.");
         }
-        boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
-
         // We partially rebuild the inactive adapter to determine if we should auto launch
-        mMultiProfilePagerAdapter.rebuildInactiveTab(false);
+        boolean rebuildActiveCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
+        boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false);
 
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
@@ -1290,7 +1289,7 @@
         }
         setContentView(mLayoutId);
         mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
-        return postRebuildList(rebuildCompleted);
+        return postRebuildList(rebuildActiveCompleted && rebuildInactiveCompleted);
     }
 
     /**
@@ -1338,10 +1337,11 @@
         int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
         if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
             return true;
-        } else if (numberOfProfiles == 2 && maybeAutolaunchIfCrossProfileSupported()) {
-            // note that autolaunching when we have 2 profiles, 1 resolved target on the active
-            // tab and 0 resolved targets on the inactive tab, is already handled before launching
-            // ResolverActivity
+        } else if (numberOfProfiles == 2
+                && mMultiProfilePagerAdapter.getActiveListAdapter().isListLoaded()
+                && mMultiProfilePagerAdapter.getInactiveListAdapter().isListLoaded()
+                && (maybeAutolaunchIfNoAppsOnInactiveTab()
+                        || maybeAutolaunchIfCrossProfileSupported())) {
             return true;
         }
         return false;
@@ -1364,6 +1364,23 @@
         return false;
     }
 
+    private boolean maybeAutolaunchIfNoAppsOnInactiveTab() {
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+        if (count != 1) {
+            return false;
+        }
+        ResolverListAdapter inactiveListAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+        if (inactiveListAdapter.getUnfilteredCount() != 0) {
+            return false;
+        }
+        TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+                .targetInfoForPosition(0, false);
+        safelyStartActivity(target);
+        finish();
+        return true;
+    }
+
     /**
      * When we have a personal and a work profile, we auto launch in the following scenario:
      * - There is 1 resolved target on each profile
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ea84090..54453d0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -87,6 +87,7 @@
     private final ResolverListCommunicator mResolverListCommunicator;
     private Runnable mPostListReadyRunnable;
     private final boolean mIsAudioCaptureDevice;
+    private boolean mIsListLoaded;
 
     public ResolverListAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList,
@@ -191,6 +192,7 @@
         mLastChosenPosition = -1;
         mAllTargetsAreBrowsers = false;
         mDisplayList.clear();
+        mIsListLoaded = false;
 
         if (mBaseResolveList != null) {
             currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -352,6 +354,7 @@
 
         mResolverListCommunicator.sendVoiceChoicesIfNeeded();
         postListReadyRunnable(doPostProcessing);
+        mIsListLoaded = true;
     }
 
     /**
@@ -611,6 +614,10 @@
         return mIntents;
     }
 
+    protected boolean isListLoaded() {
+        return mIsListLoaded;
+    }
+
     /**
      * Necessary methods to communicate between {@link ResolverListAdapter}
      * and {@link ResolverActivity}.
diff --git a/core/java/com/android/internal/inputmethod/OWNERS b/core/java/com/android/internal/inputmethod/OWNERS
new file mode 100644
index 0000000..fc0e5d4
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../../../../../services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 518911e..0e9c2c4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -19,8 +19,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.type.DefaultMimeMapFactory;
 import android.os.Build;
@@ -36,7 +34,6 @@
 import com.android.internal.logging.AndroidConfig;
 import com.android.server.NetworkManagementSocketTagger;
 
-import dalvik.annotation.compat.VersionCodes;
 import dalvik.system.RuntimeHooks;
 import dalvik.system.ThreadPrioritySetter;
 import dalvik.system.VMRuntime;
@@ -67,18 +64,8 @@
 
     private static volatile boolean mCrashing = false;
 
-    /**
-     * Native heap allocations will now have a non-zero tag in the most significant byte.
-     * See
-     * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>.
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = VersionCodes.Q)
-    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
-
     private static final native void nativeFinishInit();
     private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
-    private static native void nativeDisableHeapPointerTagging();
 
     private static int Clog_e(String tag, String msg, Throwable tr) {
         return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
@@ -411,20 +398,6 @@
         if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
     }
 
-    private static void maybeDisableHeapPointerTagging(long[] disabledCompatChanges) {
-        // Heap tagging needs to be disabled before any additional threads are created, but the
-        // AppCompat framework is not initialized enough at this point.
-        // Check if the change is enabled manually.
-        if (disabledCompatChanges != null) {
-            for (int i = 0; i < disabledCompatChanges.length; i++) {
-                if (disabledCompatChanges[i] == NATIVE_HEAP_POINTER_TAGGING) {
-                    nativeDisableHeapPointerTagging();
-                    break;
-                }
-            }
-        }
-    }
-
     protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
             String[] argv, ClassLoader classLoader) {
         // If the application calls System.exit(), terminate the process
@@ -437,8 +410,6 @@
         VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
         VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
 
-        maybeDisableHeapPointerTagging(disabledCompatChanges);
-
         final Arguments args = new Arguments(argv);
 
         // The end of of the RuntimeInit event (see #zygoteInit).
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index dfd700f..94924a5 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -24,7 +24,6 @@
 import android.net.Credentials;
 import android.net.LocalServerSocket;
 import android.net.LocalSocket;
-import android.os.Build;
 import android.os.FactoryTest;
 import android.os.IVold;
 import android.os.Process;
@@ -122,6 +121,25 @@
      */
     public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
 
+    public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
+    /**
+     * Enable pointer tagging in this process.
+     * Tags are checked during memory deallocation, but not on access.
+     * TBI stands for Top-Byte-Ignore, an ARM CPU feature.
+     * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging}
+     */
+    public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19;
+
+    /**
+     * Enable asynchronous memory tag checks in this process.
+     */
+    public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19;
+
+    /**
+     * Enable synchronous memory tag checks in this process.
+     */
+    public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
@@ -272,7 +290,7 @@
     static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) {
+            boolean isTopApp, String[] pkgDataInfoList) {
         ZygoteHooks.preFork();
 
         int pid = nativeForkAndSpecialize(
@@ -280,8 +298,6 @@
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
                 pkgDataInfoList);
         if (pid == 0) {
-            Zygote.disableExecuteOnly(targetSdkVersion);
-
             // Note that this event ends at the end of handleChildProc,
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
         }
@@ -677,8 +693,6 @@
                                  args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
                                  args.mPkgDataInfoList);
 
-            disableExecuteOnly(args.mTargetSdkVersion);
-
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
             return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
@@ -758,17 +772,6 @@
     }
 
     /**
-     * Mark execute-only segments of libraries read+execute for apps with targetSdkVersion<Q.
-     */
-    private static void disableExecuteOnly(int targetSdkVersion) {
-        if ((targetSdkVersion < Build.VERSION_CODES.Q) && !nativeDisableExecuteOnly()) {
-            Log.e("Zygote", "Failed to set libraries to read+execute.");
-        }
-    }
-
-    private static native boolean nativeDisableExecuteOnly();
-
-    /**
      * @return  Raw file descriptors for the read-end of USAP reporting pipes.
      */
     static int[] getUsapPipeFDs() {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index c91c661..4949811 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -257,8 +257,8 @@
         pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
                 parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
                 parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
-                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
-                parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList);
+                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
+                parsedArgs.mPkgDataInfoList);
 
         try {
             if (pid == 0) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ae54eb2..e34aa97 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -788,6 +788,10 @@
             Zygote.applyDebuggerSystemProperty(parsedArgs);
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
+            /* Enable pointer tagging in the system server unconditionally. Hardware support for
+             * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */
+            parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+
             if (shouldProfileSystemServer()) {
                 parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
             }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index adb4036..36025e3 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1627,9 +1627,12 @@
 
         int opacity = PixelFormat.OPAQUE;
         final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration;
+        // TODO(b/149585281) remove when root task has the correct bounds for freeform
+        final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor
+                && winConfig.getWindowingMode() != WINDOWING_MODE_FREEFORM;
         // If we draw shadows in the compositor we don't need to force the surface to be
         // translucent.
-        if (winConfig.hasWindowShadow() && !mWindow.mRenderShadowsInCompositor) {
+        if (winConfig.hasWindowShadow() && !renderShadowsInCompositor) {
             // If the window has a shadow, it must be translucent.
             opacity = PixelFormat.TRANSLUCENT;
         } else{
@@ -2414,16 +2417,18 @@
     }
 
     private void updateElevation() {
+        final int windowingMode =
+                getResources().getConfiguration().windowConfiguration.getWindowingMode();
+        final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor
+                && windowingMode != WINDOWING_MODE_FREEFORM;
         // If rendering shadows in the compositor, don't set an elevation on the view
-        if (mWindow.mRenderShadowsInCompositor) {
+        if (renderShadowsInCompositor) {
             return;
         }
         float elevation = 0;
         final boolean wasAdjustedForStack = mElevationAdjustedForStack;
         // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
         // since the shadow is bound to the content size and not the target size.
-        final int windowingMode =
-                getResources().getConfiguration().windowConfiguration.getWindowingMode();
         if ((windowingMode == WINDOWING_MODE_FREEFORM) && !isResizing()) {
             elevation = hasWindowFocus() ?
                     DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
index c68e506..b61b9de 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/core/java/com/android/internal/policy/DockedDividerUtils.java
@@ -16,14 +16,15 @@
 
 package com.android.internal.policy;
 
-import android.graphics.Rect;
-
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
 
+import android.content.res.Resources;
+import android.graphics.Rect;
+
 /**
  * Utility functions for docked stack divider used by both window manager and System UI.
  *
@@ -105,23 +106,6 @@
         return start + (end - start) / 2 - dividerSize / 2;
     }
 
-    public static int getDockSideFromCreatedMode(boolean dockOnTopOrLeft,
-            boolean isHorizontalDivision) {
-        if (dockOnTopOrLeft) {
-            if (isHorizontalDivision) {
-                return DOCKED_TOP;
-            } else {
-                return DOCKED_LEFT;
-            }
-        } else {
-            if (isHorizontalDivision) {
-                return DOCKED_BOTTOM;
-            } else {
-                return DOCKED_RIGHT;
-            }
-        }
-    }
-
     public static int invertDockSide(int dockSide) {
         switch (dockSide) {
             case DOCKED_LEFT:
@@ -136,4 +120,21 @@
                 return DOCKED_INVALID;
         }
     }
+
+    /** Returns the inset distance from the divider window edge to the dividerview. */
+    public static int getDividerInsets(Resources res) {
+        return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets);
+    }
+
+    /** Returns the size of the divider */
+    public static int getDividerSize(Resources res, int dividerInsets) {
+        final int windowWidth = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        return windowWidth - 2 * dividerInsets;
+    }
+
+    /** Returns the docked-stack side */
+    public static int getDockSide(int displayWidth, int displayHeight) {
+        return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP;
+    }
 }
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 0f50596..3d5dfbb 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -21,6 +21,7 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.DisplayInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
@@ -54,6 +55,7 @@
     void onOemHookRawEvent(in byte[] rawData);
     void onCarrierNetworkChange(in boolean active);
     void onUserMobileDataStateChanged(in boolean enabled);
+    void onDisplayInfoChanged(in DisplayInfo displayInfo);
     void onPhoneCapabilityChanged(in PhoneCapability capability);
     void onActiveDataSubIdChanged(in int subId);
     void onRadioPowerStateChanged(in int state);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 47752c5..520ffc9 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -23,6 +23,7 @@
 import android.telephony.CallQuality;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
+import android.telephony.DisplayInfo;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
@@ -87,6 +88,7 @@
     void notifyOpportunisticSubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
     void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
+    void notifyDisplayInfoChanged(int slotIndex, int subId, in DisplayInfo displayInfo);
     void notifyPhoneCapabilityChanged(in PhoneCapability capability);
     void notifyActiveDataSubIdChanged(int activeDataSubId);
     void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java b/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java
deleted file mode 100644
index ebeec40..0000000
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2014 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.view.animation;
-
-/**
- * Static utility class for constructing native interpolators to keep the
- * JNI simpler
- */
-public final class NativeInterpolatorFactoryHelper {
-    private NativeInterpolatorFactoryHelper() {}
-
-    public static native long createAccelerateDecelerateInterpolator();
-    public static native long createAccelerateInterpolator(float factor);
-    public static native long createAnticipateInterpolator(float tension);
-    public static native long createAnticipateOvershootInterpolator(float tension);
-    public static native long createBounceInterpolator();
-    public static native long createCycleInterpolator(float cycles);
-    public static native long createDecelerateInterpolator(float factor);
-    public static native long createLinearInterpolator();
-    public static native long createOvershootInterpolator(float tension);
-    public static native long createPathInterpolator(float[] x, float[] y);
-    public static native long createLutInterpolator(float[] values);
-}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 30d0d1d..653cbc9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -42,9 +42,7 @@
         "android_util_StringBlock.cpp",
         "android_util_XmlBlock.cpp",
         "android_util_jar_StrictJarFile.cpp",
-        "android_view_RenderNodeAnimator.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
-        "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
     ],
 
     include_dirs: [
@@ -236,6 +234,7 @@
                 "libui",
                 "libgraphicsenv",
                 "libgui",
+                "libmediandk",
                 "libsensor",
                 "libinput",
                 "libcamera_client",
@@ -351,6 +350,8 @@
         "android/graphics/apex/android_paint.cpp",
         "android/graphics/apex/android_region.cpp",
 
+        "android_graphics_animation_NativeInterpolatorFactory.cpp",
+        "android_graphics_animation_RenderNodeAnimator.cpp",
         "android_graphics_Canvas.cpp",
         "android_graphics_ColorSpace.cpp",
         "android_graphics_drawable_AnimatedVectorDrawable.cpp",
@@ -449,9 +450,10 @@
             ],
             shared_libs: [
                 "libandroidfw",
+                "libEGL",
+                "libmediandk",
                 "libnativedisplay",
                 "libnativewindow",
-                "libgui",
                 "libpdfium",
             ],
             static_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b47b7e3..bbc23fe 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -119,13 +119,11 @@
 extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
 extern int register_android_view_InputApplicationHandle(JNIEnv* env);
 extern int register_android_view_InputWindowHandle(JNIEnv* env);
-extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_SurfaceControl(JNIEnv* env);
 extern int register_android_view_SurfaceSession(JNIEnv* env);
 extern int register_android_view_CompositionSamplingListener(JNIEnv* env);
 extern int register_android_view_TextureView(JNIEnv* env);
-extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
 extern int register_android_database_CursorWindow(JNIEnv* env);
 extern int register_android_database_SQLiteConnection(JNIEnv* env);
 extern int register_android_database_SQLiteGlobal(JNIEnv* env);
@@ -182,6 +180,8 @@
 extern int register_android_view_MotionEvent(JNIEnv* env);
 extern int register_android_view_PointerIcon(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
+extern int register_android_view_VerifiedKeyEvent(JNIEnv* env);
+extern int register_android_view_VerifiedMotionEvent(JNIEnv* env);
 extern int register_android_content_res_ObbScanner(JNIEnv* env);
 extern int register_android_content_res_Configuration(JNIEnv* env);
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
@@ -241,14 +241,6 @@
     gCurRuntime->setExitWithoutCleanup(exitWithoutCleanup);
 }
 
-static void com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging(
-        JNIEnv* env, jobject clazz) {
-    HeapTaggingLevel tag_level = M_HEAP_TAGGING_LEVEL_NONE;
-    if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &tag_level, sizeof(tag_level))) {
-        ALOGE("ERROR: could not disable heap pointer tagging\n");
-    }
-}
-
 /*
  * JNI registration.
  */
@@ -260,8 +252,6 @@
              (void*)com_android_internal_os_RuntimeInit_nativeFinishInit},
             {"nativeSetExitWithoutCleanup", "(Z)V",
              (void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup},
-            {"nativeDisableHeapPointerTagging", "()V",
-             (void*)com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging},
     };
     return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
         methods, NELEM(methods));
@@ -1469,7 +1459,6 @@
         REG_JNI(register_android_os_VintfRuntimeInfo),
         REG_JNI(register_android_service_DataLoaderService),
         REG_JNI(register_android_view_DisplayEventReceiver),
-        REG_JNI(register_android_view_RenderNodeAnimator),
         REG_JNI(register_android_view_InputApplicationHandle),
         REG_JNI(register_android_view_InputWindowHandle),
         REG_JNI(register_android_view_Surface),
@@ -1477,7 +1466,6 @@
         REG_JNI(register_android_view_SurfaceSession),
         REG_JNI(register_android_view_CompositionSamplingListener),
         REG_JNI(register_android_view_TextureView),
-        REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper),
         REG_JNI(register_com_google_android_gles_jni_EGLImpl),
         REG_JNI(register_com_google_android_gles_jni_GLImpl),
         REG_JNI(register_android_opengl_jni_EGL14),
@@ -1562,6 +1550,8 @@
         REG_JNI(register_android_view_MotionEvent),
         REG_JNI(register_android_view_PointerIcon),
         REG_JNI(register_android_view_VelocityTracker),
+        REG_JNI(register_android_view_VerifiedKeyEvent),
+        REG_JNI(register_android_view_VerifiedMotionEvent),
 
         REG_JNI(register_android_content_res_ObbScanner),
         REG_JNI(register_android_content_res_Configuration),
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 7ceef98..7ee509b 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -65,6 +65,8 @@
 extern int register_android_graphics_PathMeasure(JNIEnv* env);
 extern int register_android_graphics_Picture(JNIEnv* env);
 extern int register_android_graphics_Region(JNIEnv* env);
+extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env);
+extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
 extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
 extern int register_android_graphics_fonts_Font(JNIEnv* env);
@@ -82,10 +84,8 @@
 extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
-extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
-extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
 
 #define REG_JNI(name)      { name }
 struct RegJNIRec {
@@ -130,6 +130,10 @@
         {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
         {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
         {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
+        {"android.graphics.animation.NativeInterpolatorFactory",
+         REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
+        {"android.graphics.animation.RenderNodeAnimator",
+         REG_JNI(register_android_graphics_animation_RenderNodeAnimator)},
         {"android.graphics.drawable.AnimatedVectorDrawable",
          REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
         {"android.graphics.drawable.VectorDrawable",
@@ -151,11 +155,8 @@
         {"android.util.Log", REG_JNI(register_android_util_Log)},
         {"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
         {"android.util.jar.StrictJarFile", REG_JNI(register_android_util_jar_StrictJarFile)},
-        {"android.view.RenderNodeAnimator", REG_JNI(register_android_view_RenderNodeAnimator)},
         {"com.android.internal.util.VirtualRefBasePtr",
          REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
-        {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
-         REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)},
 };
 // Vector to store the names of classes that need delegates of their native methods
 static vector<string> classesToDelegate;
diff --git a/core/jni/android/graphics/apex/jni_runtime.cpp b/core/jni/android/graphics/apex/jni_runtime.cpp
index 1f66153..35c997d 100644
--- a/core/jni/android/graphics/apex/jni_runtime.cpp
+++ b/core/jni/android/graphics/apex/jni_runtime.cpp
@@ -60,6 +60,8 @@
 extern int register_android_graphics_Picture(JNIEnv*);
 extern int register_android_graphics_Region(JNIEnv* env);
 extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
+extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env);
+extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
 extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
 extern int register_android_graphics_fonts_Font(JNIEnv* env);
@@ -123,6 +125,8 @@
     REG_JNI(register_android_graphics_SurfaceTexture),
     REG_JNI(register_android_graphics_Typeface),
     REG_JNI(register_android_graphics_YuvImage),
+    REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
+    REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
     REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
     REG_JNI(register_android_graphics_drawable_VectorDrawable),
     REG_JNI(register_android_graphics_fonts_Font),
diff --git a/core/jni/com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp b/core/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
similarity index 95%
rename from core/jni/com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp
rename to core/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index f4d2e7b..2073ac2 100644
--- a/core/jni/com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp
+++ b/core/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -90,7 +90,7 @@
 // JNI Glue
 // ----------------------------------------------------------------------------
 
-const char* const kClassPathName = "com/android/internal/view/animation/NativeInterpolatorFactoryHelper";
+const char* const kClassPathName = "android/graphics/animation/NativeInterpolatorFactory";
 
 static const JNINativeMethod gMethods[] = {
     { "createAccelerateDecelerateInterpolator", "()J", (void*) createAccelerateDecelerateInterpolator },
@@ -106,7 +106,7 @@
     { "createLutInterpolator", "([F)J", (void*) createLutInterpolator },
 };
 
-int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv* env) {
+int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env) {
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
 
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_graphics_animation_RenderNodeAnimator.cpp
similarity index 96%
rename from core/jni/android_view_RenderNodeAnimator.cpp
rename to core/jni/android_graphics_animation_RenderNodeAnimator.cpp
index ca32b00..878d4fc 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -190,7 +190,7 @@
 // JNI Glue
 // ----------------------------------------------------------------------------
 
-const char* const kClassPathName = "android/view/RenderNodeAnimator";
+const char* const kClassPathName = "android/graphics/animation/RenderNodeAnimator";
 
 static const JNINativeMethod gMethods[] = {
     { "nCreateAnimator", "(IF)J", (void*) createAnimator },
@@ -203,12 +203,12 @@
     { "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
     { "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
     { "nSetAllowRunningAsync", "(JZ)V", (void*) setAllowRunningAsync },
-    { "nSetListener", "(JLandroid/view/RenderNodeAnimator;)V", (void*) setListener},
+    { "nSetListener", "(JLandroid/graphics/animation/RenderNodeAnimator;)V", (void*) setListener},
     { "nStart", "(J)V", (void*) start},
     { "nEnd", "(J)V", (void*) end },
 };
 
-int register_android_view_RenderNodeAnimator(JNIEnv* env) {
+int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env) {
     sLifecycleChecker.incStrong(0);
     gRenderNodeAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName);
     gRenderNodeAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env,
@@ -216,7 +216,7 @@
 
     gRenderNodeAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
             env, gRenderNodeAnimatorClassInfo.clazz, "callOnFinished",
-            "(Landroid/view/RenderNodeAnimator;)V");
+            "(Landroid/graphics/animation/RenderNodeAnimator;)V");
 
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 87be5f6..d8f30e9 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -566,10 +566,10 @@
     return (jint) status;
 }
 
-static jint
-android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state)
-{
-    return (jint) check_AudioSystem_Command(AudioSystem::setPhoneState((audio_mode_t) state));
+static jint android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state,
+                                                    jint uid) {
+    return (jint)check_AudioSystem_Command(
+            AudioSystem::setPhoneState((audio_mode_t)state, (uid_t)uid));
 }
 
 static jint
@@ -2434,7 +2434,7 @@
           (void *)android_media_AudioSystem_getDeviceConnectionState},
          {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I",
           (void *)android_media_AudioSystem_handleDeviceConfigChange},
-         {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState},
+         {"setPhoneState", "(II)I", (void *)android_media_AudioSystem_setPhoneState},
          {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse},
          {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse},
          {"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f564d75..ce9a048 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -65,6 +65,7 @@
 static struct {
     jclass clazz;
     jmethodID ctor;
+    jfieldID isInternal;
     jfieldID density;
     jfieldID secure;
 } gDisplayInfoClassInfo;
@@ -781,6 +782,8 @@
     }
 
     jobject object = env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor);
+    env->SetBooleanField(object, gDisplayInfoClassInfo.isInternal,
+                         info.connectionType == DisplayConnectionType::Internal);
     env->SetFloatField(object, gDisplayInfoClassInfo.density, info.density);
     env->SetBooleanField(object, gDisplayInfoClassInfo.secure, info.secure);
     return object;
@@ -1528,6 +1531,7 @@
     jclass infoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayInfo");
     gDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, infoClazz);
     gDisplayInfoClassInfo.ctor = GetMethodIDOrDie(env, infoClazz, "<init>", "()V");
+    gDisplayInfoClassInfo.isInternal = GetFieldIDOrDie(env, infoClazz, "isInternal", "Z");
     gDisplayInfoClassInfo.density = GetFieldIDOrDie(env, infoClazz, "density", "F");
     gDisplayInfoClassInfo.secure = GetFieldIDOrDie(env, infoClazz, "secure", "Z");
 
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5a8225c..27c5a73 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -17,40 +17,34 @@
 #define LOG_TAG "ThreadedRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
-#include <algorithm>
-#include <atomic>
-#include <inttypes.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include <GraphicsJNI.h>
-
-#include <gui/BufferItemConsumer.h>
-#include <gui/BufferQueue.h>
-#include <gui/Surface.h>
-
-#include "android_graphics_HardwareRendererObserver.h"
-
-#include <private/EGL/cache.h>
-
-#include <utils/RefBase.h>
-#include <utils/StrongPointer.h>
-#include <utils/Timers.h>
-#include <utils/TraceUtils.h>
-#include <android_runtime/android_view_Surface.h>
-#include <system/window.h>
-
 #include <FrameInfo.h>
+#include <GraphicsJNI.h>
 #include <Picture.h>
 #include <Properties.h>
 #include <RootRenderNode.h>
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <media/NdkImage.h>
+#include <media/NdkImageReader.h>
+#include <nativehelper/JNIHelp.h>
+#include <pipeline/skia/ShaderCache.h>
+#include <private/EGL/cache.h>
 #include <renderthread/CanvasContext.h>
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
 #include <renderthread/RenderThread.h>
-#include <pipeline/skia/ShaderCache.h>
 #include <utils/Color.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
+#include <utils/TraceUtils.h>
+
+#include <algorithm>
+#include <atomic>
+
+#include "android_graphics_HardwareRendererObserver.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
 
 namespace android {
 
@@ -78,6 +72,9 @@
     return env;
 }
 
+typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
+ANW_fromSurface fromSurface;
+
 class JvmErrorReporter : public ErrorHandler {
 public:
     JvmErrorReporter(JNIEnv* env) {
@@ -178,9 +175,9 @@
 static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject jsurface, jboolean discardBuffer) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    sp<Surface> surface;
+    ANativeWindow* window = nullptr;
     if (jsurface) {
-        surface = android_view_Surface_getSurface(env, jsurface);
+        window = fromSurface(env, jsurface);
     }
     bool enableTimeout = true;
     if (discardBuffer) {
@@ -188,7 +185,7 @@
         enableTimeout = false;
         proxy->setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
     }
-    proxy->setSurface(surface, enableTimeout);
+    proxy->setSurface(window, enableTimeout);
 }
 
 static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
@@ -458,8 +455,10 @@
         jint right, jint bottom, jlong bitmapPtr) {
     SkBitmap bitmap;
     bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-    sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
-    return RenderProxy::copySurfaceInto(surface, left, top, right, bottom, &bitmap);
+    ANativeWindow* window = fromSurface(env, jsurface);
+    jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap);
+    ANativeWindow_release(window);
+    return result;
 }
 
 class ContextFactory : public IContextFactory {
@@ -480,23 +479,35 @@
     uint32_t width = jwidth;
     uint32_t height = jheight;
 
-    // Create a Surface wired up to a BufferItemConsumer
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> rawConsumer;
-    BufferQueue::createBufferQueue(&producer, &rawConsumer);
-    // We only need 1 buffer but some drivers have bugs so workaround it by setting max count to 2
-    rawConsumer->setMaxBufferCount(2);
-    sp<BufferItemConsumer> consumer = new BufferItemConsumer(rawConsumer,
-            GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_NEVER);
-    consumer->setDefaultBufferSize(width, height);
-    sp<Surface> surface = new Surface(producer);
+    // Create an ImageReader wired up to a BufferItemConsumer
+    AImageReader* rawReader;
+    media_status_t result =
+            AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888,
+                                      AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, 2, &rawReader);
+    std::unique_ptr<AImageReader, decltype(&AImageReader_delete)> reader(rawReader,
+                                                                         AImageReader_delete);
+
+    if (result != AMEDIA_OK) {
+        ALOGW("Error creating image reader!");
+        return nullptr;
+    }
+
+    // Note that ownership of this window is maintained by AImageReader, so we
+    // shouldn't need to wrap around a smart pointer.
+    ANativeWindow* window;
+    result = AImageReader_getWindow(rawReader, &window);
+
+    if (result != AMEDIA_OK) {
+        ALOGW("Error retrieving the native window!");
+        return nullptr;
+    }
 
     // Render into the surface
     {
         ContextFactory factory;
         RenderProxy proxy{true, renderNode, &factory};
         proxy.setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
-        proxy.setSurface(surface);
+        proxy.setSurface(window);
         // Shadows can't be used via this interface, so just set the light source
         // to all 0s.
         proxy.setLightAlpha(0, 0);
@@ -508,33 +519,34 @@
         proxy.syncAndDrawFrame();
     }
 
-    // Yank out the GraphicBuffer
-    BufferItem bufferItem;
-    status_t err;
-    if ((err = consumer->acquireBuffer(&bufferItem, 0, true)) != OK) {
-        ALOGW("Failed to acquireBuffer, error %d (%s)", err, strerror(-err));
+    AImage* rawImage;
+    result = AImageReader_acquireNextImage(rawReader, &rawImage);
+    std::unique_ptr<AImage, decltype(&AImage_delete)> image(rawImage, AImage_delete);
+    if (result != AMEDIA_OK) {
+        ALOGW("Error reading image: %d!", result);
         return nullptr;
     }
-    sp<GraphicBuffer> buffer = bufferItem.mGraphicBuffer;
-    // We don't really care if this fails or not since we're just going to destroy this anyway
-    consumer->releaseBuffer(bufferItem);
-    if (!buffer.get()) {
-        ALOGW("GraphicBuffer is null?");
-        return nullptr;
-    }
-    if (buffer->getWidth() != width || buffer->getHeight() != height) {
-        ALOGW("GraphicBuffer size mismatch, got %dx%d expected %dx%d",
-                buffer->getWidth(), buffer->getHeight(), width, height);
+
+    AHardwareBuffer* buffer;
+    result = AImage_getHardwareBuffer(rawImage, &buffer);
+
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(buffer, &desc);
+
+    if (desc.width != width || desc.height != height) {
+        ALOGW("AHardwareBuffer size mismatch, got %dx%d expected %dx%d", desc.width, desc.height,
+              width, height);
         // Continue I guess?
     }
 
-    sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace);
+    sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(
+            static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
     if (cs == nullptr) {
         // nullptr is treated as SRGB in Skia, thus explicitly use SRGB in order to make sure
         // the returned bitmap has a color space.
         cs = SkColorSpace::MakeSRGB();
     }
-    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer->toAHardwareBuffer(), cs);
+    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
     return bitmap::createBitmap(env, bitmap.release(),
             android::bitmap::kBitmapCreateFlag_Premultiplied);
 }
@@ -722,6 +734,11 @@
     gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass,
             "onFrameComplete", "(J)V");
 
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+    LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
+                        "Failed to find required symbol ANativeWindow_fromSurface!");
+
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
 
diff --git a/core/jni/android_view_VerifiedKeyEvent.cpp b/core/jni/android_view_VerifiedKeyEvent.cpp
index 8fc301c..bba10aa 100644
--- a/core/jni/android_view_VerifiedKeyEvent.cpp
+++ b/core/jni/android_view_VerifiedKeyEvent.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedKey-JNI"
 
 #include "android_view_VerifiedKeyEvent.h"
 #include <input/Input.h>
@@ -22,18 +22,29 @@
 
 namespace android {
 
+static struct {
+    jclass clazz;
+
+    jmethodID constructor;
+} gVerifiedKeyEventClassInfo;
+
 // ----------------------------------------------------------------------------
 
 jobject android_view_VerifiedKeyEvent(JNIEnv* env, const VerifiedKeyEvent& event) {
-    static jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+    return env->NewObject(gVerifiedKeyEventClassInfo.clazz, gVerifiedKeyEventClassInfo.constructor,
+                          event.deviceId, event.eventTimeNanos, event.source, event.displayId,
+                          event.action, event.downTimeNanos, event.flags, event.keyCode,
+                          event.scanCode, event.metaState, event.repeatCount);
+}
 
-    static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+int register_android_view_VerifiedKeyEvent(JNIEnv* env) {
+    jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+    gVerifiedKeyEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
-    jobject object =
-            env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
-                           event.displayId, event.action, event.downTimeNanos, event.flags,
-                           event.keyCode, event.scanCode, event.metaState, event.repeatCount);
-    return object;
+    gVerifiedKeyEventClassInfo.constructor =
+            GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+
+    return OK;
 }
 
 } // namespace android
diff --git a/core/jni/android_view_VerifiedMotionEvent.cpp b/core/jni/android_view_VerifiedMotionEvent.cpp
index 7a5c71a..c281197 100644
--- a/core/jni/android_view_VerifiedMotionEvent.cpp
+++ b/core/jni/android_view_VerifiedMotionEvent.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedMotion-JNI"
 
 #include "android_view_VerifiedMotionEvent.h"
 #include <input/Input.h>
@@ -22,18 +22,30 @@
 
 namespace android {
 
+static struct {
+    jclass clazz;
+
+    jmethodID constructor;
+} gVerifiedMotionEventClassInfo;
+
 // ----------------------------------------------------------------------------
 
 jobject android_view_VerifiedMotionEvent(JNIEnv* env, const VerifiedMotionEvent& event) {
-    static jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+    return env->NewObject(gVerifiedMotionEventClassInfo.clazz,
+                          gVerifiedMotionEventClassInfo.constructor, event.deviceId,
+                          event.eventTimeNanos, event.source, event.displayId, event.rawX,
+                          event.rawY, event.actionMasked, event.downTimeNanos, event.flags,
+                          event.metaState, event.buttonState);
+}
 
-    static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+int register_android_view_VerifiedMotionEvent(JNIEnv* env) {
+    jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+    gVerifiedMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
-    jobject object =
-            env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
-                           event.displayId, event.rawX, event.rawY, event.actionMasked,
-                           event.downTimeNanos, event.flags, event.metaState, event.buttonState);
-    return object;
+    gVerifiedMotionEventClassInfo.constructor =
+            GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+
+    return OK;
 }
 
 } // namespace android
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7a9a3f8..d91911c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -49,7 +49,6 @@
 #include <fcntl.h>
 #include <grp.h>
 #include <inttypes.h>
-#include <link.h>
 #include <malloc.h>
 #include <mntent.h>
 #include <paths.h>
@@ -59,7 +58,6 @@
 #include <sys/capability.h>
 #include <sys/cdefs.h>
 #include <sys/eventfd.h>
-#include <sys/mman.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
@@ -72,25 +70,23 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
-#include <android-base/file.h>
 #include <android-base/stringprintf.h>
-#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <bionic/malloc.h>
-#include <bionic/page.h>
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
 #include <cutils/sockets.h>
 #include <private/android_filesystem_config.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-#include <selinux/android.h>
-#include <seccomp_policy.h>
-#include <stats_event_list.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/sched_policy.h>
+#include <seccomp_policy.h>
+#include <selinux/android.h>
+#include <stats_socket.h>
+#include <utils/String8.h>
+#include <utils/Trace.h>
 
 #include "core_jni_helpers.h"
 #include <nativehelper/JNIHelp.h>
@@ -349,6 +345,8 @@
 enum RuntimeFlags : uint32_t {
   DEBUG_ENABLE_JDWP = 1,
   PROFILE_FROM_SHELL = 1 << 15,
+  MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20),
+  MEMORY_TAG_LEVEL_TBI = 1 << 19,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -1078,7 +1076,7 @@
   // Close any logging related FDs before we start evaluating the list of
   // file descriptors.
   __android_log_close();
-  stats_log_close();
+  AStatsSocket_close();
 
   // If this is the first fork for this zygote, create the open FD table.  If
   // it isn't, we just need to check whether the list of open files has changed
@@ -1627,6 +1625,16 @@
     }
   }
 
+  HeapTaggingLevel heap_tagging_level;
+  switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) {
+    case RuntimeFlags::MEMORY_TAG_LEVEL_TBI:
+      heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
+      break;
+    default:
+      heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
+  }
+  android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level));
+
   if (NeedsNoRandomizeWorkaround()) {
     // Work around ARM kernel ASLR lossage (http://b/5817320).
     int old_personality = personality(0xffffffff);
@@ -1639,7 +1647,7 @@
   SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
 
   __android_log_close();
-  stats_log_close();
+  AStatsSocket_close();
 
   const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
   const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -1893,25 +1901,6 @@
   UnmountTree("/storage");
 }
 
-static int DisableExecuteOnly(struct dl_phdr_info* info,
-                              size_t size [[maybe_unused]],
-                              void* data [[maybe_unused]]) {
-  // Search for any execute-only segments and mark them read+execute.
-  for (int i = 0; i < info->dlpi_phnum; i++) {
-    const auto& phdr = info->dlpi_phdr[i];
-    if ((phdr.p_type == PT_LOAD) && (phdr.p_flags == PF_X)) {
-      auto addr = reinterpret_cast<void*>(info->dlpi_addr + PAGE_START(phdr.p_vaddr));
-      size_t len = PAGE_OFFSET(phdr.p_vaddr) + phdr.p_memsz;
-      if (mprotect(addr, len, PROT_READ | PROT_EXEC) == -1) {
-        ALOGE("mprotect(%p, %zu, PROT_READ | PROT_EXEC) failed: %m", addr, len);
-        return -1;
-      }
-    }
-  }
-  // Return non-zero to exit dl_iterate_phdr.
-  return 0;
-}
-
 }  // anonymous namespace
 
 namespace android {
@@ -2274,14 +2263,6 @@
   }
 }
 
-/**
- * @param env  Managed runtime environment
- * @return  True if disable was successful.
- */
-static jboolean com_android_internal_os_Zygote_nativeDisableExecuteOnly(JNIEnv* env, jclass) {
-  return dl_iterate_phdr(DisableExecuteOnly, nullptr) == 0;
-}
-
 static void com_android_internal_os_Zygote_nativeBlockSigTerm(JNIEnv* env, jclass) {
   auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1);
   BlockSignal(SIGTERM, fail_fn);
@@ -2363,8 +2344,6 @@
         {"nativeGetUsapPoolCount", "()I",
          (void*)com_android_internal_os_Zygote_nativeGetUsapPoolCount},
         {"nativeEmptyUsapPool", "()V", (void*)com_android_internal_os_Zygote_nativeEmptyUsapPool},
-        {"nativeDisableExecuteOnly", "()Z",
-         (void*)com_android_internal_os_Zygote_nativeDisableExecuteOnly},
         {"nativeBlockSigTerm", "()V", (void*)com_android_internal_os_Zygote_nativeBlockSigTerm},
         {"nativeUnblockSigTerm", "()V", (void*)com_android_internal_os_Zygote_nativeUnblockSigTerm},
         {"nativeBoostUsapPriority", "()V",
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index bf4cdee..829c252 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -34,6 +34,7 @@
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
 import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
+import "frameworks/base/core/proto/android/server/bluetooth_manager_service.proto";
 import "frameworks/base/core/proto/android/server/fingerprint.proto";
 import "frameworks/base/core/proto/android/server/jobscheduler.proto";
 import "frameworks/base/core/proto/android/server/location/context_hub.proto";
@@ -428,7 +429,7 @@
         (section).args = "dropbox --proto system_app_wtf"
     ];
 
-    optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crashes = 3037 [
+    optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crash = 3037 [
         (section).type = SECTION_DUMPSYS,
         (section).args = "dropbox --proto system_server_crash"
     ];
@@ -488,6 +489,11 @@
         (section).args = "connmetrics --proto"
     ];
 
+    optional com.android.server.BluetoothManagerServiceDumpProto bluetooth_manager = 3050 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "bluetooth_manager --proto"
+    ];
+
     optional com.android.server.location.ContextHubServiceProto context_hub = 3051 [
         (section).type = SECTION_DUMPSYS,
         (section).args = "contexthub --proto"
diff --git a/core/proto/android/server/bluetooth_manager_service.proto b/core/proto/android/server/bluetooth_manager_service.proto
new file mode 100644
index 0000000..998413f
--- /dev/null
+++ b/core/proto/android/server/bluetooth_manager_service.proto
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package com.android.server;
+
+import "frameworks/base/core/proto/android/bluetooth/enums.proto";
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+message BluetoothManagerServiceDumpProto {
+   option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+   message ActiveLog {
+      option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+      optional int64 timestamp_ms = 1;
+      optional bool enable = 2;
+      optional string package_name = 3;
+      optional .android.bluetooth.EnableDisableReasonEnum reason = 4;
+   }
+
+   optional bool enabled = 1;
+   optional int32 state = 2;
+   optional string state_name = 3;
+   optional string address = 4 [(.android.privacy).dest = DEST_EXPLICIT];
+   optional string name = 5 [(.android.privacy).dest = DEST_EXPLICIT];
+   optional int64 last_enabled_time_ms = 6;
+   optional int64 curr_timestamp_ms = 7;
+   repeated ActiveLog active_logs = 8;
+   optional int32 num_crashes = 9;
+   optional bool crash_log_maxed = 10;
+   repeated int64 crash_timestamps_ms = 11;
+   optional int32 num_ble_apps = 12;
+   repeated string ble_app_package_names = 13;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
index 6749719..15f4abb 100644
--- a/core/proto/android/server/notificationhistory.proto
+++ b/core/proto/android/server/notificationhistory.proto
@@ -55,6 +55,11 @@
     // The small icon of the notification
     optional Icon icon = 12;
 
+    // The conversation id, if any, that this notification belongs to
+    optional string conversation_id = 13;
+    // conversation_id_index contains the index + 1 of the conversation id in the string pool
+    optional int32 conversation_id_index = 14;
+
     // Matches the constants of android.graphics.drawable.Icon
     enum ImageTypeEnum {
       TYPE_UNKNOWN = 0;
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index 294b6ef..e476c52 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -52,6 +52,11 @@
   optional int32 conversation_flags = 6;
 }
 
+// On disk data of events.
+message PeopleEventsProto {
+  repeated PeopleEventProto events = 1;
+}
+
 // Individual event (com.android.server.people.data.Event).
 message PeopleEventProto {
 
@@ -65,11 +70,23 @@
   optional int32 duration = 3;
 }
 
+// On disk data of event indexes.
+message PeopleEventIndexesProto {
+  repeated TypedPeopleEventIndexProto typed_indexes = 1;
+}
+
+// Mapping of event_type to event index.
+message TypedPeopleEventIndexProto {
+  optional int32 event_type = 1;
+  optional PeopleEventIndexProto index = 2;
+}
+
 // Index of events' time distributions (com.android.server.people.data.EventIndex).
 message PeopleEventIndexProto {
-    // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further
-    // details can be found in class documentation.
-    repeated int64 event_bitmaps = 1;
+  // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further
+  // details can be found in class documentation.
+  repeated int64 event_bitmaps = 1;
 
-    optional int64 last_updated_time = 2;
+  optional int64 last_updated_time = 2;
 }
+
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index b0b9ce6f..08db454 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -275,6 +275,7 @@
     optional float adjust_divider_amount = 25;
     optional bool animating_bounds = 26;
     optional float minimize_amount = 27;
+    optional bool created_by_organizer = 28;
 }
 
 /* represents ActivityRecordProto */
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index d1392a5..a4e2193 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -159,4 +159,21 @@
   ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
   SET_TIME = 133;
   SET_TIME_ZONE = 134;
+  SET_PERSONAL_APPS_SUSPENDED = 135;
+  SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF = 136;
+  COMP_TO_ORG_OWNED_PO_MIGRATED = 137;
+  SET_CROSS_PROFILE_PACKAGES = 138;
+  SET_INTERACT_ACROSS_PROFILES_APP_OP = 139;
+  GET_CROSS_PROFILE_PACKAGES = 140;
+  CAN_REQUEST_INTERACT_ACROSS_PROFILES_TRUE = 141;
+  CAN_REQUEST_INTERACT_ACROSS_PROFILES_FALSE_NO_PROFILES = 142;
+  CAN_REQUEST_INTERACT_ACROSS_PROFILES_FALSE_WHITELIST = 143;
+  CAN_REQUEST_INTERACT_ACROSS_PROFILES_FALSE_PERMISSION = 144;
+  CAN_INTERACT_ACROSS_PROFILES_TRUE = 145;
+  CAN_INTERACT_ACROSS_PROFILES_FALSE_PERMISSION = 146;
+  CAN_INTERACT_ACROSS_PROFILES_FALSE_NO_PROFILES = 147;
+  CREATE_CROSS_PROFILE_INTENT = 148;
+  IS_MANAGED_PROFILE = 149;
+  START_ACTIVITY_BY_INTENT = 150;
+  BIND_CROSS_PROFILE_SERVICE = 151;
 }
diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto
index 34ed90a..e1af962 100644
--- a/core/proto/android/stats/mediametrics/mediametrics.proto
+++ b/core/proto/android/stats/mediametrics/mediametrics.proto
@@ -154,6 +154,8 @@
     optional int64 latency_avg = 18;
     optional int64 latency_count = 19;
     optional int64 latency_unknown = 20;
+    optional int32 queue_input_buffer_error = 21;
+    optional int32 queue_secure_input_buffer_error = 22;
 }
 
 /**
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 3dc74f8..b365de4 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -54,3 +54,22 @@
         "res/**/*",
     ],
 }
+
+// Generate a text file containing a list of permissions that non-system apps
+// are allowed to obtain.
+genrule {
+  name: "permission-list-normal",
+  out: ["permission-list-normal.txt"],
+  srcs: ["AndroidManifest.xml"],
+  cmd: "cat $(in) " +
+       // xmllint has trouble accessing attributes under the android namespace.
+       // Strip these prefixes prior to processing with xmllint.
+       " | sed -r 's/android:(name|protectionLevel)/\\1/g' " +
+       " | $(location xmllint) /dev/stdin --xpath " +
+       " '//permission[not(contains(@protectionLevel, \"signature\"))]/@name'" +
+       // The result of xmllint is name="value" pairs. Format these to just the
+       // permission name, one per-line.
+       " | sed -r 's/\\s*name=\\s*//g' | tr -d '\"'" +
+       " > $(out)",
+  tools: ["xmllint"]
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57ba7fe..b2047ad 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1640,7 +1640,7 @@
 
     <!-- Allows network stack services (Connectivity and Wifi) to coordinate
          <p>Not for use by third-party or privileged applications.
-         @SystemApi
+         @SystemApi @TestApi
          @hide This should only be used by Connectivity and Wifi Services.
     -->
     <permission android:name="android.permission.NETWORK_STACK"
@@ -4941,7 +4941,13 @@
          <p>Not for use by third-party applications.
          @hide -->
     <permission android:name="android.permission.ACCESS_TV_TUNER"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+    <!-- @SystemApi Allows an application to access descrambler of TV tuner HAL
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
 
     <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
@@ -5344,6 +5350,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.people.data.DataMaintenanceService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
                 android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
diff --git a/core/res/res/layout/autofill_inline_suggestion.xml b/core/res/res/layout/autofill_inline_suggestion.xml
deleted file mode 100644
index 27faea4..0000000
--- a/core/res/res/layout/autofill_inline_suggestion.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="?android:attr/autofillInlineSuggestionChip"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:paddingTop="4dp"
-    android:paddingBottom="4dp"
-    android:orientation="horizontal">
-
-    <ImageView
-        android:id="@+id/autofill_inline_suggestion_start_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:scaleType="fitCenter"
-        android:contentDescription="autofill_inline_suggestion_start_icon" />
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:layout_weight="1"
-        android:paddingStart="4dp"
-        android:paddingEnd="4dp"
-        android:orientation="vertical"
-        android:gravity="center">
-
-        <TextView
-            style="?android:attr/autofillInlineSuggestionTitle"
-            android:id="@+id/autofill_inline_suggestion_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="1"/>
-
-        <TextView
-            style="?android:attr/autofillInlineSuggestionSubtitle"
-            android:id="@+id/autofill_inline_suggestion_subtitle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="1"/>
-    </LinearLayout>
-
-    <ImageView
-        android:id="@+id/autofill_inline_suggestion_end_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:scaleType="fitCenter"
-        android:contentDescription="autofill_inline_suggestion_end_icon" />
-</LinearLayout>
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 6959e9c..5ed4c53 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -34,7 +34,7 @@
         android:layout_marginTop="16dp"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/textAppearanceMedium"
+        android:fontFamily="@string/config_headlineFontFamilyMedium"
         android:textColor="@color/resolver_empty_state_text"
         android:textSize="18sp"/>
     <TextView
@@ -52,7 +52,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="@null"
-        android:textAppearance="?attr/textAppearanceMedium"
+        android:fontFamily="@string/config_headlineFontFamilyMedium"
         android:textSize="14sp"
         android:textColor="@color/resolver_tabs_active_color"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7d8b8db..9cdd3ed 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3774,7 +3774,7 @@
         <attr name="animatedImageDrawable" format="reference"/>
         <!-- Html description of the accessibility service, to help users understand
              how the service can help them.-->
-        <attr name="htmlDescription" format="string"/>
+        <attr name="htmlDescription" format="reference"/>
 
         <!-- Short description of the accessibility service purpose or behavior.-->
         <attr name="description" />
@@ -3795,7 +3795,7 @@
         <attr name="animatedImageDrawable" format="reference"/>
         <!-- Html description of the target of accessibility shortcut purpose or behavior, to help
              users understand how the target of accessibility shortcut can help them. -->
-        <attr name="htmlDescription" format="string"/>
+        <attr name="htmlDescription" format="reference"/>
     </declare-styleable>
 
     <!-- Use <code>print-service</code> as the root tag of the XML resource that
@@ -9207,12 +9207,4 @@
     </declare-styleable>
 
     <attr name="autoSizePresetSizes" />
-
-    <declare-styleable name="AutofillInlineSuggestion">
-        <!-- @hide @SystemApi -->
-        <attr name="isAutofillInlineSuggestionTheme" format="boolean" />
-        <attr name="autofillInlineSuggestionChip" format="reference" />
-        <attr name="autofillInlineSuggestionTitle" format="reference" />
-        <attr name="autofillInlineSuggestionSubtitle" format="reference" />
-    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c66261b..16bed503 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1770,6 +1770,21 @@
              -->
         <attr name="requestLegacyExternalStorage" format="boolean" />
 
+        <!-- If {@code true} this app would like to preserve the legacy storage
+             model from a previously installed version. Note that this may not always be
+             respected due to policy or backwards compatibility reasons.
+
+             <p>This has no effect on the first install of an app on a device.
+             For an updating app, setting this to {@code true} will preserve the legacy behaviour
+             configured by the {@code requestLegacyExternalStorage} flag. If on an update, this
+             flag is set to {@code false} then the legacy access is not preserved, such an app can
+             only have legacy access with the {@code requestLegacyExternalStorage} flag.
+             <p>
+
+             The default value is {@code false}.
+             -->
+        <attr name="preserveLegacyExternalStorage" format="boolean" />
+
         <!-- If {@code true} this app declares that it should be visible to all other apps on
              device, regardless of what they declare via the {@code queries} tags in their
              manifest.
@@ -1782,6 +1797,15 @@
 
              The default value is {@code false}. -->
         <attr name="crossProfile" format="boolean" />
+
+        <!-- If {@code true} this app will receive tagged pointers to native heap allocations
+             from functions like malloc() on compatible devices. Note that this may not always
+             be respected due to policy or backwards compatibility reasons. See the
+             <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged Pointers</a>
+             document for more information on this feature.
+
+             The default value is {@code true}. -->
+        <attr name="allowNativeHeapPointerTagging" format="boolean" />
     </declare-styleable>
 
     <!-- The <code>feature</code> tag declares a feature. A feature is a logical part of an app.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9282925..bbe9a6d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -59,6 +59,7 @@
         <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
     </string-array>
 
     <string translatable="false" name="status_bar_rotate">rotate</string>
@@ -94,6 +95,7 @@
     <string translatable="false" name="status_bar_camera">camera</string>
     <string translatable="false" name="status_bar_airplane">airplane</string>
     <string translatable="false" name="status_bar_sensors_off">sensors_off</string>
+    <string translatable="false" name="status_bar_screen_record">screen_record</string>
 
     <!-- Flag indicating whether the surface flinger has limited
          alpha compositing functionality in hardware.  If set, the window
@@ -2830,13 +2832,13 @@
          "logout" =  Logout the current user
          -->
     <string-array translatable="false" name="config_globalActionsList">
+        <item>emergency</item>
         <item>power</item>
         <item>restart</item>
         <item>lockdown</item>
         <item>logout</item>
         <item>bugreport</item>
         <item>screenshot</item>
-        <item>emergency</item>
     </string-array>
 
     <!-- Number of milliseconds to hold a wake lock to ensure that drawing is fully
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4172044..80e5d1b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3012,18 +3012,14 @@
       <public name="sdkVersion" />
       <!-- @hide @SystemApi -->
       <public name="minExtensionVersion" />
-      <public name="autofillInlineSuggestionChip" />
-      <public name="autofillInlineSuggestionTitle" />
-      <public name="autofillInlineSuggestionSubtitle" />
-      <!-- @hide @SystemApi -->
-      <public name="isAutofillInlineSuggestionTheme" />
+      <public name="allowNativeHeapPointerTagging" />
+      <public name="preserveLegacyExternalStorage" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b5">
     </public-group>
 
     <public-group type="style" first-id="0x010302e5">
-      <public name="Theme.AutofillInlineSuggestion" />
     </public-group>
 
     <public-group type="id" first-id="0x0102004a">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1ba8323..e6a93e5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4376,10 +4376,7 @@
     service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
     <string name="accessibility_shortcut_toogle_warning">
         When the shortcut is on, pressing both volume buttons for 3 seconds will start an
-        accessibility feature.\n\n
-        Current accessibility feature:\n
-        <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
-        You can change the feature in Settings > Accessibility.
+        accessibility feature.
     </string>
 
     <!-- Text in button that edit the accessibility shortcut menu, user can delete
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 751eca0..bcce1f0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1482,22 +1482,6 @@
         <item name="android:windowExitAnimation">@anim/slide_out_down</item>
     </style>
 
-    <!-- The style for the Autofill inline suggestion chip. -->
-    <!-- @hide -->
-    <style name="AutofillInlineSuggestionChip">
-        <item name="background">@drawable/autofill_dataset_picker_background</item>
-    </style>
-
-    <!-- @hide -->
-    <style name="AutofillInlineSuggestionTitle">
-        <item name="android:textAppearance">@style/TextAppearance</item>
-    </style>
-
-    <!-- @hide -->
-    <style name="AutofillInlineSuggestionSubtitle">
-        <item name="android:textAppearance">@style/TextAppearance.Small</item>
-    </style>
-
     <!-- The style for the container of media actions in a notification. -->
     <!-- @hide -->
     <style name="NotificationMediaActionContainer">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5aefe11..02d90a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2908,6 +2908,7 @@
   <java-symbol type="string" name="status_bar_microphone" />
   <java-symbol type="string" name="status_bar_camera" />
   <java-symbol type="string" name="status_bar_sensors_off" />
+  <java-symbol type="string" name="status_bar_screen_record" />
 
   <!-- Locale picker -->
   <java-symbol type="id" name="locale_search_menu" />
@@ -3260,7 +3261,6 @@
   <java-symbol type="layout" name="autofill_dataset_picker"/>
   <java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
   <java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
-  <java-symbol type="layout" name="autofill_inline_suggestion" />
   <java-symbol type="id" name="autofill" />
   <java-symbol type="id" name="autofill_dataset_footer"/>
   <java-symbol type="id" name="autofill_dataset_header"/>
@@ -3268,10 +3268,6 @@
   <java-symbol type="id" name="autofill_dataset_list"/>
   <java-symbol type="id" name="autofill_dataset_picker"/>
   <java-symbol type="id" name="autofill_dataset_title" />
-  <java-symbol type="id" name="autofill_inline_suggestion_end_icon" />
-  <java-symbol type="id" name="autofill_inline_suggestion_start_icon" />
-  <java-symbol type="id" name="autofill_inline_suggestion_subtitle" />
-  <java-symbol type="id" name="autofill_inline_suggestion_title" />
   <java-symbol type="id" name="autofill_save_custom_subtitle" />
   <java-symbol type="id" name="autofill_save_icon" />
   <java-symbol type="id" name="autofill_save_no" />
@@ -3902,4 +3898,6 @@
 
   <!-- Whether to expand the lock screen user switcher by default -->
   <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" />
+
+  <java-symbol type="string" name="loading" />
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 5e6dd82..5d9cb48 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -892,12 +892,4 @@
         <item name="windowContentTransitions">false</item>
         <item name="windowActivityTransitions">false</item>
     </style>
-
-    <!-- Theme for the Autofill inline suggestion on IME -->
-    <style name="Theme.AutofillInlineSuggestion" parent="Theme.DeviceDefault">
-        <item name="isAutofillInlineSuggestionTheme">true</item>
-        <item name="autofillInlineSuggestionChip">@style/AutofillInlineSuggestionChip</item>
-        <item name="autofillInlineSuggestionTitle">@style/AutofillInlineSuggestionTitle</item>
-        <item name="autofillInlineSuggestionSubtitle">@style/AutofillInlineSuggestionSubtitle</item>
-    </style>
 </resources>
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
index 9e94bdc..afe9d7f 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
@@ -44,7 +44,7 @@
         testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
         assetsProvider = DirectoryAssetsProvider(testDir)
         loader = ResourcesLoader()
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
     }
 
     @After
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
index e3ba93d..da5092d 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
@@ -119,7 +119,7 @@
 
         val loader = ResourcesLoader()
         loader.providers = listOf(one, two)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
 
         assertOpenedAsset()
         inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -149,7 +149,7 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(two)
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
 
         assertOpenedAsset()
         inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -170,7 +170,7 @@
         val loader = ResourcesLoader()
         val one = ResourcesProvider.empty(assetsProvider1)
         val two = ResourcesProvider.empty(assetsProvider2)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(one, two)
 
         assertOpenedAsset()
@@ -186,7 +186,7 @@
         val loader = ResourcesLoader()
         val one = ResourcesProvider.empty(assetsProvider1)
         val two = ResourcesProvider.empty(assetsProvider2)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(one, two)
 
         assertOpenedAsset()
@@ -202,7 +202,7 @@
         val loader = ResourcesLoader()
         val one = ResourcesProvider.empty(assetsProvider1)
         val two = ResourcesProvider.empty(assetsProvider2)
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(one, two)
 
         assertOpenedAsset()
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
index 0cc56d7..16eafcd 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -192,13 +192,13 @@
     }
 
     @Test
-    fun addMultipleProviders() {
+    fun addProvidersRepeatedly() {
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
@@ -213,25 +213,25 @@
     }
 
     @Test
-    fun addMultipleLoaders() {
+    fun addLoadersRepeatedly() {
         val originalValue = getValue()
         val testOne = openOne()
         val testTwo = openTwo()
         val loader1 = ResourcesLoader()
         val loader2 = ResourcesLoader()
 
-        resources.addLoader(loader1)
+        resources.addLoaders(loader1)
         loader1.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
-        resources.addLoader(loader2)
+        resources.addLoaders(loader2)
         loader2.addProvider(testTwo)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader2)
+        resources.removeLoaders(loader2)
         assertEquals(originalValue, getValue())
     }
 
@@ -242,7 +242,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(testOne, testTwo)
         assertEquals(valueTwo, getValue())
 
@@ -254,20 +254,20 @@
     }
 
     @Test
-    fun setMultipleLoaders() {
+    fun addMultipleLoaders() {
         val originalValue = getValue()
         val loader1 = ResourcesLoader()
         loader1.addProvider(openOne())
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader2)
+        resources.removeLoaders(loader2)
         assertEquals(valueOne, getValue())
 
-        resources.loaders = Collections.emptyList()
+        resources.removeLoaders(loader1)
         assertEquals(originalValue, getValue())
     }
 
@@ -291,7 +291,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         loader.addProvider(testTwo)
         loader.addProvider(testOne)
@@ -308,9 +308,9 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.addLoader(loader1)
-        resources.addLoader(loader2)
-        resources.addLoader(loader1)
+        resources.addLoaders(loader1)
+        resources.addLoaders(loader2)
+        resources.addLoaders(loader1)
 
         assertEquals(2, resources.loaders.size)
         assertEquals(resources.loaders[0], loader1)
@@ -323,7 +323,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         loader.addProvider(testTwo)
 
@@ -341,12 +341,16 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
-        resources.removeLoader(loader1)
-        resources.removeLoader(loader1)
+        resources.addLoaders(loader1, loader2)
+        resources.removeLoaders(loader1)
+        resources.removeLoaders(loader1)
 
         assertEquals(1, resources.loaders.size)
         assertEquals(resources.loaders[0], loader2)
+
+        resources.removeLoaders(loader2, loader2)
+
+        assertEquals(0, resources.loaders.size)
     }
 
     @Test
@@ -355,7 +359,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.providers = listOf(testOne, testTwo)
         loader.providers = listOf(testOne, testTwo)
 
@@ -365,14 +369,14 @@
     }
 
     @Test
-    fun repeatedSetLoaders() {
+    fun repeatedAddMultipleLoaders() {
         val loader1 = ResourcesLoader()
         loader1.addProvider(openOne())
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
 
         assertEquals(2, resources.loaders.size)
         assertEquals(resources.loaders[0], loader1)
@@ -386,7 +390,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         loader.addProvider(testTwo)
         assertEquals(valueTwo, getValue())
@@ -414,20 +418,20 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(testTwo)
 
-        resources.addLoader(loader1)
-        resources.addLoader(loader2)
+        resources.addLoaders(loader1)
+        resources.addLoaders(loader2)
         assertEquals(valueTwo, getValue())
 
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(valueTwo, getValue())
 
-        resources.addLoader(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueOne, getValue())
 
-        resources.removeLoader(loader2)
+        resources.removeLoaders(loader2)
         assertEquals(valueOne, getValue())
 
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(originalValue, getValue())
     }
 
@@ -444,10 +448,11 @@
         val loader2 = ResourcesLoader()
         loader2.providers = listOf(testThree, testFour)
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
         assertEquals(valueFour, getValue())
 
-        resources.loaders = listOf(loader2, loader1)
+        resources.removeLoaders(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueTwo, getValue())
 
         loader1.removeProvider(testTwo)
@@ -471,7 +476,7 @@
         val loader2 = ResourcesLoader()
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueOne, getValue())
 
         // The child context should include the loaders of the original context.
@@ -479,12 +484,12 @@
         assertEquals(valueOne, getValue(childContext))
 
         // Changing the loaders of the child context should not affect the original context.
-        childContext.resources.loaders = listOf(loader1, loader2)
+        childContext.resources.addLoaders(loader2)
         assertEquals(valueOne, getValue())
         assertEquals(valueTwo, getValue(childContext))
 
         // Changing the loaders of the original context should not affect the child context.
-        resources.removeLoader(loader1)
+        resources.removeLoaders(loader1)
         assertEquals(originalValue, getValue())
         assertEquals(valueTwo, getValue(childContext))
 
@@ -506,7 +511,7 @@
         val testTwo = openTwo()
         val loader = ResourcesLoader()
 
-        resources.addLoader(loader)
+        resources.addLoaders(loader)
         loader.addProvider(testOne)
         assertEquals(valueOne, getValue())
 
@@ -527,7 +532,7 @@
         assertEquals(originalValue, getValue())
         assertEquals(originalValue, getValue(childContext2))
 
-        childContext2.resources.addLoader(loader)
+        childContext2.resources.addLoaders(loader)
         assertEquals(originalValue, getValue())
         assertEquals(valueTwo, getValue(childContext))
         assertEquals(valueTwo, getValue(childContext2))
@@ -539,7 +544,7 @@
         loader.addProvider(openOne())
 
         val applicationContext = context.applicationContext
-        applicationContext.resources.addLoader(loader)
+        applicationContext.resources.addLoaders(loader)
         assertEquals(valueOne, getValue(applicationContext))
 
         val activity = mTestActivityRule.launchActivity(Intent())
@@ -556,7 +561,7 @@
         loader2.addProvider(openTwo())
 
         val applicationContext = context.applicationContext
-        applicationContext.resources.addLoader(loader1)
+        applicationContext.resources.addLoaders(loader1)
         assertEquals(valueOne, getValue(applicationContext))
 
         var token: IBinder? = null
@@ -569,7 +574,7 @@
             assertEquals(valueOne, getValue(applicationContext))
             assertEquals(valueOne, getValue(activity))
 
-            activity.resources.addLoader(loader2)
+            activity.resources.addLoaders(loader2)
             assertEquals(valueOne, getValue(applicationContext))
             assertEquals(valueTwo, getValue(activity))
 
@@ -598,10 +603,11 @@
         loader2.addProvider(provider1)
         loader2.addProvider(openTwo())
 
-        resources.loaders = listOf(loader1, loader2)
+        resources.addLoaders(loader1, loader2)
         assertEquals(valueTwo, getValue())
 
-        resources.loaders = listOf(loader2, loader1)
+        resources.removeLoaders(loader1)
+        resources.addLoaders(loader1)
         assertEquals(valueOne, getValue())
 
         assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index d1608d0..8d8acb7 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,6 +21,7 @@
 import android.app.NotificationHistory.HistoricalNotification;
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
+import android.util.Slog;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -48,6 +49,10 @@
         String expectedText = "text" + index;
         Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
                 index);
+        String conversationId = null;
+        if (index % 2 == 0) {
+            conversationId = "convo" + index;
+        }
 
         return new HistoricalNotification.Builder()
                 .setPackage(packageName)
@@ -59,6 +64,7 @@
                 .setTitle(expectedTitle)
                 .setText(expectedText)
                 .setIcon(expectedIcon)
+                .setConversationId(conversationId)
                 .build();
     }
 
@@ -74,6 +80,7 @@
         String expectedText = "text";
         Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
                 android.R.drawable.btn_star);
+        String expectedConversationId = "convo";
 
         HistoricalNotification n = new HistoricalNotification.Builder()
                 .setPackage(expectedPackage)
@@ -85,6 +92,7 @@
                 .setTitle(expectedTitle)
                 .setText(expectedText)
                 .setIcon(expectedIcon)
+                .setConversationId(expectedConversationId)
                 .build();
 
         assertThat(n.getPackage()).isEqualTo(expectedPackage);
@@ -96,6 +104,7 @@
         assertThat(n.getTitle()).isEqualTo(expectedTitle);
         assertThat(n.getText()).isEqualTo(expectedText);
         assertThat(expectedIcon.sameAs(n.getIcon())).isTrue();
+        assertThat(n.getConversationId()).isEqualTo(expectedConversationId);
     }
 
     @Test
@@ -153,6 +162,9 @@
             expectedStrings.add(n.getPackage());
             expectedStrings.add(n.getChannelName());
             expectedStrings.add(n.getChannelId());
+            if (n.getConversationId() != null) {
+                expectedStrings.add(n.getConversationId());
+            }
             history.addNotificationToWrite(n);
         }
 
@@ -180,6 +192,9 @@
             expectedStrings.add(n.getPackage());
             expectedStrings.add(n.getChannelName());
             expectedStrings.add(n.getChannelId());
+            if (n.getConversationId() != null) {
+                expectedStrings.add(n.getConversationId());
+            }
             history.addNotificationToWrite(n);
         }
 
@@ -212,6 +227,9 @@
                 postRemoveExpectedStrings.add(n.getPackage());
                 postRemoveExpectedStrings.add(n.getChannelName());
                 postRemoveExpectedStrings.add(n.getChannelId());
+                if (n.getConversationId() != null) {
+                    postRemoveExpectedStrings.add(n.getConversationId());
+                }
                 postRemoveExpectedEntries.add(n);
             }
 
@@ -221,14 +239,14 @@
         history.poolStringsFromNotifications();
 
         assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
-        // 2 package names and 10 * 2 unique channel names and ids
-        assertThat(history.getPooledStringsToWrite().length).isEqualTo(22);
+        // 2 package names and 10 * 2 unique channel names and ids and 5 conversation ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(27);
 
         history.removeNotificationsFromWrite("pkgOdd");
 
 
-        // 1 package names and 5 * 2 unique channel names and ids
-        assertThat(history.getPooledStringsToWrite().length).isEqualTo(11);
+        // 1 package names and 5 * 2 unique channel names and ids and 5 conversation ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(16);
         assertThat(history.getNotificationsToWrite())
                 .containsExactlyElementsIn(postRemoveExpectedEntries);
     }
@@ -246,6 +264,9 @@
                 postRemoveExpectedStrings.add(n.getPackage());
                 postRemoveExpectedStrings.add(n.getChannelName());
                 postRemoveExpectedStrings.add(n.getChannelId());
+                if (n.getConversationId() != null) {
+                    postRemoveExpectedStrings.add(n.getConversationId());
+                }
                 postRemoveExpectedEntries.add(n);
             }
 
@@ -255,14 +276,14 @@
         history.poolStringsFromNotifications();
 
         assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
-        // 1 package name and 10 unique channel names and ids
-        assertThat(history.getPooledStringsToWrite().length).isEqualTo(21);
+        // 1 package name and 20 unique channel names and ids and 5 conversation ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(26);
 
         history.removeNotificationFromWrite("pkg", 987654323);
 
 
-        // 1 package names and 9 * 2 unique channel names and ids
-        assertThat(history.getPooledStringsToWrite().length).isEqualTo(19);
+        // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(23);
         assertThat(history.getNotificationsToWrite())
                 .containsExactlyElementsIn(postRemoveExpectedEntries);
     }
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
index c897ace..693d4ca 100644
--- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -16,15 +16,20 @@
 
 package android.content.integrity;
 
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableMap;
 
-import org.testng.annotations.Test;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.util.Arrays;
 import java.util.Collections;
 
+@RunWith(JUnit4.class)
 public class InstallerAllowedByManifestFormulaTest {
 
     private static final InstallerAllowedByManifestFormula
@@ -70,7 +75,7 @@
     }
 
     @Test
-    public void testFormulaMatches_certificateNotInManifest() {
+    public void testFormulaMatches_certificateDoesNotMatchManifest() {
         AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
                 .setInstallerName("installer1")
                 .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
@@ -92,6 +97,19 @@
         assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
     }
 
+    @Test
+    public void testFormulaMatches_certificateNotSpecifiedInManifest() {
+        AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+                .setInstallerName("installer1")
+                .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
+                .setAllowedInstallersAndCert(ImmutableMap.of(
+                        "installer1", INSTALLER_CERTIFICATE_NOT_EVALUATED,
+                        "installer2", "installer_cert1"
+                )).build();
+
+        assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
+    }
+
     /** Returns a builder with all fields filled with some dummy data. */
     private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
         return new AppInstallMetadata.Builder()
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index e4dc993..4cc70ba 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -30,12 +30,23 @@
  * Unit tests for bundle that requires accessing hidden APS.  Tests that can be written only with
  * public APIs should go in the CTS counterpart.
  *
- * Run with:
- * bit FrameworksCoreTests:android.os.BundleTest
+ * Run with: atest FrameworksCoreTests:android.os.BundleTest
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BundleTest {
+
+    /**
+     * Take a bundle, write it to a parcel and return the parcel.
+     */
+    private Parcel getParcelledBundle(Bundle bundle) {
+        final Parcel p = Parcel.obtain();
+        // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+        bundle.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        return p;
+    }
+
     /**
      * Create a test bundle, parcel it and return the parcel.
      */
@@ -48,12 +59,7 @@
             pipe[1].close();
             source.putParcelable("fd", pipe[0]);
         }
-        final Parcel p = Parcel.obtain();
-        // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
-        source.writeToParcel(p, 0);
-        p.setDataPosition(0);
-
-        return p;
+        return getParcelledBundle(source);
     }
 
     /**
@@ -137,4 +143,78 @@
         checkBundle(b, withFd);
         p.recycle();
     }
+
+    @Test
+    public void kindofEquals_bothUnparcelled_same() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "S");
+        bundle2.putInt("IntKey", 2);
+
+        assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_bothUnparcelled_different() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "T");
+        bundle2.putLong("LongKey", 30L);
+
+        assertFalse(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_bothParcelled_same() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+        bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "S");
+        bundle2.putInt("IntKey", 2);
+        bundle2.readFromParcel(getParcelledBundle(bundle2));
+
+        assertTrue(bundle1.isParcelled());
+        assertTrue(bundle2.isParcelled());
+        assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_bothParcelled_different() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+        bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "T");
+        bundle2.putLong("LongKey", 5);
+        bundle2.readFromParcel(getParcelledBundle(bundle2));
+
+        assertTrue(bundle1.isParcelled());
+        assertTrue(bundle2.isParcelled());
+        assertFalse(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_ParcelledUnparcelled_empty() {
+        Bundle bundle1 = new Bundle();
+        bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+        Bundle bundle2 = new Bundle();
+
+        assertTrue(bundle1.isParcelled());
+        assertFalse(bundle2.isParcelled());
+        // Even though one is parcelled and the other is not, both are empty, so it should
+        // return true
+        assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index ce71beb..6d0e58b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -18,6 +18,7 @@
 
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -355,18 +356,14 @@
     public void hasOtherProfileOneOption() throws Exception {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
-
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         markWorkProfileUserAvailable();
+
+        ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
         Intent sendIntent = createSendTextIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(2);
-        ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
-        when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
-                Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
-
         final ChooserWrapperActivity activity = mActivityRule
                 .launchActivity(Intent.createChooser(sendIntent, null));
         waitForIdle();
@@ -382,9 +379,11 @@
 
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
-                createResolvedComponentsForTestWithOtherProfile(2);
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
         waitForIdle();
-        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
                 .perform(click());
         waitForIdle();
         assertThat(chosen[0], is(toChoose));
@@ -1218,17 +1217,7 @@
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
                 workProfileTargets);
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
         sendIntent.setType("TestType");
         markWorkProfileUserAvailable();
@@ -1245,7 +1234,7 @@
     }
 
     @Test
-    public void testWorkTab_workProfileHasExpectedNumberOfTargets() {
+    public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
@@ -1254,18 +1243,7 @@
                 createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
                 createResolvedComponentsForTest(workProfileTargets);
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
         sendIntent.setType("TestType");
 
@@ -1284,12 +1262,12 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> workResolvedComponentInfos =
                 createResolvedComponentsForTest(workProfileTargets);
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
         sendIntent.setType("TestType");
         ResolveInfo[] chosen = new ResolveInfo[1];
@@ -1312,6 +1290,85 @@
         assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
     }
 
+    @Test
+    public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        sOverrides.hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        final ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+
+        onView(withText(R.string.resolver_cant_share_with_work_apps))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_workProfileDisabled_emptyStateShown() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        sOverrides.isQuietModeEnabled = true;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        final ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_turn_on_work_apps))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_noWorkTargets_emptyStateShown() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        final ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
     private Intent createSendTextIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -1486,4 +1543,21 @@
     private void markWorkProfileUserAvailable() {
         sOverrides.workProfileUserHandle = UserHandle.of(10);
     }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index a68b5908..363551b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,15 +16,10 @@
 
 package com.android.internal.app;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
 import android.app.usage.UsageStatsManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -35,7 +30,6 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Size;
 
@@ -45,8 +39,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
-import org.mockito.Mockito;
-
+import java.util.List;
 import java.util.function.Function;
 
 public class ChooserWrapperActivity extends ChooserActivity {
@@ -56,6 +49,15 @@
     static final OverrideData sOverrides = new OverrideData();
     private UsageStatsManager mUsm;
 
+    @Override
+    protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+            Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
+        AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
+                super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
+        multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
+        return multiProfilePagerAdapter;
+    }
+
     ChooserListAdapter getAdapter() {
         return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
     }
@@ -206,6 +208,9 @@
         public int alternateProfileSetting;
         public Resources resources;
         public UserHandle workProfileUserHandle;
+        public boolean hasCrossProfileIntents;
+        public boolean isQuietModeEnabled;
+        public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
 
         public void reset() {
             onSafelyStartCallback = null;
@@ -221,6 +226,26 @@
             alternateProfileSetting = 0;
             resources = null;
             workProfileUserHandle = null;
+            hasCrossProfileIntents = true;
+            isQuietModeEnabled = false;
+            multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
+                @Override
+                public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
+                        int targetUserId) {
+                    return hasCrossProfileIntents;
+                }
+
+                @Override
+                public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+                    return isQuietModeEnabled;
+                }
+
+                @Override
+                public void requestQuietModeEnabled(boolean enabled,
+                        UserHandle workProfileUserHandle) {
+                    isQuietModeEnabled = enabled;
+                }
+            };
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 5f4194a..a7bf488 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -18,16 +18,16 @@
 
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.android.internal.app.MatcherUtils.first;
 import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
-import static com.android.internal.app.ResolverDataProvider.createResolvedComponentInfoWithOtherId;
 import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
 
 import static org.hamcrest.CoreMatchers.allOf;
@@ -56,9 +56,6 @@
 import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
 import com.android.internal.widget.ResolverDrawerLayout;
 
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -224,17 +221,14 @@
     public void hasOtherProfileOneOption() throws Exception {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
-
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         markWorkProfileUserAvailable();
+
+        ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
         Intent sendIntent = createSendImageIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(2);
-        ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
-
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
         Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
         waitForIdle();
@@ -249,8 +243,9 @@
         };
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
-                createResolvedComponentsForTestWithOtherProfile(2);
-        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
+        // We pick the first one as there is another one in the work profile side
+        onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
                 .perform(click());
         onView(withId(R.id.button_once))
                 .perform(click());
@@ -415,18 +410,14 @@
     }
 
     @Test
-    public void testWorkTab_workTabListEmptyBeforeGoingToTab() {
+    public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        setupResolverControllers(personalResolvedComponentInfos,
+                new ArrayList<>(workResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
         markWorkProfileUserAvailable();
 
@@ -434,8 +425,8 @@
         waitForIdle();
 
         assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
-        // The work list adapter must only be filled when we open the work tab
-        assertThat(activity.getWorkListAdapter().getCount(), is(0));
+        // The work list adapter must be populated in advance before tapping the other tab
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
     }
 
     @Test
@@ -445,17 +436,7 @@
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         markWorkProfileUserAvailable();
 
@@ -474,34 +455,7 @@
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        when(sOverrides.resolverListController.getResolversForIntentAsUser(
-                Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.resolverListController.getResolversForIntentAsUser(
-                Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle)))
-                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle)))
-                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         markWorkProfileUserAvailable();
 
@@ -521,18 +475,7 @@
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -552,18 +495,7 @@
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
@@ -597,35 +529,7 @@
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(1);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-
-        when(sOverrides.resolverListController.getResolversForIntentAsUser(
-                Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.resolverListController.getResolversForIntentAsUser(
-                Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle)))
-                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle)))
-                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
-        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class)))
-                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class),
-                eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -644,10 +548,10 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
-                Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
@@ -672,6 +576,82 @@
         assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
     }
 
+    @Test
+    public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        sOverrides.hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+
+        onView(withText(R.string.resolver_cant_share_with_work_apps))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_workProfileDisabled_emptyStateShown() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        sOverrides.isQuietModeEnabled = true;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_turn_on_work_apps))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_noWorkTargets_emptyStateShown() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -722,4 +702,21 @@
     private void markWorkProfileUserAvailable() {
         ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
     }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 36c8724..2087104 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -47,6 +47,15 @@
                 filterLastUsed, createListController(userHandle), useLayoutForBrowsables, this);
     }
 
+    @Override
+    protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+            Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
+        AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
+                super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
+        multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
+        return multiProfilePagerAdapter;
+    }
+
     ResolverWrapperAdapter getAdapter() {
         return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
     }
@@ -124,6 +133,9 @@
         public ResolverListController workResolverListController;
         public Boolean isVoiceInteraction;
         public UserHandle workProfileUserHandle;
+        public boolean hasCrossProfileIntents;
+        public boolean isQuietModeEnabled;
+        public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
 
         public void reset() {
             onSafelyStartCallback = null;
@@ -132,6 +144,26 @@
             resolverListController = mock(ResolverListController.class);
             workResolverListController = mock(ResolverListController.class);
             workProfileUserHandle = null;
+            hasCrossProfileIntents = true;
+            isQuietModeEnabled = false;
+            multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
+                @Override
+                public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
+                        int targetUserId) {
+                    return hasCrossProfileIntents;
+                }
+
+                @Override
+                public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+                    return isQuietModeEnabled;
+                }
+
+                @Override
+                public void requestQuietModeEnabled(boolean enabled,
+                        UserHandle workProfileUserHandle) {
+                    isQuietModeEnabled = enabled;
+                }
+            };
         }
     }
 }
\ No newline at end of file
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index af115b1..4b95e4d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -381,6 +381,8 @@
         <permission name="android.permission.REBOOT"/>
         <!-- Permission required for access VIBRATOR_STATE. -->
         <permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
+        <!-- Permission required for UsageStatsTest CTS test. -->
+        <permission name="android.permission.MANAGE_NOTIFICATIONS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 06d4fbd..ce8ff7d 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -867,7 +867,8 @@
         }
     }
 
-    private ColorSpace(
+    /** @hide */
+    ColorSpace(
             @NonNull String name,
             @NonNull Model model,
             @IntRange(from = MIN_ID, to = MAX_ID) int id) {
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
new file mode 100644
index 0000000..f9033a5
--- /dev/null
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace
+ * must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance
+ * that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}.
+ * TODO: Make public
+ * @hide
+ */
+public final class ParcelableColorSpace extends ColorSpace implements Parcelable {
+    private final ColorSpace mColorSpace;
+
+    /**
+     * Checks if the given ColorSpace is able to be parceled. A ColorSpace can only be
+     * parceled if it is a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb}
+     * instance that has an ICC parametric transfer function as returned by
+     * {@link Rgb#getTransferParameters()}
+     */
+    public static boolean isParcelable(@NonNull ColorSpace colorSpace) {
+        if (colorSpace.getId() == ColorSpace.MIN_ID) {
+            if (!(colorSpace instanceof ColorSpace.Rgb)) {
+                return false;
+            }
+            ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
+            if (rgb.getTransferParameters() == null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Constructs a new ParcelableColorSpace that wraps the provided ColorSpace.
+     *
+     * @param colorSpace The ColorSpace to wrap. The ColorSpace must be either named or be an
+     *                   RGB ColorSpace with an ICC parametric transfer function.
+     * @throws IllegalArgumentException If the provided ColorSpace does not satisfy the requirements
+     * to be parceled. See {@link #isParcelable(ColorSpace)}.
+     */
+    public ParcelableColorSpace(@NonNull ColorSpace colorSpace) {
+        super(colorSpace.getName(), colorSpace.getModel(), colorSpace.getId());
+        mColorSpace = colorSpace;
+
+        if (mColorSpace.getId() == ColorSpace.MIN_ID) {
+            if (!(mColorSpace instanceof ColorSpace.Rgb)) {
+                throw new IllegalArgumentException(
+                        "Unable to parcel unknown ColorSpaces that are not ColorSpace.Rgb");
+            }
+            ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace;
+            if (rgb.getTransferParameters() == null) {
+                throw new IllegalArgumentException("ColorSpace must use an ICC "
+                        + "parametric transfer function to be parcelable");
+            }
+        }
+    }
+
+    public @NonNull ColorSpace getColorSpace() {
+        return mColorSpace;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        final int id = mColorSpace.getId();
+        dest.writeInt(id);
+        if (id == ColorSpace.MIN_ID) {
+            // Not a named color space. We have to actually write, like, stuff. And things. Ugh.
+            // Cast is safe because this was asserted in the constructor
+            ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace;
+            dest.writeString(rgb.getName());
+            dest.writeFloatArray(rgb.getPrimaries());
+            dest.writeFloatArray(rgb.getWhitePoint());
+            ColorSpace.Rgb.TransferParameters transferParameters = rgb.getTransferParameters();
+            dest.writeDouble(transferParameters.a);
+            dest.writeDouble(transferParameters.b);
+            dest.writeDouble(transferParameters.c);
+            dest.writeDouble(transferParameters.d);
+            dest.writeDouble(transferParameters.e);
+            dest.writeDouble(transferParameters.f);
+            dest.writeDouble(transferParameters.g);
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<ParcelableColorSpace> CREATOR =
+            new Parcelable.Creator<ParcelableColorSpace>() {
+
+        public @NonNull ParcelableColorSpace createFromParcel(@NonNull Parcel in) {
+            final int id = in.readInt();
+            if (id == ColorSpace.MIN_ID) {
+                String name = in.readString();
+                float[] primaries = in.createFloatArray();
+                float[] whitePoint = in.createFloatArray();
+                double a = in.readDouble();
+                double b = in.readDouble();
+                double c = in.readDouble();
+                double d = in.readDouble();
+                double e = in.readDouble();
+                double f = in.readDouble();
+                double g = in.readDouble();
+                ColorSpace.Rgb.TransferParameters function =
+                        new ColorSpace.Rgb.TransferParameters(a, b, c, d, e, f, g);
+                return new ParcelableColorSpace(
+                        new ColorSpace.Rgb(name, primaries, whitePoint, function));
+            } else {
+                return new ParcelableColorSpace(ColorSpace.get(id));
+            }
+        }
+
+        public ParcelableColorSpace[] newArray(int size) {
+            return new ParcelableColorSpace[size];
+        }
+    };
+
+    @Override
+    public boolean isWideGamut() {
+        return mColorSpace.isWideGamut();
+    }
+
+    @Override
+    public float getMinValue(int component) {
+        return mColorSpace.getMinValue(component);
+    }
+
+    @Override
+    public float getMaxValue(int component) {
+        return mColorSpace.getMaxValue(component);
+    }
+
+    @Override
+    public @NonNull float[] toXyz(@NonNull float[] v) {
+        return mColorSpace.toXyz(v);
+    }
+
+    @Override
+    public @NonNull float[] fromXyz(@NonNull float[] v) {
+        return mColorSpace.fromXyz(v);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ParcelableColorSpace other = (ParcelableColorSpace) o;
+        return mColorSpace.equals(other.mColorSpace);
+    }
+
+    @Override
+    public int hashCode() {
+        return mColorSpace.hashCode();
+    }
+
+    /** @hide */
+    @Override
+    long getNativeInstance() {
+        return mColorSpace.getNativeInstance();
+    }
+}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 3835b2d..752695f 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -22,8 +22,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.animation.RenderNodeAnimator;
 import android.view.NativeVectorDrawableAnimator;
-import android.view.RenderNodeAnimator;
 import android.view.Surface;
 import android.view.View;
 
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
similarity index 89%
rename from core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
rename to graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
index d28ab07..36062c1 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.view.animation;
+package android.graphics.animation;
 
 import android.animation.TimeInterpolator;
 import android.util.TimeUtils;
@@ -26,14 +26,15 @@
  * with {@link HasNativeInterpolator}
  *
  * This implements TimeInterpolator to allow for easier interop with Animators
+ * @hide
  */
 @HasNativeInterpolator
-public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator {
+public class FallbackLUTInterpolator implements NativeInterpolator, TimeInterpolator {
 
     // If the duration of an animation is more than 300 frames, we cap the sample size to 300.
     private static final int MAX_SAMPLE_POINTS = 300;
     private TimeInterpolator mSourceInterpolator;
-    private final float mLut[];
+    private final float[] mLut;
 
     /**
      * Used to cache the float[] LUT for use across multiple native
@@ -50,7 +51,7 @@
         // We need 2 frame values as the minimal.
         int numAnimFrames = Math.max(2, (int) Math.ceil(((double) duration) / animIntervalMs));
         numAnimFrames = Math.min(numAnimFrames, MAX_SAMPLE_POINTS);
-        float values[] = new float[numAnimFrames];
+        float[] values = new float[numAnimFrames];
         float lastFrame = numAnimFrames - 1;
         for (int i = 0; i < numAnimFrames; i++) {
             float inValue = i / lastFrame;
@@ -61,7 +62,7 @@
 
     @Override
     public long createNativeInterpolator() {
-        return NativeInterpolatorFactoryHelper.createLutInterpolator(mLut);
+        return NativeInterpolatorFactory.createLutInterpolator(mLut);
     }
 
     /**
@@ -69,7 +70,7 @@
      */
     public static long createNativeInterpolator(TimeInterpolator interpolator, long duration) {
         float[] lut = createLUT(interpolator, duration);
-        return NativeInterpolatorFactoryHelper.createLutInterpolator(lut);
+        return NativeInterpolatorFactory.createLutInterpolator(lut);
     }
 
     @Override
diff --git a/core/java/com/android/internal/view/animation/HasNativeInterpolator.java b/graphics/java/android/graphics/animation/HasNativeInterpolator.java
similarity index 89%
rename from core/java/com/android/internal/view/animation/HasNativeInterpolator.java
rename to graphics/java/android/graphics/animation/HasNativeInterpolator.java
index 48ea4da..c53d5a4 100644
--- a/core/java/com/android/internal/view/animation/HasNativeInterpolator.java
+++ b/graphics/java/android/graphics/animation/HasNativeInterpolator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.view.animation;
+package android.graphics.animation;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -23,10 +23,10 @@
 
 /**
  * This is a class annotation that signals that it is safe to create
- * a native interpolator counterpart via {@link NativeInterpolatorFactory}
+ * a native interpolator counterpart via {@link NativeInterpolator}
  *
  * The idea here is to prevent subclasses of interpolators from being treated as a
- * NativeInterpolatorFactory, and instead have them fall back to the LUT & LERP
+ * NativeInterpolator, and instead have them fall back to the LUT & LERP
  * method like a custom interpolator.
  *
  * @hide
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/graphics/java/android/graphics/animation/NativeInterpolator.java
similarity index 71%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to graphics/java/android/graphics/animation/NativeInterpolator.java
index fcacd52..1e6fea8 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/graphics/java/android/graphics/animation/NativeInterpolator.java
@@ -14,8 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.internal.view.animation;
+package android.graphics.animation;
 
-public interface NativeInterpolatorFactory {
+/**
+ * @hide
+ */
+public interface NativeInterpolator {
+    /**
+     * Generates a native interpolator object that can be used by HardwareRenderer to draw
+     * RenderNodes.
+     * @return ptr to native object
+     */
     long createNativeInterpolator();
 }
diff --git a/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java b/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java
new file mode 100644
index 0000000..3886614
--- /dev/null
+++ b/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * Static utility class for constructing native interpolators to keep the
+ * JNI simpler
+ *
+ * @hide
+ */
+public final class NativeInterpolatorFactory {
+    private NativeInterpolatorFactory() {}
+
+    /**
+     * Create a native interpolator from the provided param generating a LUT variant if a native
+     * implementation does not exist.
+     */
+    public static long createNativeInterpolator(TimeInterpolator interpolator, long
+            duration) {
+        if (interpolator == null) {
+            return createLinearInterpolator();
+        } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+            return ((NativeInterpolator) interpolator).createNativeInterpolator();
+        } else {
+            return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
+        }
+    }
+
+    /** Creates a specialized native interpolator for Accelerate/Decelerate */
+    public static native long createAccelerateDecelerateInterpolator();
+    /** Creates a specialized native interpolator for Accelerate */
+    public static native long createAccelerateInterpolator(float factor);
+    /** Creates a specialized native interpolator for Anticipate */
+    public static native long createAnticipateInterpolator(float tension);
+    /** Creates a specialized native interpolator for Anticipate with Overshoot */
+    public static native long createAnticipateOvershootInterpolator(float tension);
+    /** Creates a specialized native interpolator for Bounce */
+    public static native long createBounceInterpolator();
+    /** Creates a specialized native interpolator for Cycle */
+    public static native long createCycleInterpolator(float cycles);
+    /** Creates a specialized native interpolator for Decelerate */
+    public static native long createDecelerateInterpolator(float factor);
+    /** Creates a specialized native interpolator for Linear interpolation */
+    public static native long createLinearInterpolator();
+    /** Creates a specialized native interpolator for Overshoot */
+    public static native long createOvershootInterpolator(float tension);
+    /** Creates a specialized native interpolator for along traveling along a Path */
+    public static native long createPathInterpolator(float[] x, float[] y);
+    /** Creates a specialized native interpolator for LUT */
+    public static native long createLutInterpolator(float[] values);
+}
diff --git a/graphics/java/android/graphics/animation/RenderNodeAnimator.java b/graphics/java/android/graphics/animation/RenderNodeAnimator.java
new file mode 100644
index 0000000..282b2f9
--- /dev/null
+++ b/graphics/java/android/graphics/animation/RenderNodeAnimator.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.RenderNode;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class RenderNodeAnimator extends Animator {
+    // Keep in sync with enum RenderProperty in Animator.h
+    public static final int TRANSLATION_X = 0;
+    public static final int TRANSLATION_Y = 1;
+    public static final int TRANSLATION_Z = 2;
+    public static final int SCALE_X = 3;
+    public static final int SCALE_Y = 4;
+    public static final int ROTATION = 5;
+    public static final int ROTATION_X = 6;
+    public static final int ROTATION_Y = 7;
+    public static final int X = 8;
+    public static final int Y = 9;
+    public static final int Z = 10;
+    public static final int ALPHA = 11;
+    // The last value in the enum, used for array size initialization
+    public static final int LAST_VALUE = ALPHA;
+
+    // Keep in sync with enum PaintFields in Animator.h
+    public static final int PAINT_STROKE_WIDTH = 0;
+
+    /**
+     * Field for the Paint alpha channel, which should be specified as a value
+     * between 0 and 255.
+     */
+    public static final int PAINT_ALPHA = 1;
+
+    private VirtualRefBasePtr mNativePtr;
+
+    private Handler mHandler;
+    private RenderNode mTarget;
+    private ViewListener mViewListener;
+    private int mRenderProperty = -1;
+    private float mFinalValue;
+    private TimeInterpolator mInterpolator;
+
+    private static final int STATE_PREPARE = 0;
+    private static final int STATE_DELAYED = 1;
+    private static final int STATE_RUNNING = 2;
+    private static final int STATE_FINISHED = 3;
+    private int mState = STATE_PREPARE;
+
+    private long mUnscaledDuration = 300;
+    private long mUnscaledStartDelay = 0;
+    // If this is true, we will run any start delays on the UI thread. This is
+    // the safe default, and is necessary to ensure start listeners fire at
+    // the correct time. Animators created by RippleDrawable (the
+    // CanvasProperty<> ones) do not have this expectation, and as such will
+    // set this to false so that the renderthread handles the startdelay instead
+    private final boolean mUiThreadHandlesDelay;
+    private long mStartDelay = 0;
+    private long mStartTime;
+
+    /**
+     * Interface used by the view system to update the view hierarchy in conjunction
+     * with this animator.
+     */
+    public interface ViewListener {
+        /** notify the listener that an alpha animation has begun. */
+        void onAlphaAnimationStart(float finalAlpha);
+        /** notify the listener that the animator has mutated a value that requires invalidation */
+        void invalidateParent(boolean forceRedraw);
+    }
+
+    public RenderNodeAnimator(int property, float finalValue) {
+        mRenderProperty = property;
+        mFinalValue = finalValue;
+        mUiThreadHandlesDelay = true;
+        init(nCreateAnimator(property, finalValue));
+    }
+
+    public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
+        init(nCreateCanvasPropertyFloatAnimator(
+                property.getNativeContainer(), finalValue));
+        mUiThreadHandlesDelay = false;
+    }
+
+    /**
+     * Creates a new render node animator for a field on a Paint property.
+     *
+     * @param property The paint property to target
+     * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
+     *            {@link #PAINT_STROKE_WIDTH}
+     * @param finalValue The target value for the property
+     */
+    public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
+        init(nCreateCanvasPropertyPaintAnimator(
+                property.getNativeContainer(), paintField, finalValue));
+        mUiThreadHandlesDelay = false;
+    }
+
+    public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
+        init(nCreateRevealAnimator(x, y, startRadius, endRadius));
+        mUiThreadHandlesDelay = true;
+    }
+
+    private void init(long ptr) {
+        mNativePtr = new VirtualRefBasePtr(ptr);
+    }
+
+    private void checkMutable() {
+        if (mState != STATE_PREPARE) {
+            throw new IllegalStateException("Animator has already started, cannot change it now!");
+        }
+        if (mNativePtr == null) {
+            throw new IllegalStateException("Animator's target has been destroyed "
+                    + "(trying to modify an animation after activity destroy?)");
+        }
+    }
+
+    static boolean isNativeInterpolator(TimeInterpolator interpolator) {
+        return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
+    }
+
+    private void applyInterpolator() {
+        if (mInterpolator == null || mNativePtr == null) return;
+
+        long ni;
+        if (isNativeInterpolator(mInterpolator)) {
+            ni = ((NativeInterpolator) mInterpolator).createNativeInterpolator();
+        } else {
+            long duration = nGetDuration(mNativePtr.get());
+            ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
+        }
+        nSetInterpolator(mNativePtr.get(), ni);
+    }
+
+    @Override
+    public void start() {
+        if (mTarget == null) {
+            throw new IllegalStateException("Missing target!");
+        }
+
+        if (mState != STATE_PREPARE) {
+            throw new IllegalStateException("Already started!");
+        }
+
+        mState = STATE_DELAYED;
+        if (mHandler == null) {
+            mHandler = new Handler(true);
+        }
+        applyInterpolator();
+
+        if (mNativePtr == null) {
+            // It's dead, immediately cancel
+            cancel();
+        } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
+            nSetStartDelay(mNativePtr.get(), mStartDelay);
+            doStart();
+        } else {
+            getHelper().addDelayedAnimation(this);
+        }
+    }
+
+    private void doStart() {
+        // Alpha is a special snowflake that has the canonical value stored
+        // in mTransformationInfo instead of in RenderNode, so we need to update
+        // it with the final value here.
+        if (mRenderProperty == RenderNodeAnimator.ALPHA && mViewListener != null) {
+            mViewListener.onAlphaAnimationStart(mFinalValue);
+        }
+
+        moveToRunningState();
+
+        if (mViewListener != null) {
+            // Kick off a frame to start the process
+            mViewListener.invalidateParent(false);
+        }
+    }
+
+    private void moveToRunningState() {
+        mState = STATE_RUNNING;
+        if (mNativePtr != null) {
+            nStart(mNativePtr.get());
+        }
+        notifyStartListeners();
+    }
+
+    private void notifyStartListeners() {
+        final ArrayList<AnimatorListener> listeners = cloneListeners();
+        final int numListeners = listeners == null ? 0 : listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            listeners.get(i).onAnimationStart(this);
+        }
+    }
+
+    @Override
+    public void cancel() {
+        if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
+            if (mState == STATE_DELAYED) {
+                getHelper().removeDelayedAnimation(this);
+                moveToRunningState();
+            }
+
+            final ArrayList<AnimatorListener> listeners = cloneListeners();
+            final int numListeners = listeners == null ? 0 : listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                listeners.get(i).onAnimationCancel(this);
+            }
+
+            end();
+        }
+    }
+
+    @Override
+    public void end() {
+        if (mState != STATE_FINISHED) {
+            if (mState < STATE_RUNNING) {
+                getHelper().removeDelayedAnimation(this);
+                doStart();
+            }
+            if (mNativePtr != null) {
+                nEnd(mNativePtr.get());
+                if (mViewListener != null) {
+                    // Kick off a frame to flush the state change
+                    mViewListener.invalidateParent(false);
+                }
+            } else {
+                // It's already dead, jump to onFinish
+                onFinished();
+            }
+        }
+    }
+
+    @Override
+    public void pause() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void resume() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** @hide */
+    public void setViewListener(ViewListener listener) {
+        mViewListener = listener;
+    }
+
+    /** Sets the animation target to the owning view of the RecordingCanvas */
+    public final void setTarget(RecordingCanvas canvas) {
+        setTarget(canvas.mNode);
+    }
+
+    /** Sets the node that is to be the target of this animation */
+    protected void setTarget(RenderNode node) {
+        checkMutable();
+        if (mTarget != null) {
+            throw new IllegalStateException("Target already set!");
+        }
+        nSetListener(mNativePtr.get(), this);
+        mTarget = node;
+        mTarget.addAnimator(this);
+    }
+
+    /** Set the start value for the animation */
+    public void setStartValue(float startValue) {
+        checkMutable();
+        nSetStartValue(mNativePtr.get(), startValue);
+    }
+
+    @Override
+    public void setStartDelay(long startDelay) {
+        checkMutable();
+        if (startDelay < 0) {
+            throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
+        }
+        mUnscaledStartDelay = startDelay;
+        mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
+    }
+
+    @Override
+    public long getStartDelay() {
+        return mUnscaledStartDelay;
+    }
+
+    @Override
+    public RenderNodeAnimator setDuration(long duration) {
+        checkMutable();
+        if (duration < 0) {
+            throw new IllegalArgumentException("duration must be positive; " + duration);
+        }
+        mUnscaledDuration = duration;
+        nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
+        return this;
+    }
+
+    @Override
+    public long getDuration() {
+        return mUnscaledDuration;
+    }
+
+    @Override
+    public long getTotalDuration() {
+        return mUnscaledDuration + mUnscaledStartDelay;
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mState == STATE_DELAYED || mState == STATE_RUNNING;
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mState != STATE_PREPARE;
+    }
+
+    @Override
+    public void setInterpolator(TimeInterpolator interpolator) {
+        checkMutable();
+        mInterpolator = interpolator;
+    }
+
+    @Override
+    public TimeInterpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    protected void onFinished() {
+        if (mState == STATE_PREPARE) {
+            // Unlikely but possible, the native side has been destroyed
+            // before we have started.
+            releaseNativePtr();
+            return;
+        }
+        if (mState == STATE_DELAYED) {
+            getHelper().removeDelayedAnimation(this);
+            notifyStartListeners();
+        }
+        mState = STATE_FINISHED;
+
+        final ArrayList<AnimatorListener> listeners = cloneListeners();
+        final int numListeners = listeners == null ? 0 : listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            listeners.get(i).onAnimationEnd(this);
+        }
+
+        // Release the native object, as it has a global reference to us. This
+        // breaks the cyclic reference chain, and allows this object to be
+        // GC'd
+        releaseNativePtr();
+    }
+
+    private void releaseNativePtr() {
+        if (mNativePtr != null) {
+            mNativePtr.release();
+            mNativePtr = null;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private ArrayList<AnimatorListener> cloneListeners() {
+        ArrayList<AnimatorListener> listeners = getListeners();
+        if (listeners != null) {
+            listeners = (ArrayList<AnimatorListener>) listeners.clone();
+        }
+        return listeners;
+    }
+
+    public long getNativeAnimator() {
+        return mNativePtr.get();
+    }
+
+    /**
+     * @return true if the animator was started, false if still delayed
+     */
+    private boolean processDelayed(long frameTimeMs) {
+        if (mStartTime == 0) {
+            mStartTime = frameTimeMs;
+        } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
+            doStart();
+            return true;
+        }
+        return false;
+    }
+
+    private static DelayedAnimationHelper getHelper() {
+        DelayedAnimationHelper helper = sAnimationHelper.get();
+        if (helper == null) {
+            helper = new DelayedAnimationHelper();
+            sAnimationHelper.set(helper);
+        }
+        return helper;
+    }
+
+    private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
+            new ThreadLocal<DelayedAnimationHelper>();
+
+    private static class DelayedAnimationHelper implements Runnable {
+
+        private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
+        private final Choreographer mChoreographer;
+        private boolean mCallbackScheduled;
+
+        DelayedAnimationHelper() {
+            mChoreographer = Choreographer.getInstance();
+        }
+
+        public void addDelayedAnimation(RenderNodeAnimator animator) {
+            mDelayedAnims.add(animator);
+            scheduleCallback();
+        }
+
+        public void removeDelayedAnimation(RenderNodeAnimator animator) {
+            mDelayedAnims.remove(animator);
+        }
+
+        private void scheduleCallback() {
+            if (!mCallbackScheduled) {
+                mCallbackScheduled = true;
+                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+            }
+        }
+
+        @Override
+        public void run() {
+            long frameTimeMs = mChoreographer.getFrameTime();
+            mCallbackScheduled = false;
+
+            int end = 0;
+            for (int i = 0; i < mDelayedAnims.size(); i++) {
+                RenderNodeAnimator animator = mDelayedAnims.get(i);
+                if (!animator.processDelayed(frameTimeMs)) {
+                    if (end != i) {
+                        mDelayedAnims.set(end, animator);
+                    }
+                    end++;
+                }
+            }
+            while (mDelayedAnims.size() > end) {
+                mDelayedAnims.remove(mDelayedAnims.size() - 1);
+            }
+
+            if (mDelayedAnims.size() > 0) {
+                scheduleCallback();
+            }
+        }
+    }
+
+    // Called by native
+    private static void callOnFinished(RenderNodeAnimator animator) {
+        if (animator.mHandler != null) {
+            animator.mHandler.post(animator::onFinished);
+        } else {
+            new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
+        }
+    }
+
+    @Override
+    public Animator clone() {
+        throw new IllegalStateException("Cannot clone this animator");
+    }
+
+    @Override
+    public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+        checkMutable();
+        nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
+    }
+
+    private static native long nCreateAnimator(int property, float finalValue);
+    private static native long nCreateCanvasPropertyFloatAnimator(
+            long canvasProperty, float finalValue);
+    private static native long nCreateCanvasPropertyPaintAnimator(
+            long canvasProperty, int paintField, float finalValue);
+    private static native long nCreateRevealAnimator(
+            int x, int y, float startRadius, float endRadius);
+
+    private static native void nSetStartValue(long nativePtr, float startValue);
+    private static native void nSetDuration(long nativePtr, long duration);
+    private static native long nGetDuration(long nativePtr);
+    private static native void nSetStartDelay(long nativePtr, long startDelay);
+    private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+    private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
+    private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
+
+    private static native void nStart(long animPtr);
+    private static native void nEnd(long animPtr);
+}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 1acf6c5..9fb72cf 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -42,6 +42,7 @@
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RenderNode;
+import android.graphics.animation.NativeInterpolatorFactory;
 import android.os.Build;
 import android.os.Handler;
 import android.util.ArrayMap;
@@ -54,7 +55,6 @@
 import android.util.TimeUtils;
 import android.view.Choreographer;
 import android.view.NativeVectorDrawableAnimator;
-import android.view.RenderNodeAnimatorSetHelper;
 import android.view.View;
 
 import com.android.internal.R;
@@ -1532,7 +1532,7 @@
             long startDelay = extraDelay + animator.getStartDelay();
             TimeInterpolator interpolator = animator.getInterpolator();
             long nativeInterpolator =
-                    RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
+                    NativeInterpolatorFactory.createNativeInterpolator(interpolator, duration);
 
             startDelay *= ValueAnimator.getDurationScale();
             duration *= ValueAnimator.getDurationScale();
@@ -1548,7 +1548,7 @@
          * to the last seen RenderNode target and start right away.
          */
         protected void recordLastSeenTarget(RecordingCanvas canvas) {
-            final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas);
+            final RenderNode node = canvas.mNode;
             mLastSeenTarget = new WeakReference<RenderNode>(node);
             // Add the animator to the list of animators on every draw
             if (mInitialized || mPendingAnimationActions.size() > 0) {
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index cce9ba3..0f37695 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -25,9 +25,9 @@
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
+import android.graphics.animation.RenderNodeAnimator;
 import android.util.FloatProperty;
 import android.util.MathUtils;
-import android.view.RenderNodeAnimator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index c520331..7c0af6d 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -152,8 +152,8 @@
             derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
             mReaderSecretKey = new SecretKeySpec(derivedKey, "AES");
 
-            mEphemeralCounter = 0;
-            mReadersExpectedEphemeralCounter = 0;
+            mEphemeralCounter = 1;
+            mReadersExpectedEphemeralCounter = 1;
 
         } catch (NoSuchAlgorithmException e) {
             throw new RuntimeException("Error performing key agreement", e);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index debb38b..81dedda 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -84,7 +84,6 @@
                 "libGLESv3",
                 "libvulkan",
                 "libui",
-                "libgui",
                 "libnativedisplay",
                 "libnativewindow",
                 "libprotobuf-cpp-lite",
@@ -289,6 +288,13 @@
     name: "hwui_test_defaults",
     defaults: ["hwui_defaults"],
     test_suites: ["device-tests"],
+    target: {
+        android: {
+            shared_libs: [
+	        "libgui",
+	    ],
+	}
+    },
     srcs: [
         "tests/common/scenes/*.cpp",
         "tests/common/LeakChecker.cpp",
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 89a9b99..84c07d7 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,16 +16,16 @@
 
 #include "Readback.h"
 
-#include "pipeline/skia/LayerDrawable.h"
-#include "renderthread/EglManager.h"
-#include "renderthread/VulkanManager.h"
-
-#include <gui/Surface.h>
-#include <ui/Fence.h>
+#include <sync/sync.h>
+#include <system/window.h>
 #include <ui/GraphicBuffer.h>
+
 #include "DeferredLayerUpdater.h"
 #include "Properties.h"
 #include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "renderthread/EglManager.h"
+#include "renderthread/VulkanManager.h"
 #include "utils/Color.h"
 #include "utils/MathUtils.h"
 #include "utils/TraceUtils.h"
@@ -35,40 +35,43 @@
 namespace android {
 namespace uirenderer {
 
-CopyResult Readback::copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) {
+CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap) {
     ATRACE_CALL();
     // Setup the source
-    sp<GraphicBuffer> sourceBuffer;
-    sp<Fence> sourceFence;
+    AHardwareBuffer* rawSourceBuffer;
+    int rawSourceFence;
     Matrix4 texTransform;
-    status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data);
+    status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence,
+                                                     texTransform.data);
+    base::unique_fd sourceFence(rawSourceFence);
     texTransform.invalidateType();
     if (err != NO_ERROR) {
         ALOGW("Failed to get last queued buffer, error = %d", err);
         return CopyResult::UnknownError;
     }
-    if (!sourceBuffer.get()) {
+    if (rawSourceBuffer == nullptr) {
         ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
         return CopyResult::SourceEmpty;
     }
-    if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) {
+
+    std::unique_ptr<AHardwareBuffer, decltype(&AHardwareBuffer_release)> sourceBuffer(
+            rawSourceBuffer, AHardwareBuffer_release);
+    AHardwareBuffer_Desc description;
+    AHardwareBuffer_describe(sourceBuffer.get(), &description);
+    if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
         ALOGW("Surface is protected, unable to copy from it");
         return CopyResult::SourceInvalid;
     }
-    err = sourceFence->wait(500 /* ms */);
-    if (err != NO_ERROR) {
+
+    if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
         ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
         return CopyResult::Timeout;
     }
-    if (!sourceBuffer.get()) {
-        return CopyResult::UnknownError;
-    }
 
-    sk_sp<SkColorSpace> colorSpace =
-            DataSpaceToColorSpace(static_cast<android_dataspace>(surface.getBuffersDataSpace()));
-    sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer(
-            reinterpret_cast<AHardwareBuffer*>(sourceBuffer.get()),
-            kPremul_SkAlphaType, colorSpace);
+    sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
+            static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+    sk_sp<SkImage> image =
+            SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
     return copyImageInto(image, texTransform, srcRect, bitmap);
 }
 
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index e86a813..e36f1ff 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -47,7 +47,7 @@
     /**
      * Copies the surface's most recently queued buffer into the provided bitmap.
      */
-    CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap);
+    CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
 
     CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
 
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 58cc08b..914c046 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -32,7 +32,6 @@
 
 #ifndef _WIN32
 #include <binder/IServiceManager.h>
-#include <private/gui/ComposerService.h>
 #endif
 #include <ui/PixelFormat.h>
 
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index e482cad..fc6e114 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -16,8 +16,10 @@
 
 #pragma once
 
-#include "SkiaPipeline.h"
+#include <EGL/egl.h>
+#include <system/window.h>
 
+#include "SkiaPipeline.h"
 #include "renderstate/RenderState.h"
 
 namespace android {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 29b4dd7..41aa1ff 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -24,18 +24,19 @@
 #include <SkOverdrawColorFilter.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
-#include <SkTypeface.h>
 #include <SkSerialProcs.h>
+#include <SkTypeface.h>
+#include <android-base/properties.h>
+#include <unistd.h>
+
+#include <sstream>
+
 #include "LightingInfo.h"
 #include "VectorDrawable.h"
 #include "thread/CommonPool.h"
 #include "tools/SkSharingProc.h"
-#include "utils/TraceUtils.h"
 #include "utils/String8.h"
-
-#include <unistd.h>
-
-#include <android-base/properties.h>
+#include "utils/TraceUtils.h"
 
 using namespace android::uirenderer::renderthread;
 
@@ -600,27 +601,24 @@
 // Overdraw debugging
 
 // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences.
-// This implementation:
-// (1) Requires transparent entries for "no overdraw" and "single draws".
-// (2) Requires premul colors (instead of unpremul).
-// (3) Requires RGBA colors (instead of BGRA).
-static const uint32_t kOverdrawColors[2][6] = {
-        {
-                0x00000000,
-                0x00000000,
-                0x2f2f0000,
-                0x2f002f00,
-                0x3f00003f,
-                0x7f00007f,
-        },
-        {
-                0x00000000,
-                0x00000000,
-                0x2f2f0000,
-                0x4f004f4f,
-                0x5f50335f,
-                0x7f00007f,
-        },
+// This implementation requires transparent entries for "no overdraw" and "single draws".
+static const SkColor kOverdrawColors[2][6] = {
+    {
+        0x00000000,
+        0x00000000,
+        0x2f0000ff,
+        0x2f00ff00,
+        0x3fff0000,
+        0x7fff0000,
+    },
+    {
+        0x00000000,
+        0x00000000,
+        0x2f0000ff,
+        0x4fffff00,
+        0x5fff89d7,
+        0x7fff0000,
+    },
 };
 
 void SkiaPipeline::renderOverdraw(const SkRect& clip,
@@ -642,8 +640,8 @@
 
     // Draw overdraw colors to the canvas.  The color filter will convert counts to colors.
     SkPaint paint;
-    const SkPMColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)];
-    paint.setColorFilter(SkOverdrawColorFilter::Make(colors));
+    const SkColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)];
+    paint.setColorFilter(SkOverdrawColorFilter::MakeWithSkColors(colors));
     surface->getCanvas()->drawImage(counts.get(), 0.0f, 0.0f, &paint);
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 91f9447..4299dd3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,15 +139,15 @@
     mAnimationContext->destroy();
 }
 
-void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) {
+void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
     ATRACE_CALL();
 
-    if (surface) {
-        mNativeSurface = std::make_unique<ReliableSurface>(std::move(surface));
+    if (window) {
+        mNativeSurface = std::make_unique<ReliableSurface>(window);
         mNativeSurface->init();
         if (enableTimeout) {
             // TODO: Fix error handling & re-shorten timeout
-            ANativeWindow_setDequeueTimeout(mNativeSurface->getNativeWindow(), 4000_ms);
+            ANativeWindow_setDequeueTimeout(window, 4000_ms);
         }
     } else {
         mNativeSurface = nullptr;
@@ -167,7 +167,7 @@
 
     mFrameNumber = -1;
 
-    if (hasSurface) {
+    if (window != nullptr && hasSurface) {
         mHaveNewSurface = true;
         mSwapHistory.clear();
         // Enable frame stats after the surface has been bound to the appropriate graphics API.
@@ -204,8 +204,7 @@
 
 void CanvasContext::allocateBuffers() {
     if (mNativeSurface) {
-        ANativeWindow* anw = mNativeSurface->getNativeWindow();
-        ANativeWindow_allocateBuffers(anw);
+        ANativeWindow_tryAllocateBuffers(mNativeSurface->getNativeWindow());
     }
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 629c741..0f1b8ae 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -35,7 +35,6 @@
 #include <SkRect.h>
 #include <SkSize.h>
 #include <cutils/compiler.h>
-#include <gui/Surface.h>
 #include <utils/Functor.h>
 
 #include <functional>
@@ -111,7 +110,7 @@
     // Won't take effect until next EGLSurface creation
     void setSwapBehavior(SwapBehavior swapBehavior);
 
-    void setSurface(sp<Surface>&& surface, bool enableTimeout = true);
+    void setSurface(ANativeWindow* window, bool enableTimeout = true);
     bool pauseSurface();
     void setStopped(bool stopped);
     bool hasSurface() const { return mNativeSurface.get(); }
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index c1fed26..5e0471c 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -20,7 +20,6 @@
 #include <GLES/gl.h>
 #include <cutils/properties.h>
 #include <log/log.h>
-#include <private/gui/SyncFeatures.h>
 #include <sync/sync.h>
 #include <utils/Trace.h>
 
@@ -79,6 +78,9 @@
     bool displayP3 = false;
     bool contextPriority = false;
     bool surfacelessContext = false;
+    bool nativeFenceSync = false;
+    bool fenceSync = false;
+    bool waitSync = false;
 } EglExtensions;
 
 EglManager::EglManager()
@@ -226,6 +228,9 @@
     EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3_passthrough");
     EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority");
     EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context");
+    EglExtensions.nativeFenceSync = extensions.has("EGL_ANDROID_native_fence_sync");
+    EglExtensions.fenceSync = extensions.has("EGL_KHR_fence_sync");
+    EglExtensions.waitSync = extensions.has("EGL_KHR_wait_sync");
 }
 
 bool EglManager::hasEglContext() {
@@ -527,8 +532,7 @@
         return INVALID_OPERATION;
     }
 
-    if (SyncFeatures::getInstance().useWaitSync() &&
-        SyncFeatures::getInstance().useNativeFenceSync()) {
+    if (EglExtensions.waitSync && EglExtensions.nativeFenceSync) {
         // Block GPU on the fence.
         // Create an EGLSyncKHR from the current fence.
         int fenceFd = ::dup(fence);
@@ -572,7 +576,7 @@
         return INVALID_OPERATION;
     }
 
-    if (SyncFeatures::getInstance().useNativeFenceSync()) {
+    if (EglExtensions.nativeFenceSync) {
         EGLSyncKHR sync = eglCreateSyncKHR(mEglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
         if (sync == EGL_NO_SYNC_KHR) {
             ALOGE("EglManager::createReleaseFence: error creating EGL fence: %#x", eglGetError());
@@ -589,7 +593,7 @@
         }
         *nativeFence = fenceFd;
         *eglFence = EGL_NO_SYNC_KHR;
-    } else if (useFenceSync && SyncFeatures::getInstance().useFenceSync()) {
+    } else if (useFenceSync && EglExtensions.fenceSync) {
         if (*eglFence != EGL_NO_SYNC_KHR) {
             // There is already a fence for the current slot.  We need to
             // wait on that before replacing it with another fence to
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index e92500f..8a0b4e8 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -16,7 +16,10 @@
 
 #include "ReliableSurface.h"
 
+#include <log/log_main.h>
 #include <private/android/AHardwareBufferHelpers.h>
+// TODO: this should be including apex instead.
+#include <vndk/window.h>
 
 namespace android::uirenderer::renderthread {
 
@@ -26,8 +29,9 @@
 // to propagate this error back to the caller
 constexpr bool DISABLE_BUFFER_PREFETCH = true;
 
-ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) {
-    LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr");
+ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) {
+    LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr");
+    ANativeWindow_acquire(mWindow);
 }
 
 ReliableSurface::~ReliableSurface() {
@@ -36,26 +40,27 @@
     // As a concrete example, if the underlying ANativeWindow is associated with
     // an EGLSurface that is still in use, then if we don't clear out the
     // interceptors then we walk into undefined behavior.
-    ANativeWindow_setCancelBufferInterceptor(mSurface.get(), nullptr, nullptr);
-    ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), nullptr, nullptr);
-    ANativeWindow_setQueueBufferInterceptor(mSurface.get(), nullptr, nullptr);
-    ANativeWindow_setPerformInterceptor(mSurface.get(), nullptr, nullptr);
+    ANativeWindow_setCancelBufferInterceptor(mWindow, nullptr, nullptr);
+    ANativeWindow_setDequeueBufferInterceptor(mWindow, nullptr, nullptr);
+    ANativeWindow_setQueueBufferInterceptor(mWindow, nullptr, nullptr);
+    ANativeWindow_setPerformInterceptor(mWindow, nullptr, nullptr);
+    ANativeWindow_release(mWindow);
 }
 
 void ReliableSurface::init() {
-    int result = ANativeWindow_setCancelBufferInterceptor(mSurface.get(), hook_cancelBuffer, this);
+    int result = ANativeWindow_setCancelBufferInterceptor(mWindow, hook_cancelBuffer, this);
     LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d",
                         result);
 
-    result = ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), hook_dequeueBuffer, this);
+    result = ANativeWindow_setDequeueBufferInterceptor(mWindow, hook_dequeueBuffer, this);
     LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d",
                         result);
 
-    result = ANativeWindow_setQueueBufferInterceptor(mSurface.get(), hook_queueBuffer, this);
+    result = ANativeWindow_setQueueBufferInterceptor(mWindow, hook_queueBuffer, this);
     LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d",
                         result);
 
-    result = ANativeWindow_setPerformInterceptor(mSurface.get(), hook_perform, this);
+    result = ANativeWindow_setPerformInterceptor(mWindow, hook_perform, this);
     LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d",
                         result);
 }
@@ -87,7 +92,7 @@
     ANativeWindowBuffer* buffer = nullptr;
 
     // Note that this calls back into our own hooked method.
-    int result = ANativeWindow_dequeueBuffer(mSurface.get(), &buffer, &fenceFd);
+    int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd);
 
     {
         std::lock_guard _lock{mMutex};
@@ -117,7 +122,7 @@
         // Note that clearReservedBuffer may be reentrant here, so
         // mReservedBuffer must be cleared once we reach here to avoid recursing
         // forever.
-        ANativeWindow_cancelBuffer(mSurface.get(), buffer, releaseFd);
+        ANativeWindow_cancelBuffer(mWindow, buffer, releaseFd);
     }
 }
 
@@ -239,10 +244,10 @@
             case ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY:
                 /* width */ va_arg(args, uint32_t);
                 /* height */ va_arg(args, uint32_t);
-                rs->mFormat = va_arg(args, PixelFormat);
+                rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
                 break;
             case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT:
-                rs->mFormat = va_arg(args, PixelFormat);
+                rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
                 break;
         }
     }
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index e3cd8c0..58cd067 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -16,12 +16,14 @@
 
 #pragma once
 
+#include <android-base/unique_fd.h>
 #include <apex/window.h>
-#include <gui/Surface.h>
+#include <utils/Errors.h>
 #include <utils/Macros.h>
 #include <utils/StrongPointer.h>
 
 #include <memory>
+#include <mutex>
 
 namespace android::uirenderer::renderthread {
 
@@ -29,7 +31,7 @@
     PREVENT_COPY_AND_ASSIGN(ReliableSurface);
 
 public:
-    ReliableSurface(sp<Surface>&& surface);
+    ReliableSurface(ANativeWindow* window);
     ~ReliableSurface();
 
     // Performs initialization that is not safe to do in the constructor.
@@ -37,12 +39,10 @@
     // passed as the data pointer is not safe.
     void init();
 
-    ANativeWindow* getNativeWindow() { return mSurface.get(); }
+    ANativeWindow* getNativeWindow() { return mWindow; }
 
     int reserveNext();
 
-    int query(int what, int* value) const { return mSurface->query(what, value); }
-
     int getAndClearError() {
         int ret = mBufferQueueState;
         mBufferQueueState = OK;
@@ -50,12 +50,12 @@
     }
 
 private:
-    sp<Surface> mSurface;
+    ANativeWindow* mWindow;
 
     mutable std::mutex mMutex;
 
     uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER;
-    PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888;
+    AHardwareBuffer_Format mFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
     std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{
             nullptr, AHardwareBuffer_release};
     ANativeWindowBuffer* mReservedBuffer = nullptr;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index f9e401a..b66a13d 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,8 +16,6 @@
 
 #include "RenderProxy.h"
 
-#include <gui/Surface.h>
-
 #include "DeferredLayerUpdater.h"
 #include "DisplayList.h"
 #include "Properties.h"
@@ -78,9 +76,11 @@
     mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
 }
 
-void RenderProxy::setSurface(const sp<Surface>& surface, bool enableTimeout) {
-    mRenderThread.queue().post([this, surf = surface, enableTimeout]() mutable {
-        mContext->setSurface(std::move(surf), enableTimeout);
+void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
+    ANativeWindow_acquire(window);
+    mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
+        mContext->setSurface(win, enableTimeout);
+        ANativeWindow_release(win);
     });
 }
 
@@ -314,11 +314,11 @@
             [context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); });
 }
 
-int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
+int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
                                  SkBitmap* bitmap) {
     auto& thread = RenderThread::getInstance();
     return static_cast<int>(thread.queue().runSync([&]() -> auto {
-        return thread.readback().copySurfaceInto(*surface, Rect(left, top, right, bottom), bitmap);
+        return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap);
     }));
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 4683e1d..3baeb2f 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -18,6 +18,7 @@
 #define RENDERPROXY_H_
 
 #include <SkBitmap.h>
+#include <android/native_window.h>
 #include <cutils/compiler.h>
 #include <utils/Functor.h>
 
@@ -69,7 +70,7 @@
     ANDROID_API bool loadSystemProperties();
     ANDROID_API void setName(const char* name);
 
-    ANDROID_API void setSurface(const sp<Surface>& surface, bool enableTimeout = true);
+    ANDROID_API void setSurface(ANativeWindow* window, bool enableTimeout = true);
     ANDROID_API void allocateBuffers();
     ANDROID_API bool pause();
     ANDROID_API void setStopped(bool stopped);
@@ -140,7 +141,7 @@
      */
     ANDROID_API void setRenderAheadDepth(int renderAhead);
 
-    ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right,
+    ANDROID_API static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
                                            int bottom, SkBitmap* bitmap);
     ANDROID_API static void prepareToDraw(Bitmap& bitmap);
 
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index f6cff1c..f4fce27 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -70,7 +70,7 @@
             magnifier->getSkBitmap(&temp);
             constexpr int x = 90;
             constexpr int y = 325;
-            RenderProxy::copySurfaceInto(renderTarget, x, y, x + magnifier->width(),
+            RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(),
                                          y + magnifier->height(), &temp);
         }
     }
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 3b6baa7..801cb7d 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -131,7 +131,7 @@
     ContextFactory factory;
     std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode.get(), &factory));
     proxy->loadSystemProperties();
-    proxy->setSurface(surface);
+    proxy->setSurface(surface.get());
     float lightX = width / 2.0;
     proxy->setLightAlpha(255 * 0.075, 255 * 0.15);
     proxy->setLightGeometry((Vector3){lightX, dp(-200.0f), dp(800.0f)}, dp(800.0f));
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 7d999c4..d08aea6 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -210,7 +210,7 @@
     auto surface = testContext.surface();
     int width = ANativeWindow_getWidth(surface.get());
     int height = ANativeWindow_getHeight(surface.get());
-    canvasContext->setSurface(std::move(surface));
+    canvasContext->setSurface(surface.get());
 
     TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
     DamageAccumulator damageAccumulator;
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
index b0af997..d2b7d5c 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -45,6 +45,12 @@
     defaults: ["libprotoutil_defaults"],
 
     export_include_dirs: ["include"],
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 cc_test {
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index f075a53..3dc7cfc 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -16,6 +16,8 @@
 
 package android.location;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Binder;
@@ -62,20 +64,24 @@
         }
 
         private void execute(Consumer<TListener> operation) {
-            mExecutor.execute(() -> {
-                TListener listener = mListener;
-                if (listener == null) {
-                    return;
-                }
+            mExecutor.execute(
+                    obtainRunnable(Registration<TRequest, TListener>::accept, this, operation)
+                            .recycleOnUse());
+        }
 
-                // we may be under the binder identity if a direct executor is used
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    operation.accept(listener);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            });
+        private void accept(Consumer<TListener> operation) {
+            TListener listener = mListener;
+            if (listener == null) {
+                return;
+            }
+
+            // we may be under the binder identity if a direct executor is used
+            long identity = Binder.clearCallingIdentity();
+            try {
+                operation.accept(listener);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
diff --git a/location/java/android/location/GnssMeasurementCorrections.java b/location/java/android/location/GnssMeasurementCorrections.java
index 19c3992..ffbe11a 100644
--- a/location/java/android/location/GnssMeasurementCorrections.java
+++ b/location/java/android/location/GnssMeasurementCorrections.java
@@ -179,6 +179,10 @@
      * Gets the environment bearing in degrees clockwise from true north, in the direction of user
      * motion. Environment bearing is provided when it is known with high probability that
      * velocity is aligned with an environment feature (such as edge of a building, or road).
+     *
+     * {@link #hasEnvironmentBearing} should be called to check the environment bearing is available
+     * before calling this method. The value is undefined if {@link #hasEnvironmentBearing} returns
+     * false.
      */
     @FloatRange(from = 0.0f, to = 360.0f)
     public float getEnvironmentBearingDegrees() {
@@ -186,7 +190,15 @@
     }
 
     /**
-     * Gets the environment bearing uncertainty in degrees.
+     * Gets the environment bearing uncertainty in degrees. It represents the standard deviation of
+     * the physical structure in the circle of position uncertainty. The uncertainty can take values
+     * between 0 and 180 degrees. The {@link #hasEnvironmentBearing} becomes false as the
+     * uncertainty value passes a predefined threshold depending on the physical structure around
+     * the user.
+     *
+     * {@link #hasEnvironmentBearing} should be called to check the environment bearing is available
+     * before calling this method. The value is undefined if {@link #hasEnvironmentBearing} returns
+     * false.
      */
     @FloatRange(from = 0.0f, to = 180.0f)
     public float getEnvironmentBearingUncertaintyDegrees() {
@@ -358,6 +370,8 @@
          * user motion. Environment bearing is provided when it is known with high probability
          * that velocity is aligned with an environment feature (such as edge of a building, or
          * road).
+         *
+         * Both the bearing and uncertainty must be set for the environment bearing to be valid.
          */
         @NonNull public Builder setEnvironmentBearingDegrees(
                 @FloatRange(from = 0.0f, to = 360.0f)
@@ -369,6 +383,8 @@
 
         /**
          * Sets the environment bearing uncertainty in degrees.
+         *
+         * Both the bearing and uncertainty must be set for the environment bearing to be valid.
          */
         @NonNull public Builder setEnvironmentBearingUncertaintyDegrees(
                 @FloatRange(from = 0.0f, to = 180.0f)
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 3d0765b..03e1c75 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -22,6 +22,8 @@
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -57,6 +59,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledRunnable;
 
 import java.util.Collections;
 import java.util.List;
@@ -81,8 +84,6 @@
 @RequiresFeature(PackageManager.FEATURE_LOCATION)
 public class LocationManager {
 
-    private static final String TAG = "LocationManager";
-
     /**
      * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a
      * specific package.
@@ -91,7 +92,7 @@
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
-    public static final long TARGETED_PENDING_INTENT = 148963590L;
+    private static final long TARGETED_PENDING_INTENT = 148963590L;
 
     /**
      * For apps targeting Android K and above, incomplete locations may not be passed to
@@ -2604,18 +2605,28 @@
                 return;
             }
 
-            mExecutor.execute(() -> {
-                Consumer<Location> consumer;
-                synchronized (GetCurrentLocationTransport.this) {
-                    if (mConsumer == null) {
-                        return;
-                    }
-                    consumer = mConsumer;
-                    cancel();
-                }
+            PooledRunnable runnable =
+                    obtainRunnable(GetCurrentLocationTransport::acceptResult, this, location)
+                            .recycleOnUse();
+            try {
+                mExecutor.execute(runnable);
+            } catch (RejectedExecutionException e) {
+                runnable.recycle();
+                throw e;
+            }
+        }
 
-                consumer.accept(location);
-            });
+        private void acceptResult(Location location) {
+            Consumer<Location> consumer;
+            synchronized (this) {
+                if (mConsumer == null) {
+                    return;
+                }
+                consumer = mConsumer;
+                cancel();
+            }
+
+            consumer.accept(location);
         }
     }
 
@@ -2649,30 +2660,36 @@
                 return;
             }
 
+            PooledRunnable runnable =
+                    obtainRunnable(LocationListenerTransport::acceptLocation, this, currentExecutor,
+                            location).recycleOnUse();
             try {
-                currentExecutor.execute(() -> {
-                    try {
-                        if (currentExecutor != mExecutor) {
-                            return;
-                        }
-
-                        // we may be under the binder identity if a direct executor is used
-                        long identity = Binder.clearCallingIdentity();
-                        try {
-                            mListener.onLocationChanged(location);
-                        } finally {
-                            Binder.restoreCallingIdentity(identity);
-                        }
-                    } finally {
-                        locationCallbackFinished();
-                    }
-                });
+                currentExecutor.execute(runnable);
             } catch (RejectedExecutionException e) {
+                runnable.recycle();
                 locationCallbackFinished();
                 throw e;
             }
         }
 
+        private void acceptLocation(Executor currentExecutor, Location location) {
+            try {
+                if (currentExecutor != mExecutor) {
+                    return;
+                }
+
+                // we may be under the binder identity if a direct executor is used
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    mListener.onLocationChanged(location);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            } finally {
+                locationCallbackFinished();
+            }
+        }
+
         @Override
         public void onProviderEnabled(String provider) {
             Executor currentExecutor = mExecutor;
@@ -2680,25 +2697,13 @@
                 return;
             }
 
+            PooledRunnable runnable =
+                    obtainRunnable(LocationListenerTransport::acceptProviderChange, this,
+                            currentExecutor, provider, true).recycleOnUse();
             try {
-                currentExecutor.execute(() -> {
-                    try {
-                        if (currentExecutor != mExecutor) {
-                            return;
-                        }
-
-                        // we may be under the binder identity if a direct executor is used
-                        long identity = Binder.clearCallingIdentity();
-                        try {
-                            mListener.onProviderEnabled(provider);
-                        } finally {
-                            Binder.restoreCallingIdentity(identity);
-                        }
-                    } finally {
-                        locationCallbackFinished();
-                    }
-                });
+                currentExecutor.execute(runnable);
             } catch (RejectedExecutionException e) {
+                runnable.recycle();
                 locationCallbackFinished();
                 throw e;
             }
@@ -2711,30 +2716,41 @@
                 return;
             }
 
+            PooledRunnable runnable =
+                    obtainRunnable(LocationListenerTransport::acceptProviderChange, this,
+                            currentExecutor, provider, false).recycleOnUse();
             try {
-                currentExecutor.execute(() -> {
-                    try {
-                        if (currentExecutor != mExecutor) {
-                            return;
-                        }
-
-                        // we may be under the binder identity if a direct executor is used
-                        long identity = Binder.clearCallingIdentity();
-                        try {
-                            mListener.onProviderDisabled(provider);
-                        } finally {
-                            Binder.restoreCallingIdentity(identity);
-                        }
-                    } finally {
-                        locationCallbackFinished();
-                    }
-                });
+                currentExecutor.execute(runnable);
             } catch (RejectedExecutionException e) {
+                runnable.recycle();
                 locationCallbackFinished();
                 throw e;
             }
         }
 
+        private void acceptProviderChange(Executor currentExecutor, String provider,
+                boolean enabled) {
+            try {
+                if (currentExecutor != mExecutor) {
+                    return;
+                }
+
+                // we may be under the binder identity if a direct executor is used
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    if (enabled) {
+                        mListener.onProviderEnabled(provider);
+                    } else {
+                        mListener.onProviderDisabled(provider);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            } finally {
+                locationCallbackFinished();
+            }
+        }
+
         @Override
         public void onRemoved() {
             // TODO: onRemoved is necessary to GC hanging listeners, but introduces some interesting
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0a0f7f6..53379b87 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1024,7 +1024,18 @@
                                                       String device_name,
                                                       int codecFormat);
     @UnsupportedAppUsage
-    public static native int setPhoneState(int state);
+    public static int setPhoneState(int state) {
+        Log.w(TAG, "Do not use this method! Use AudioManager.setMode() instead.");
+        return 0;
+    }
+    /**
+     * @hide
+     * Send the current audio mode to audio policy manager and audio HAL.
+     * @param state the audio mode
+     * @param uid the UID of the app owning the audio mode
+     * @return command completion status.
+     */
+    public static native int setPhoneState(int state, int uid);
     @UnsupportedAppUsage
     public static native int setForceUse(int usage, int config);
     @UnsupportedAppUsage
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 0e6ade5..2178393 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -875,12 +875,12 @@
         }
 
         /**
-         * @return the unmodifiable list of transferrable routes for the session.
+         * @return the unmodifiable list of transferable routes for the session.
          */
         @NonNull
-        public List<MediaRoute2Info> getTransferrableRoutes() {
+        public List<MediaRoute2Info> getTransferableRoutes() {
             synchronized (mControllerLock) {
-                return getRoutesWithIdsLocked(mSessionInfo.getTransferrableRoutes());
+                return getRoutesWithIdsLocked(mSessionInfo.getTransferableRoutes());
             }
         }
 
@@ -1033,12 +1033,12 @@
          * all of the following conditions:
          * <ul>
          * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
-         * <li>ID should be included in {@link #getTransferrableRoutes()}</li>
+         * <li>ID should be included in {@link #getTransferableRoutes()}</li>
          * </ul>
          * If the route doesn't meet any of above conditions, it will be ignored.
          *
          * @see #getSelectedRoutes()
-         * @see #getTransferrableRoutes()
+         * @see #getTransferableRoutes()
          * @see RoutingControllerCallback#onControllerUpdated
          */
         public void transferToRoute(@NonNull MediaRoute2Info route) {
@@ -1057,9 +1057,9 @@
                 return;
             }
 
-            List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
-            if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) {
-                Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+            List<MediaRoute2Info> transferableRoutes = getTransferableRoutes();
+            if (!checkRouteListContainsRouteId(transferableRoutes, route.getId())) {
+                Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
                 return;
             }
 
@@ -1156,7 +1156,7 @@
                     .map(MediaRoute2Info::getId).collect(Collectors.toList());
             List<String> deselectableRoutes = getDeselectableRoutes().stream()
                     .map(MediaRoute2Info::getId).collect(Collectors.toList());
-            List<String> transferrableRoutes = getTransferrableRoutes().stream()
+            List<String> transferableRoutes = getTransferableRoutes().stream()
                     .map(MediaRoute2Info::getId).collect(Collectors.toList());
 
             StringBuilder result = new StringBuilder()
@@ -1171,8 +1171,8 @@
                     .append(", deselectableRoutes={")
                     .append(deselectableRoutes)
                     .append("}")
-                    .append(", transferrableRoutes={")
-                    .append(transferrableRoutes)
+                    .append(", transferableRoutes={")
+                    .append(transferableRoutes)
                     .append("}")
                     .append(" }");
             return result.toString();
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 5ce291c..4801d44 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -259,7 +259,7 @@
     /**
      * Selects media route for the specified package name.
      *
-     * If the given route is {@link RoutingController#getTransferrableRoutes() a transferrable
+     * If the given route is {@link RoutingController#getTransferableRoutes() a transferable
      * route} of a routing session of the application, the session will be transferred to
      * the route. If not, a new routing session will be created.
      *
@@ -274,7 +274,7 @@
         //TODO: instead of release all controllers, add an API to specify controllers that
         // should be released (or is the system controller).
         for (RoutingSessionInfo sessionInfo : getRoutingSessions(packageName)) {
-            if (!transferred && sessionInfo.getTransferrableRoutes().contains(route.getId())) {
+            if (!transferred && sessionInfo.getTransferableRoutes().contains(route.getId())) {
                 new RoutingController(sessionInfo).transferToRoute(route);
                 transferred = true;
             } else if (!sessionInfo.isSystemSession()) {
@@ -543,13 +543,13 @@
         }
 
         /**
-         * @return the unmodifiable list of transferrable routes for the session.
+         * @return the unmodifiable list of transferable routes for the session.
          */
         @NonNull
-        public List<MediaRoute2Info> getTransferrableRoutes() {
+        public List<MediaRoute2Info> getTransferableRoutes() {
             List<String> routeIds;
             synchronized (mControllerLock) {
-                routeIds = mSessionInfo.getTransferrableRoutes();
+                routeIds = mSessionInfo.getTransferableRoutes();
             }
             return getRoutesWithIds(routeIds);
         }
@@ -643,12 +643,12 @@
          * all of the following conditions:
          * <ul>
          * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
-         * <li>ID should be included in {@link #getTransferrableRoutes()}</li>
+         * <li>ID should be included in {@link #getTransferableRoutes()}</li>
          * </ul>
          * If the route doesn't meet any of above conditions, it will be ignored.
          *
          * @see #getSelectedRoutes()
-         * @see #getTransferrableRoutes()
+         * @see #getTransferableRoutes()
          */
         public void transferToRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
@@ -663,8 +663,8 @@
                 return;
             }
 
-            if (!sessionInfo.getTransferrableRoutes().contains(route.getId())) {
-                Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+            if (!sessionInfo.getTransferableRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
                 return;
             }
 
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 05fa511..2bffe8a 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -156,9 +156,7 @@
             }
             BackgroundThread.getExecutor().execute(() -> {
                 final Uri uri = scanFileQuietly(mProvider, new File(path));
-                if (mClient != null) {
-                    mClient.onScanCompleted(path, uri);
-                }
+                runCallBack(mContext, mClient, path, uri);
             });
         }
     }
@@ -187,9 +185,7 @@
                     .acquireContentProviderClient(MediaStore.AUTHORITY)) {
                 for (String path : paths) {
                     final Uri uri = scanFileQuietly(client, new File(path));
-                    if (callback != null) {
-                        callback.onScanCompleted(path, uri);
-                    }
+                    runCallBack(context, callback, path, uri);
                 }
             }
         });
@@ -206,6 +202,23 @@
         return uri;
     }
 
+    private static void runCallBack(Context context, OnScanCompletedListener callback,
+            String path, Uri uri) {
+        if (callback != null) {
+            // Ignore exceptions from callback to avoid calling app from crashing.
+            // Don't ignore exceptions for apps targeting 'R' or higher.
+            try {
+                callback.onScanCompleted(path, uri);
+            } catch (Throwable e) {
+                if (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) {
+                    throw e;
+                } else {
+                    Log.w(TAG, "Ignoring exception from callback for backward compatibility", e);
+                }
+            }
+        }
+    }
+
     @Deprecated
     static class ClientProxy implements MediaScannerConnectionClient {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index ebcb9ed..2e038e6 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -31,8 +31,16 @@
 import java.util.Set;
 
 /**
- * A media route discovery preference describing the kinds of routes that media router
+ * A media route discovery preference describing the features of routes that media router
  * would like to discover and whether to perform active scanning.
+ * <p>
+ * When {@link MediaRouter2} instances set discovery preferences by calling
+ * {@link MediaRouter2#registerRouteCallback}, they are merged into a single discovery preference
+ * and it is delivered to call {@link MediaRoute2ProviderService#onDiscoveryPreferenceChanged}.
+ * </p><p>
+ * According to the given discovery preference, {@link MediaRoute2ProviderService} discovers
+ * routes and publishes them.
+ * </p>
  *
  * @see MediaRouter2#registerRouteCallback
  */
@@ -53,12 +61,12 @@
 
     @NonNull
     private final List<String> mPreferredFeatures;
-    private final boolean mActiveScan;
+    private final boolean mShouldPerformActiveScan;
     @Nullable
     private final Bundle mExtras;
 
     /**
-     * An empty discovery preference.
+     * An empty discovery preference
      * @hide
      */
     public static final RouteDiscoveryPreference EMPTY =
@@ -66,23 +74,39 @@
 
     RouteDiscoveryPreference(@NonNull Builder builder) {
         mPreferredFeatures = builder.mPreferredFeatures;
-        mActiveScan = builder.mActiveScan;
+        mShouldPerformActiveScan = builder.mActiveScan;
         mExtras = builder.mExtras;
     }
 
     RouteDiscoveryPreference(@NonNull Parcel in) {
         mPreferredFeatures = in.createStringArrayList();
-        mActiveScan = in.readBoolean();
+        mShouldPerformActiveScan = in.readBoolean();
         mExtras = in.readBundle();
     }
 
+    /**
+     * Gets the features of routes that media router would like to discover.
+     * <p>
+     * Routes that have at least one of the features will be discovered.
+     * They may include predefined features such as
+     * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+     * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+     * </p>
+     */
     @NonNull
     public List<String> getPreferredFeatures() {
         return mPreferredFeatures;
     }
 
-    public boolean isActiveScan() {
-        return mActiveScan;
+    /**
+     * Gets whether active scanning should be performed.
+     * <p>
+     * If any of discovery preferences sets this as {@code true}, active scanning will
+     * be performed regardless of other discovery preferences.
+     * </p>
+     */
+    public boolean shouldPerformActiveScan() {
+        return mShouldPerformActiveScan;
     }
 
     /**
@@ -100,7 +124,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStringList(mPreferredFeatures);
-        dest.writeBoolean(mActiveScan);
+        dest.writeBoolean(mShouldPerformActiveScan);
         dest.writeBundle(mExtras);
     }
 
@@ -112,7 +136,7 @@
                 .append(String.join(", ", mPreferredFeatures))
                 .append("}")
                 .append(", activeScan=")
-                .append(mActiveScan)
+                .append(mShouldPerformActiveScan)
                 .append(" }");
 
         return result.toString();
@@ -128,12 +152,12 @@
         }
         RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
         return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
-                && mActiveScan == other.mActiveScan;
+                && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPreferredFeatures, mActiveScan);
+        return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
     }
 
     /**
@@ -154,12 +178,12 @@
             Objects.requireNonNull(preference, "preference must not be null");
 
             mPreferredFeatures = preference.getPreferredFeatures();
-            mActiveScan = preference.isActiveScan();
+            mActiveScan = preference.shouldPerformActiveScan();
             mExtras = preference.getExtras();
         }
 
         /**
-         * A constructor to combine all of the preferences into a single preference .
+         * A constructor to combine all of the preferences into a single preference.
          * It ignores extras of preferences.
          *
          * @hide
@@ -171,13 +195,19 @@
             mActiveScan = false;
             for (RouteDiscoveryPreference preference : preferences) {
                 routeFeatureSet.addAll(preference.mPreferredFeatures);
-                mActiveScan |= preference.mActiveScan;
+                mActiveScan |= preference.mShouldPerformActiveScan;
             }
             mPreferredFeatures = new ArrayList<>(routeFeatureSet);
         }
 
         /**
          * Sets preferred route features to discover.
+         * @param preferredFeatures features of routes that media router would like to discover.
+         *                          May include predefined features
+         *                          such as {@link MediaRoute2Info#FEATURE_LIVE_AUDIO},
+         *                          {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+         *                          or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK}
+         *                          or custom features defined by a provider.
          */
         @NonNull
         public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
@@ -188,9 +218,13 @@
 
         /**
          * Sets if active scanning should be performed.
+         * <p>
+         * Since active scanning uses more system resources, set this as {@code true} only
+         * when it's necessary.
+         * </p>
          */
         @NonNull
-        public Builder setActiveScan(boolean activeScan) {
+        public Builder setShouldPerformActiveScan(boolean activeScan) {
             mActiveScan = activeScan;
             return this;
         }
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 0d4e666..19a46ce 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -55,7 +55,7 @@
     final List<String> mSelectedRoutes;
     final List<String> mSelectableRoutes;
     final List<String> mDeselectableRoutes;
-    final List<String> mTransferrableRoutes;
+    final List<String> mTransferableRoutes;
 
     final int mVolumeHandling;
     final int mVolumeMax;
@@ -79,8 +79,8 @@
                 convertToUniqueRouteIds(builder.mSelectableRoutes));
         mDeselectableRoutes = Collections.unmodifiableList(
                 convertToUniqueRouteIds(builder.mDeselectableRoutes));
-        mTransferrableRoutes = Collections.unmodifiableList(
-                convertToUniqueRouteIds(builder.mTransferrableRoutes));
+        mTransferableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mTransferableRoutes));
 
         mVolumeHandling = builder.mVolumeHandling;
         mVolumeMax = builder.mVolumeMax;
@@ -100,7 +100,7 @@
         mSelectedRoutes = ensureList(src.createStringArrayList());
         mSelectableRoutes = ensureList(src.createStringArrayList());
         mDeselectableRoutes = ensureList(src.createStringArrayList());
-        mTransferrableRoutes = ensureList(src.createStringArrayList());
+        mTransferableRoutes = ensureList(src.createStringArrayList());
 
         mVolumeHandling = src.readInt();
         mVolumeMax = src.readInt();
@@ -193,11 +193,11 @@
     }
 
     /**
-     * Gets the list of ids of transferrable routes for the session.
+     * Gets the list of ids of transferable routes for the session.
      */
     @NonNull
-    public List<String> getTransferrableRoutes() {
-        return mTransferrableRoutes;
+    public List<String> getTransferableRoutes() {
+        return mTransferableRoutes;
     }
 
     /**
@@ -260,7 +260,7 @@
         dest.writeStringList(mSelectedRoutes);
         dest.writeStringList(mSelectableRoutes);
         dest.writeStringList(mDeselectableRoutes);
-        dest.writeStringList(mTransferrableRoutes);
+        dest.writeStringList(mTransferableRoutes);
         dest.writeInt(mVolumeHandling);
         dest.writeInt(mVolumeMax);
         dest.writeInt(mVolume);
@@ -284,7 +284,7 @@
                 && Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
                 && Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
                 && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
-                && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes)
+                && Objects.equals(mTransferableRoutes, other.mTransferableRoutes)
                 && (mVolumeHandling == other.mVolumeHandling)
                 && (mVolumeMax == other.mVolumeMax)
                 && (mVolume == other.mVolume);
@@ -293,7 +293,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mId, mClientPackageName, mProviderId,
-                mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes,
+                mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
                 mVolumeMax, mVolumeHandling, mVolume);
     }
 
@@ -311,8 +311,8 @@
                 .append(", deselectableRoutes={")
                 .append(String.join(",", mDeselectableRoutes))
                 .append("}")
-                .append(", transferrableRoutes={")
-                .append(String.join(",", mTransferrableRoutes))
+                .append(", transferableRoutes={")
+                .append(String.join(",", mTransferableRoutes))
                 .append("}")
                 .append(", volumeHandling=").append(getVolumeHandling())
                 .append(", volumeMax=").append(getVolumeMax())
@@ -350,7 +350,7 @@
         final List<String> mSelectedRoutes;
         final List<String> mSelectableRoutes;
         final List<String> mDeselectableRoutes;
-        final List<String> mTransferrableRoutes;
+        final List<String> mTransferableRoutes;
         int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
         int mVolumeMax;
         int mVolume;
@@ -381,7 +381,7 @@
             mSelectedRoutes = new ArrayList<>();
             mSelectableRoutes = new ArrayList<>();
             mDeselectableRoutes = new ArrayList<>();
-            mTransferrableRoutes = new ArrayList<>();
+            mTransferableRoutes = new ArrayList<>();
         }
 
         /**
@@ -400,7 +400,7 @@
             mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
             mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
             mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
-            mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+            mTransferableRoutes = new ArrayList<>(sessionInfo.mTransferableRoutes);
 
             mVolumeHandling = sessionInfo.mVolumeHandling;
             mVolumeMax = sessionInfo.mVolumeMax;
@@ -524,35 +524,35 @@
         }
 
         /**
-         * Clears the transferrable routes.
+         * Clears the transferable routes.
          */
         @NonNull
-        public Builder clearTransferrableRoutes() {
-            mTransferrableRoutes.clear();
+        public Builder clearTransferableRoutes() {
+            mTransferableRoutes.clear();
             return this;
         }
 
         /**
-         * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
+         * Adds a route to the transferable routes. The {@code routeId} must not be empty.
          */
         @NonNull
-        public Builder addTransferrableRoute(@NonNull String routeId) {
+        public Builder addTransferableRoute(@NonNull String routeId) {
             if (TextUtils.isEmpty(routeId)) {
                 throw new IllegalArgumentException("routeId must not be empty");
             }
-            mTransferrableRoutes.add(routeId);
+            mTransferableRoutes.add(routeId);
             return this;
         }
 
         /**
-         * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
+         * Removes a route from the transferable routes. The {@code routeId} must not be empty.
          */
         @NonNull
-        public Builder removeTransferrableRoute(@NonNull String routeId) {
+        public Builder removeTransferableRoute(@NonNull String routeId) {
             if (TextUtils.isEmpty(routeId)) {
                 throw new IllegalArgumentException("routeId must not be empty");
             }
-            mTransferrableRoutes.remove(routeId);
+            mTransferableRoutes.remove(routeId);
             return this;
         }
 
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 9b183a3..4db1109 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -532,6 +532,8 @@
         Filter filter = nativeOpenFilter(
                 mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
         if (filter != null) {
+            filter.setMainType(mainType);
+            filter.setSubtype(subType);
             filter.setCallback(cb);
             if (mHandler == null) {
                 mHandler = createEventHandler();
@@ -610,10 +612,10 @@
      *
      * @return  a {@link Descrambler} object.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
     @Nullable
     public Descrambler openDescrambler() {
-        TunerUtils.checkTunerPermission(mContext);
+        TunerUtils.checkDescramblerPermission(mContext);
         return nativeOpenDescrambler();
     }
 
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index f54808d..82af658 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -38,6 +38,14 @@
      * Invalid stream ID.
      */
     public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
+    /**
+     * Invalid filter ID.
+     */
+    public static final int INVALID_FILTER_ID = Constants.Constant.INVALID_FILTER_ID;
+    /**
+     * Invalid AV Sync ID.
+     */
+    public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID;
 
     /** @hide */
     @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index 30aaa02..5ecb8f0 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -27,7 +27,9 @@
  * @hide
  */
 public final class TunerUtils {
-    private static final String PERMISSION = android.Manifest.permission.ACCESS_TV_TUNER;
+    private static final String TUNER_PERMISSION = android.Manifest.permission.ACCESS_TV_TUNER;
+    private static final String DESCRAMBLER_PERMISSION =
+            android.Manifest.permission.ACCESS_TV_DESCRAMBLER;
 
     /**
      * Checks whether the caller has permission to access tuner.
@@ -36,9 +38,30 @@
      * @throws SecurityException if the caller doesn't have the permission.
      */
     public static void checkTunerPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(PERMISSION)
+        checkPermission(context, TUNER_PERMISSION);
+    }
+
+    /**
+     * Checks whether the caller has permission to access the descrambler.
+     *
+     * @param context context of the caller.
+     * @throws SecurityException if the caller doesn't have the permission.
+     */
+    public static void checkDescramblerPermission(Context context) {
+        checkPermission(context, DESCRAMBLER_PERMISSION);
+    }
+
+    /**
+     * Checks whether the caller has the given permission.
+     *
+     * @param context context of the caller.
+     * @param permission the given permission.
+     * @throws SecurityException if the caller doesn't have the permission.
+     */
+    public static void checkPermission(Context context, String permission) {
+        if (context.checkCallingOrSelfPermission(permission)
                 != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Caller must have " + PERMISSION + " permission.");
+            throw new SecurityException("Caller must have " + permission + " permission.");
         }
     }
 
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 06de6e8..a98183b 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.hardware.tv.tuner.V1_0.Constants;
 import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.TunerUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -177,6 +178,8 @@
     private long mNativeContext;
     private FilterCallback mCallback;
     private final int mId;
+    private int mMainType;
+    private int mSubtype;
 
     private native int nativeConfigureFilter(
             int type, int subType, FilterConfiguration settings);
@@ -197,6 +200,15 @@
     }
 
     /** @hide */
+    public void setMainType(@Type int mainType) {
+        mMainType = mainType;
+    }
+    /** @hide */
+    public void setSubtype(@Subtype int subtype) {
+        mSubtype = subtype;
+    }
+
+    /** @hide */
     public void setCallback(FilterCallback cb) {
         mCallback = cb;
     }
@@ -213,10 +225,13 @@
      */
     @Result
     public int configure(@NonNull FilterConfiguration config) {
-        int subType = -1;
+        // TODO: validate main type, subtype, config, settings
+        int subType;
         Settings s = config.getSettings();
         if (s != null) {
             subType = s.getType();
+        } else {
+            subType = TunerUtils.getFilterSubtype(mMainType, mSubtype);
         }
         return nativeConfigureFilter(config.getType(), subType, config);
     }
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index bf5aaed..a8dbfa5 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -160,6 +160,12 @@
          */
         @NonNull
         public IpFilterConfiguration build() {
+            int ipAddrLength = mSrcIpAddress.length;
+            if (ipAddrLength != mDstIpAddress.length || (ipAddrLength != 4 && ipAddrLength != 16)) {
+                throw new IllegalArgumentException(
+                    "The lengths of src and dst IP address must be 4 or 16 and must be the same."
+                            + "srcLength=" + ipAddrLength + ", dstLength=" + mDstIpAddress.length);
+            }
             return new IpFilterConfiguration(
                     mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
         }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 08c3f98..ac59003 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -27,16 +27,36 @@
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 using ::android::hardware::Void;
+using ::android::hardware::hidl_bitfield;
 using ::android::hardware::hidl_vec;
 using ::android::hardware::tv::tuner::V1_0::DataFormat;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpLengthType;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
 using ::android::hardware::tv::tuner::V1_0::DemuxMmtpPid;
 using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType;
+using ::android::hardware::tv::tuner::V1_0::DemuxScHevcIndex;
+using ::android::hardware::tv::tuner::V1_0::DemuxScIndex;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterType;
 using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsIndex;
 using ::android::hardware::tv::tuner::V1_0::DvrSettings;
 using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSettings;
 using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard;
@@ -111,6 +131,9 @@
 
 static fields_t gFields;
 
+static int IP_V4_LENGTH = 4;
+static int IP_V6_LENGTH = 16;
+
 namespace android {
 /////////////// LnbCallback ///////////////////////
 LnbCallback::LnbCallback(jweak tunerObj, LnbId id) : mObject(tunerObj), mId(id) {}
@@ -1367,38 +1390,352 @@
     return NULL;
 }
 
-static DemuxFilterSettings getFilterSettings(
-        JNIEnv *env, int type, int subtype, jobject filterSettingsObj) {
+static DemuxFilterSectionBits getFilterSectionBits(JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits");
+    jbyteArray jfilterBytes = static_cast<jbyteArray>(
+            env->GetObjectField(settings, env->GetFieldID(clazz, "mFilter", "[B")));
+    jsize size = env->GetArrayLength(jfilterBytes);
+    std::vector<uint8_t> filterBytes(size);
+    env->GetByteArrayRegion(
+            jfilterBytes, 0, size, reinterpret_cast<jbyte*>(&filterBytes[0]));
+
+    jbyteArray jmask = static_cast<jbyteArray>(
+            env->GetObjectField(settings, env->GetFieldID(clazz, "mMask", "[B")));
+    size = env->GetArrayLength(jmask);
+    std::vector<uint8_t> mask(size);
+    env->GetByteArrayRegion(jmask, 0, size, reinterpret_cast<jbyte*>(&mask[0]));
+
+    jbyteArray jmode = static_cast<jbyteArray>(
+            env->GetObjectField(settings, env->GetFieldID(clazz, "mMode", "[B")));
+    size = env->GetArrayLength(jmode);
+    std::vector<uint8_t> mode(size);
+    env->GetByteArrayRegion(jmode, 0, size, reinterpret_cast<jbyte*>(&mode[0]));
+
+    DemuxFilterSectionBits filterSectionBits {
+        .filter = filterBytes,
+        .mask = mask,
+        .mode = mode,
+    };
+    return filterSectionBits;
+}
+
+static DemuxFilterSectionSettings::Condition::TableInfo getFilterTableInfo(
+        JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo");
+    uint16_t tableId = static_cast<uint16_t>(
+            env->GetIntField(settings, env->GetFieldID(clazz, "mTableId", "I")));
+    uint16_t version = static_cast<uint16_t>(
+            env->GetIntField(settings, env->GetFieldID(clazz, "mVersion", "I")));
+    DemuxFilterSectionSettings::Condition::TableInfo tableInfo {
+        .tableId = tableId,
+        .version = version,
+    };
+    return tableInfo;
+}
+
+static DemuxFilterSectionSettings getFilterSectionSettings(JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettings");
+    bool isCheckCrc = static_cast<bool>(
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mCrcEnabled", "Z")));
+    bool isRepeat = static_cast<bool>(
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRepeat", "Z")));
+    bool isRaw = static_cast<bool>(
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z")));
+
+    DemuxFilterSectionSettings filterSectionSettings {
+        .isCheckCrc = isCheckCrc,
+        .isRepeat = isRepeat,
+        .isRaw = isRaw,
+    };
+    if (env->IsInstanceOf(
+            settings,
+            env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits"))) {
+        filterSectionSettings.condition.sectionBits(getFilterSectionBits(env, settings));
+    } else if (env->IsInstanceOf(
+            settings,
+            env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo"))) {
+        filterSectionSettings.condition.tableInfo(getFilterTableInfo(env, settings));
+    }
+    return filterSectionSettings;
+}
+
+static DemuxFilterAvSettings getFilterAvSettings(JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/AvSettings");
+    bool isPassthrough = static_cast<bool>(
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsPassthrough", "Z")));
+    DemuxFilterAvSettings filterAvSettings {
+        .isPassthrough = isPassthrough,
+    };
+    return filterAvSettings;
+}
+
+static DemuxFilterPesDataSettings getFilterPesDataSettings(JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/PesSettings");
+    uint16_t streamId = static_cast<uint16_t>(
+            env->GetIntField(settings, env->GetFieldID(clazz, "mStreamId", "I")));
+    bool isRaw = static_cast<bool>(
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z")));
+    DemuxFilterPesDataSettings filterPesDataSettings {
+        .streamId = streamId,
+        .isRaw = isRaw,
+    };
+    return filterPesDataSettings;
+}
+
+static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/RecordSettings");
+    hidl_bitfield<DemuxTsIndex> tsIndexMask = static_cast<hidl_bitfield<DemuxTsIndex>>(
+            env->GetIntField(settings, env->GetFieldID(clazz, "mTsIndexMask", "I")));
+    DemuxRecordScIndexType scIndexType = static_cast<DemuxRecordScIndexType>(
+            env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexType", "I")));
+    jint scIndexMask = env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexMask", "I"));
+
+    DemuxFilterRecordSettings filterRecordSettings {
+        .tsIndexMask = tsIndexMask,
+        .scIndexType = scIndexType,
+    };
+    if (scIndexType == DemuxRecordScIndexType::SC) {
+        filterRecordSettings.scIndexMask.sc(static_cast<hidl_bitfield<DemuxScIndex>>(scIndexMask));
+    } else if (scIndexType == DemuxRecordScIndexType::SC_HEVC) {
+        filterRecordSettings.scIndexMask.scHevc(
+                static_cast<hidl_bitfield<DemuxScHevcIndex>>(scIndexMask));
+    }
+    return filterRecordSettings;
+}
+
+static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings");
+    uint32_t downloadId = static_cast<uint32_t>(
+            env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I")));
+
+    DemuxFilterDownloadSettings filterDownloadSettings {
+        .downloadId = downloadId,
+    };
+    return filterDownloadSettings;
+}
+
+static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/filter/IpFilterConfiguration");
+
+    jbyteArray jsrcIpAddress = static_cast<jbyteArray>(
+            env->GetObjectField(config, env->GetFieldID(clazz, "mSrcIpAddress", "[B")));
+    jsize srcSize = env->GetArrayLength(jsrcIpAddress);
+    jbyteArray jdstIpAddress = static_cast<jbyteArray>(
+            env->GetObjectField(config, env->GetFieldID(clazz, "mDstIpAddress", "[B")));
+    jsize dstSize = env->GetArrayLength(jdstIpAddress);
+
+    DemuxIpAddress res;
+
+    if (srcSize != dstSize) {
+        // should never happen. Validated on Java size.
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+            "IP address lengths don't match. srcLength=%d, dstLength=%d", srcSize, dstSize);
+        return res;
+    }
+
+    if (srcSize == IP_V4_LENGTH) {
+        uint8_t srcAddr[IP_V4_LENGTH];
+        uint8_t dstAddr[IP_V4_LENGTH];
+        env->GetByteArrayRegion(
+                jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr));
+        env->GetByteArrayRegion(
+                jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr));
+        res.srcIpAddress.v4(srcAddr);
+        res.dstIpAddress.v4(dstAddr);
+    } else if (srcSize == IP_V6_LENGTH) {
+        uint8_t srcAddr[IP_V6_LENGTH];
+        uint8_t dstAddr[IP_V6_LENGTH];
+        env->GetByteArrayRegion(
+                jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr));
+        env->GetByteArrayRegion(
+                jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr));
+        res.srcIpAddress.v6(srcAddr);
+        res.dstIpAddress.v6(dstAddr);
+    } else {
+        // should never happen. Validated on Java size.
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+            "Invalid IP address length %d", srcSize);
+        return res;
+    }
+
+    uint16_t srcPort = static_cast<uint16_t>(
+            env->GetIntField(config, env->GetFieldID(clazz, "mSrcPort", "I")));
+    uint16_t dstPort = static_cast<uint16_t>(
+            env->GetIntField(config, env->GetFieldID(clazz, "mDstPort", "I")));
+
+    res.srcPort = srcPort;
+    res.dstPort = dstPort;
+
+    return res;
+}
+
+static DemuxFilterSettings getFilterConfiguration(
+        JNIEnv *env, int type, int subtype, jobject filterConfigObj) {
     DemuxFilterSettings filterSettings;
-    // TODO: more setting types
     jobject settingsObj =
             env->GetObjectField(
-                    filterSettingsObj,
+                    filterConfigObj,
                     env->GetFieldID(
                             env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"),
                             "mSettings",
                             "Landroid/media/tv/tuner/filter/Settings;"));
-    if (type == (int)DemuxFilterMainType::TS) {
-        // DemuxTsFilterSettings
-        jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
-        int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I"));
-        if (subtype == (int)DemuxTsFilterType::PES) {
-            // DemuxFilterPesDataSettings
-            jclass settingClazz =
-                    env->FindClass("android/media/tv/tuner/filter/PesSettings");
-            int streamId = env->GetIntField(
-                    settingsObj, env->GetFieldID(settingClazz, "mStreamId", "I"));
-            bool isRaw = (bool)env->GetBooleanField(
-                    settingsObj, env->GetFieldID(settingClazz, "mIsRaw", "Z"));
-            DemuxFilterPesDataSettings filterPesDataSettings {
-                    .streamId = static_cast<uint16_t>(streamId),
-                    .isRaw = isRaw,
-            };
+    DemuxFilterMainType mainType = static_cast<DemuxFilterMainType>(type);
+    switch (mainType) {
+        case DemuxFilterMainType::TS: {
+            jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
+            uint16_t tpid = static_cast<uint16_t>(
+                    env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mTpid", "I")));
             DemuxTsFilterSettings tsFilterSettings {
-                    .tpid = static_cast<uint16_t>(tpid),
+                .tpid = tpid,
             };
-            tsFilterSettings.filterSettings.pesData(filterPesDataSettings);
+
+            DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype);
+            switch (tsType) {
+                case DemuxTsFilterType::SECTION:
+                    tsFilterSettings.filterSettings.section(
+                            getFilterSectionSettings(env, settingsObj));
+                    break;
+                case DemuxTsFilterType::AUDIO:
+                case DemuxTsFilterType::VIDEO:
+                    tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj));
+                    break;
+                case DemuxTsFilterType::PES:
+                    tsFilterSettings.filterSettings.pesData(
+                            getFilterPesDataSettings(env, settingsObj));
+                    break;
+                case DemuxTsFilterType::RECORD:
+                    tsFilterSettings.filterSettings.record(
+                            getFilterRecordSettings(env, settingsObj));
+                    break;
+                default:
+                    break;
+            }
             filterSettings.ts(tsFilterSettings);
+            break;
+        }
+        case DemuxFilterMainType::MMTP: {
+            jclass clazz = env->FindClass("android/media/tv/tuner/filter/MmtpFilterConfiguration");
+            uint16_t mmtpPid = static_cast<uint16_t>(
+                    env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mMmtpPid", "I")));
+            DemuxMmtpFilterSettings mmtpFilterSettings {
+                .mmtpPid = mmtpPid,
+            };
+            DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype);
+            switch (mmtpType) {
+                case DemuxMmtpFilterType::SECTION:
+                    mmtpFilterSettings.filterSettings.section(
+                            getFilterSectionSettings(env, settingsObj));
+                    break;
+                case DemuxMmtpFilterType::AUDIO:
+                case DemuxMmtpFilterType::VIDEO:
+                    mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj));
+                    break;
+                case DemuxMmtpFilterType::PES:
+                    mmtpFilterSettings.filterSettings.pesData(
+                            getFilterPesDataSettings(env, settingsObj));
+                    break;
+                case DemuxMmtpFilterType::RECORD:
+                    mmtpFilterSettings.filterSettings.record(
+                            getFilterRecordSettings(env, settingsObj));
+                    break;
+                case DemuxMmtpFilterType::DOWNLOAD:
+                    mmtpFilterSettings.filterSettings.download(
+                            getFilterDownloadSettings(env, settingsObj));
+                    break;
+                default:
+                    break;
+            }
+            filterSettings.mmtp(mmtpFilterSettings);
+            break;
+        }
+        case DemuxFilterMainType::IP: {
+            DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj);
+
+            DemuxIpFilterSettings ipFilterSettings {
+                .ipAddr = ipAddr,
+            };
+            DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype);
+            switch (ipType) {
+                case DemuxIpFilterType::SECTION: {
+                    ipFilterSettings.filterSettings.section(
+                            getFilterSectionSettings(env, settingsObj));
+                    break;
+                }
+                case DemuxIpFilterType::IP: {
+                    jclass clazz = env->FindClass(
+                            "android/media/tv/tuner/filter/IpFilterConfiguration");
+                    bool bPassthrough = static_cast<bool>(
+                            env->GetBooleanField(
+                                    filterConfigObj, env->GetFieldID(
+                                            clazz, "mPassthrough", "Z")));
+                    ipFilterSettings.filterSettings.bPassthrough(bPassthrough);
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+            filterSettings.ip(ipFilterSettings);
+            break;
+        }
+        case DemuxFilterMainType::TLV: {
+            jclass clazz = env->FindClass("android/media/tv/tuner/filter/TlvFilterConfiguration");
+            uint8_t packetType = static_cast<uint8_t>(
+                    env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I")));
+            bool isCompressedIpPacket = static_cast<bool>(
+                    env->GetBooleanField(
+                            filterConfigObj, env->GetFieldID(clazz, "mIsCompressedIpPacket", "Z")));
+
+            DemuxTlvFilterSettings tlvFilterSettings {
+                .packetType = packetType,
+                .isCompressedIpPacket = isCompressedIpPacket,
+            };
+            DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype);
+            switch (tlvType) {
+                case DemuxTlvFilterType::SECTION: {
+                    tlvFilterSettings.filterSettings.section(
+                            getFilterSectionSettings(env, settingsObj));
+                    break;
+                }
+                case DemuxTlvFilterType::TLV: {
+                    bool bPassthrough = static_cast<bool>(
+                            env->GetBooleanField(
+                                    filterConfigObj, env->GetFieldID(
+                                            clazz, "mPassthrough", "Z")));
+                    tlvFilterSettings.filterSettings.bPassthrough(bPassthrough);
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+            filterSettings.tlv(tlvFilterSettings);
+            break;
+        }
+        case DemuxFilterMainType::ALP: {
+            jclass clazz = env->FindClass("android/media/tv/tuner/filter/AlpFilterConfiguration");
+            uint8_t packetType = static_cast<uint8_t>(
+                    env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I")));
+            DemuxAlpLengthType lengthType = static_cast<DemuxAlpLengthType>(
+                    env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mLengthType", "I")));
+            DemuxAlpFilterSettings alpFilterSettings {
+                .packetType = packetType,
+                .lengthType = lengthType,
+            };
+            DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype);
+            switch (alpType) {
+                case DemuxAlpFilterType::SECTION:
+                    alpFilterSettings.filterSettings.section(
+                            getFilterSectionSettings(env, settingsObj));
+                    break;
+                default:
+                    break;
+            }
+            filterSettings.alp(alpFilterSettings);
+            break;
+        }
+        default: {
+            break;
         }
     }
     return filterSettings;
@@ -1439,7 +1776,7 @@
         ALOGD("Failed to configure filter: filter not found");
         return (int)Result::INVALID_STATE;
     }
-    DemuxFilterSettings filterSettings = getFilterSettings(env, type, subtype, settings);
+    DemuxFilterSettings filterSettings = getFilterConfiguration(env, type, subtype, settings);
     Result res = iFilterSp->configure(filterSettings);
     MQDescriptorSync<uint8_t> filterMQDesc;
     if (res == Result::SUCCESS && filterSp->mFilterMQ == NULL) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
similarity index 99%
rename from media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
rename to media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 3ffb951..615dc48 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -70,8 +70,8 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class MediaRouterManagerTest {
-    private static final String TAG = "MediaRouterManagerTest";
+public class MediaRouter2ManagerTest {
+    private static final String TAG = "MediaRouter2ManagerTest";
     private static final int TIMEOUT_MS = 5000;
 
     private Context mContext;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
index 267927f..f1dcf3d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
@@ -191,7 +191,7 @@
         RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName)
                 .addSelectedRoute(routeId)
                 .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
+                .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
                 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(SESSION_VOLUME_MAX)
                 .setVolume(SESSION_VOLUME_INITIAL)
@@ -300,7 +300,7 @@
                 .clearSelectedRoutes()
                 .addSelectedRoute(routeId)
                 .removeDeselectableRoute(routeId)
-                .removeTransferrableRoute(routeId)
+                .removeTransferableRoute(routeId)
                 .build();
         notifySessionUpdated(newSessionInfo);
         publishRoutes();
diff --git a/media/tests/TunerTest/Android.bp b/media/tests/TunerTest/Android.bp
new file mode 100644
index 0000000..cef8791
--- /dev/null
+++ b/media/tests/TunerTest/Android.bp
@@ -0,0 +1,18 @@
+android_test {
+    name: "mediatunertest",
+
+    srcs: ["**/*.java"],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+
+    static_libs: [
+        "android-support-test",
+        "testng"
+    ],
+
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/media/tests/TunerTest/AndroidManifest.xml b/media/tests/TunerTest/AndroidManifest.xml
new file mode 100644
index 0000000..17e9f19
--- /dev/null
+++ b/media/tests/TunerTest/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.mediatunertest">
+
+    <uses-permission android:name="android.permission.ACCESS_TV_TUNER" />
+
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.mediatunertest"
+                     android:label="Media Tuner Tests"/>
+</manifest>
diff --git a/media/tests/TunerTest/AndroidTest.xml b/media/tests/TunerTest/AndroidTest.xml
new file mode 100644
index 0000000..d9c31f45
--- /dev/null
+++ b/media/tests/TunerTest/AndroidTest.xml
@@ -0,0 +1,17 @@
+<configuration description="Runs Media Tuner tests.">
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="MediaTunerTest"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="mediatunertest.apk"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.mediatunertest"/>
+        <option name="hidden-api-checks" value="false"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/media/tests/TunerTest/res/values/strings.xml b/media/tests/TunerTest/res/values/strings.xml
new file mode 100644
index 0000000..b313944
--- /dev/null
+++ b/media/tests/TunerTest/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- name of the app [CHAR LIMIT=25]-->
+    <string name="app_name">MediaTunerTest</string>
+</resources>
\ No newline at end of file
diff --git a/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
new file mode 100644
index 0000000..cbfbf77
--- /dev/null
+++ b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediatunertest;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.media.tv.tuner.Descrambler;
+import android.media.tv.tuner.Tuner;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TunerTest {
+    private static final String TAG = "MediaTunerTest";
+
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testTunerConstructor() throws Exception {
+        Tuner tuner = new Tuner(mContext, "123", 1, null);
+        assertNotNull(tuner);
+    }
+
+    @Test
+    public void testOpenDescrambler() throws Exception {
+        Tuner tuner = new Tuner(mContext, "123", 1, null);
+        Descrambler descrambler = tuner.openDescrambler();
+        assertNotNull(descrambler);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 07b7b22..8eeaefd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -17,8 +17,10 @@
 package com.android.systemui;
 
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.car.CarNavigationBar;
 import com.android.systemui.pip.PipUI;
 import com.android.systemui.power.PowerUI;
@@ -29,6 +31,7 @@
 import com.android.systemui.statusbar.car.CarStatusBar;
 import com.android.systemui.statusbar.car.CarStatusBarModule;
 import com.android.systemui.statusbar.notification.InstantAppNotifier;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.tv.TvStatusBar;
 import com.android.systemui.theme.ThemeOverlayController;
@@ -42,7 +45,8 @@
 import dagger.multibindings.IntoMap;
 
 /** Binder for car specific {@link SystemUI} modules. */
-@Module(includes = {RecentsModule.class, CarStatusBarModule.class})
+@Module(includes = {RecentsModule.class, CarStatusBarModule.class, NotificationsModule.class,
+        BubbleModule.class, KeyguardModule.class})
 public abstract class CarSystemUIBinder {
     /** Inject into AuthController. */
     @Binds
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 498bd87..7f64990 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -54,10 +54,10 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.StatusBarDependenciesModule;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
 import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
@@ -67,6 +67,7 @@
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -87,6 +88,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -112,7 +114,8 @@
 /**
  * Dagger Module providing {@link CarStatusBar}.
  */
-@Module(includes = {StatusBarDependenciesModule.class})
+@Module(includes = {StatusBarDependenciesModule.class, StatusBarPhoneDependenciesModule.class,
+        NotificationRowModule.class})
 public class CarStatusBarModule {
     /**
      * Provides our instance of StatusBar which is considered optional.
diff --git a/packages/PrintSpooler/res/values-ja/donottranslate.xml b/packages/PrintSpooler/res/values-ja/donottranslate.xml
index d334ddd..6a0f768 100644
--- a/packages/PrintSpooler/res/values-ja/donottranslate.xml
+++ b/packages/PrintSpooler/res/values-ja/donottranslate.xml
@@ -16,7 +16,7 @@
 
 <resources>
 
-    <string name="mediasize_default">JIS_B5</string>
+    <string name="mediasize_default">ISO_A4</string>
     <string name="mediasize_standard">@string/mediasize_standard_japan</string>
 
 </resources>
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 6212493..3f42ad4 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -27,6 +27,7 @@
         "SettingsLibRadioButtonPreference",
         "WifiTrackerLib",
         "SettingsLibDisplayDensityUtils",
+        "SettingsLibSchedulesProvider",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
index e6f8c01..dc6dddb 100644
--- a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
@@ -18,75 +18,39 @@
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/entity_header"
-    style="@style/EntityHeader"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:orientation="horizontal">
+    style="@style/EntityHeader">
 
     <LinearLayout
         android:id="@+id/entity_header_content"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:paddingTop="32dp"
+        android:paddingBottom="32dp"
         android:layout_centerHorizontal="true"
-        android:gravity="center_horizontal"
-        android:orientation="horizontal">
+        android:orientation="vertical">
 
         <LinearLayout
             android:id="@+id/entity_header_content"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_centerHorizontal="true"
-            android:gravity="center_horizontal"
-            android:orientation="vertical">
+            android:layout_gravity="center"
+            android:orientation="horizontal">
 
             <ImageView
                 android:id="@+id/entity_header_icon_personal"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:scaleType="fitCenter"
-                android:antialias="true"/>
+                style="@style/CrossProfileEntityHeaderIcon" />
 
-            <TextView
-                android:id="@+id/install_type"
-                style="@style/TextAppearance.EntityHeaderSummary"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="2dp"
-                android:text="Personal"/>
-        </LinearLayout>
-
-        <ImageView
-            android:id="@+id/entity_header_swap_horiz"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:scaleType="fitCenter"
-            android:antialias="true"
-            android:src="@drawable/ic_swap_horiz_grey"/>
-
-        <LinearLayout
-            android:id="@+id/entity_header_content"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerHorizontal="true"
-            android:gravity="center_horizontal"
-            android:orientation="vertical">
+            <ImageView
+                android:id="@+id/entity_header_swap_horiz"
+                style="@style/CrossProfileSwapHorizIcon "/>
 
             <ImageView
                 android:id="@+id/entity_header_icon_work"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:scaleType="fitCenter"
-                android:antialias="true"/>
-            <TextView
-                android:id="@+id/install_type"
-                style="@style/TextAppearance.EntityHeaderSummary"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="2dp"
-                android:text="Work"/>
+                style="@style/CrossProfileEntityHeaderIcon" />
         </LinearLayout>
-    </LinearLayout>
 
+        <TextView
+            android:id="@+id/entity_header_title"
+            style="@style/CrossProfileEntityHeaderTitle" />
+    </LinearLayout>
 </RelativeLayout>
diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
index 6a2b729..4a99e84 100644
--- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml
+++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
@@ -37,4 +37,35 @@
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">marquee</item>
     </style>
+
+    <style name="CrossProfileEntityHeaderIcon">
+        <item name="android:layout_width">48dp</item>
+        <item name="android:layout_height">48dp</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:antialias">true</item>
+    </style>
+
+    <style name="CrossProfileSwapHorizIcon">
+        <item name="android:layout_width">24dp</item>
+        <item name="android:layout_height">24dp</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:layout_marginStart">10dp</item>
+        <item name="android:layout_marginEnd">10dp</item>
+        <item name="android:src">@drawable/ic_swap_horiz_grey</item>
+        <item name="android:antialias">true</item>
+    </style>
+
+    <style name="CrossProfileEntityHeaderTitle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:fontFamily">google-sans-medium</item>
+        <item name="android:layout_marginTop">8dp</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
new file mode 100644
index 0000000..ef59252
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -0,0 +1,12 @@
+android_library {
+    name: "SettingsLibSchedulesProvider",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+          "androidx.annotation_annotation",
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml
new file mode 100644
index 0000000..1b0e4bf
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.schedulesprovider">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
new file mode 100644
index 0000000..7d2b8e2
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.schedulesprovider;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * This is a schedule data item. It contains the schedule title text, the summary text which
+ * displays on the summary of the Settings preference and an {@link Intent}. Intent is able to
+ * launch the editing page of the schedule data when user clicks this item (preference).
+ */
+public class ScheduleInfo implements Parcelable {
+    private static final String TAG = "ScheduleInfo";
+    private final String mTitle;
+    private final String mSummary;
+    private final Intent mIntent;
+
+    public ScheduleInfo(Builder builder) {
+        mTitle = builder.mTitle;
+        mSummary = builder.mSummary;
+        mIntent = builder.mIntent;
+    }
+
+    protected ScheduleInfo(Parcel in) {
+        mTitle = in.readString();
+        mSummary = in.readString();
+        mIntent = in.readParcelable(Intent.class.getClassLoader());
+    }
+
+    /**
+     * Returns the title text.
+     *
+     * @return The title.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns the summary text.
+     *
+     * @return The summary.
+     */
+    public String getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * Returns an {@link Intent}.
+     */
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Verify the member variables are valid.
+     *
+     * @return {@code true} if all member variables are valid.
+     */
+    public boolean isValid() {
+        return !TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mSummary) && (mIntent != null);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mTitle);
+        dest.writeString(mSummary);
+        dest.writeParcelable(mIntent, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ScheduleInfo> CREATOR = new Creator<ScheduleInfo>() {
+        @Override
+        public ScheduleInfo createFromParcel(Parcel in) {
+            return new ScheduleInfo(in);
+        }
+
+        @Override
+        public ScheduleInfo[] newArray(int size) {
+            return new ScheduleInfo[size];
+        }
+    };
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "title : " + mTitle + " summary : " + mSummary + (mIntent == null
+                ? " and intent is null." : ".");
+    }
+
+    /**
+     * A simple builder for {@link ScheduleInfo}.
+     */
+    public static class Builder {
+        @NonNull
+        private String mTitle;
+        @NonNull
+        private String mSummary;
+        @NonNull
+        private Intent mIntent;
+
+        /**
+         * Sets the title.
+         *
+         * @param title The title of the preference item.
+         * @return This instance.
+         */
+        public Builder setTitle(@NonNull String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the summary.
+         *
+         * @param summary The summary of the preference summary.
+         * @return This instance.
+         */
+        public Builder setSummary(@NonNull String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        /**
+         * Sets the {@link Intent}.
+         *
+         * @param intent The action when user clicks the preference.
+         * @return This instance.
+         */
+        public Builder setIntent(@NonNull Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Creates an instance of {@link ScheduleInfo}.
+         *
+         * @return The instance of {@link ScheduleInfo}.
+         */
+        public ScheduleInfo build() {
+            return new ScheduleInfo(this);
+        }
+    }
+}
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
new file mode 100644
index 0000000..a423e47
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.schedulesprovider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This provider is a bridge for client apps to provide the schedule data.
+ * Client provider needs to implement their {@link #getScheduleInfoList()} and returns a list of
+ * {@link ScheduleInfo}.
+ */
+public abstract class SchedulesProvider extends ContentProvider {
+    public static final String METHOD_GENERATE_SCHEDULE_INFO_LIST = "generateScheduleInfoList";
+    public static final String BUNDLE_SCHEDULE_INFO_LIST = "scheduleInfoList";
+    private static final String TAG = "SchedulesProvider";
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public final Cursor query(
+            Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException("Query operation is not supported currently.");
+    }
+
+    @Override
+    public final String getType(Uri uri) {
+        throw new UnsupportedOperationException("GetType operation is not supported currently.");
+    }
+
+    @Override
+    public final Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException("Insert operation is not supported currently.");
+    }
+
+    @Override
+    public final int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("Delete operation not supported currently.");
+    }
+
+    @Override
+    public final int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        throw new UnsupportedOperationException("Update operation is not supported currently.");
+    }
+
+    /**
+     * Return the list of the schedule information.
+     *
+     * @return a list of the {@link ScheduleInfo}.
+     */
+    public abstract ArrayList<ScheduleInfo> getScheduleInfoList();
+
+    /**
+     * Returns a bundle which contains a list of {@link ScheduleInfo} and data types:
+     * scheduleInfoList : ArrayList<ScheduleInfo>
+     */
+    @Override
+    public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+        final Bundle bundle = new Bundle();
+        if (METHOD_GENERATE_SCHEDULE_INFO_LIST.equals(method)) {
+            final ArrayList<ScheduleInfo> scheduleInfoList = filterInvalidData(
+                    getScheduleInfoList());
+            if (scheduleInfoList != null) {
+                bundle.putParcelableArrayList(BUNDLE_SCHEDULE_INFO_LIST, scheduleInfoList);
+            }
+        }
+        return bundle;
+    }
+
+    /**
+     * To filter the invalid schedule info.
+     *
+     * @param scheduleInfoList The list of the {@link ScheduleInfo}.
+     * @return The valid list of the {@link ScheduleInfo}.
+     */
+    private ArrayList<ScheduleInfo> filterInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
+        if (scheduleInfoList == null) {
+            Log.d(TAG, "package : " + getContext().getPackageName() + " has no scheduling data.");
+            return null;
+        }
+        // Dump invalid data in debug mode.
+        if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
+            new Thread(() -> {
+                dumpInvalidData(scheduleInfoList);
+            }).start();
+        }
+        final List<ScheduleInfo> filteredList = scheduleInfoList
+                .stream()
+                .filter(scheduleInfo -> scheduleInfo.isValid())
+                .collect(Collectors.toList());
+
+        return new ArrayList<>(filteredList);
+    }
+
+    private void dumpInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
+        Log.d(TAG, "package : " + getContext().getPackageName()
+                + " provided some scheduling data are invalid.");
+        scheduleInfoList
+                .stream()
+                .filter(scheduleInfo -> !scheduleInfo.isValid())
+                .forEach(scheduleInfo -> Log.d(TAG, scheduleInfo.toString()));
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e910967..8e4a982 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -74,13 +74,6 @@
         refreshDevices();
     }
 
-    @VisibleForTesting
-    String getControlCategoryByPackageName(String packageName) {
-        //TODO(b/117129183): Use package name to get ControlCategory.
-        //Since api not ready, return fixed ControlCategory for prototype.
-        return "com.google.android.gms.cast.CATEGORY_CAST";
-    }
-
     @Override
     public void stopScan() {
         mRouterManager.unregisterCallback(mMediaRouterCallback);
@@ -192,5 +185,10 @@
         public void onRoutesChanged(List<MediaRoute2Info> routes) {
             refreshDevices();
         }
+
+        @Override
+        public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+            refreshDevices();
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 984ab11..f8db70a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -16,6 +16,8 @@
 package com.android.settingslib.media;
 
 import android.app.Notification;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.text.TextUtils;
@@ -26,13 +28,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -66,9 +68,16 @@
     @VisibleForTesting
     List<MediaDevice> mMediaDevices = new ArrayList<>();
     @VisibleForTesting
+    List<MediaDevice> mDisconnectedMediaDevices = new ArrayList<>();
+    @VisibleForTesting
     MediaDevice mPhoneDevice;
     @VisibleForTesting
     MediaDevice mCurrentConnectedDevice;
+    @VisibleForTesting
+    DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
+            new DeviceAttributeChangeCallback();
+    @VisibleForTesting
+    BluetoothAdapter mBluetoothAdapter;
 
     /**
      * Register to start receiving callbacks for MediaDevice events.
@@ -89,6 +98,7 @@
         mPackageName = packageName;
         mLocalBluetoothManager =
                 LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mLocalBluetoothManager == null) {
             Log.e(TAG, "Bluetooth is not supported on this device");
             return;
@@ -164,7 +174,7 @@
     }
 
     void dispatchDeviceListUpdate() {
-        Collections.sort(mMediaDevices, COMPARATOR);
+        //TODO(b/149260820): Use new rule to rank device once device type api is ready.
         for (DeviceCallback callback : getCallbacks()) {
             callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
         }
@@ -278,12 +288,44 @@
         public void onDeviceListAdded(List<MediaDevice> devices) {
             mMediaDevices.clear();
             mMediaDevices.addAll(devices);
+            mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+
             final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
             mCurrentConnectedDevice = infoMediaDevice != null
                     ? infoMediaDevice : updateCurrentConnectedDevice();
             dispatchDeviceListUpdate();
         }
 
+        private List<MediaDevice> buildDisconnectedBluetoothDevice() {
+            for (MediaDevice device : mDisconnectedMediaDevices) {
+                ((BluetoothMediaDevice) device).getCachedDevice()
+                        .unregisterCallback(mDeviceAttributeChangeCallback);
+            }
+            mDisconnectedMediaDevices.clear();
+            final List<BluetoothDevice> bluetoothDevices =
+                    mBluetoothAdapter.getMostRecentlyConnectedDevices();
+            final CachedBluetoothDeviceManager cachedDeviceManager =
+                    mLocalBluetoothManager.getCachedDeviceManager();
+
+            for (BluetoothDevice device : bluetoothDevices) {
+                final CachedBluetoothDevice cachedDevice =
+                        cachedDeviceManager.findDevice(device);
+                if (cachedDevice != null) {
+                    if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
+                            && !cachedDevice.isConnected()) {
+                        final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext,
+                                cachedDevice,
+                                null, null, mPackageName);
+                        if (!mMediaDevices.contains(mediaDevice)) {
+                            cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
+                            mDisconnectedMediaDevices.add(mediaDevice);
+                        }
+                    }
+                }
+            }
+            return new ArrayList<>(mDisconnectedMediaDevices);
+        }
+
         @Override
         public void onDeviceRemoved(MediaDevice device) {
             if (mMediaDevices.contains(device)) {
@@ -345,4 +387,17 @@
          */
         default void onDeviceAttributesChanged() {};
     }
+
+    /**
+     * This callback is for update {@link BluetoothMediaDevice} summary when
+     * {@link CachedBluetoothDevice} connection state is changed.
+     */
+    @VisibleForTesting
+    class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
+
+        @Override
+        public void onDeviceAttributesChanged() {
+            dispatchDeviceAttributesChanged();
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
new file mode 100644
index 0000000..885b7d3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.notification;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ShadowGenerator;
+
+/**
+ * Factory for creating normalized conversation icons.
+ * We are not using Launcher's IconFactory because conversation rendering only runs on the UI
+ * thread, so there is no need to manage a pool across multiple threads.
+ */
+public class ConversationIconFactory extends BaseIconFactory {
+
+    final LauncherApps mLauncherApps;
+    final PackageManager mPackageManager;
+    final IconDrawableFactory mIconDrawableFactory;
+
+    public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm,
+            IconDrawableFactory iconDrawableFactory, int iconSizePx) {
+        super(context, context.getResources().getConfiguration().densityDpi,
+                iconSizePx);
+        mLauncherApps = la;
+        mPackageManager = pm;
+        mIconDrawableFactory = iconDrawableFactory;
+    }
+
+    private int getBadgeSize() {
+        return mContext.getResources().getDimensionPixelSize(
+                com.android.launcher3.icons.R.dimen.profile_badge_size);
+    }
+    /**
+     * Returns the conversation info drawable
+     */
+    private Drawable getConversationDrawable(ShortcutInfo shortcutInfo) {
+        return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+    }
+
+    /**
+     * Get the {@link Drawable} that represents the app icon
+     */
+    private Drawable getBadgedIcon(String packageName, int userId) {
+        try {
+            final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
+            return mIconDrawableFactory.getBadgedIcon(appInfo, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            return mPackageManager.getDefaultActivityIcon();
+        }
+    }
+
+    /**
+     * Turns a Drawable into a Bitmap
+     */
+    BitmapInfo toBitmap(Drawable userBadgedAppIcon) {
+        Bitmap bitmap = createIconBitmap(
+                userBadgedAppIcon, 1f, getBadgeSize());
+
+        Canvas c = new Canvas();
+        ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
+        c.setBitmap(bitmap);
+        shadowGenerator.recreateIcon(Bitmap.createBitmap(bitmap), c);
+        return createIconBitmap(bitmap);
+    }
+
+    /**
+     * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
+     */
+    public Bitmap getConversationBitmap(ShortcutInfo info, String packageName, int uid) {
+        return getConversationBitmap(getConversationDrawable(info), packageName, uid);
+    }
+
+    /**
+     * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
+     */
+    public Bitmap getConversationBitmap(Drawable baseIcon, String packageName, int uid) {
+        int userId = UserHandle.getUserId(uid);
+        Drawable badge = getBadgedIcon(packageName, userId);
+        BitmapInfo iconInfo = createBadgedIconBitmap(baseIcon,
+                UserHandle.of(userId),
+                true /* shrinkNonAdaptiveIcons */);
+
+        badgeWithDrawable(iconInfo.icon,
+                new BitmapDrawable(mContext.getResources(), toBitmap(badge).icon));
+        return iconInfo.icon;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 954eb9b..b47aa98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1366,7 +1366,7 @@
             mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
         } else {
             mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
-            mConfig.requirePMF = true;
+            mConfig.requirePmf = true;
         }
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 05f5fa0..8b815bf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -203,4 +203,46 @@
 
         assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
     }
+
+    @Test
+    public void onRoutesRemoved_getAvailableRoutes_shouldAddMediaDevice() {
+        final MediaRoute2Info info = mock(MediaRoute2Info.class);
+        when(info.getId()).thenReturn(TEST_ID);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info);
+        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+
+        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+        assertThat(mediaDevice).isNull();
+
+        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+
+        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice);
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+    }
+
+    @Test
+    public void onRoutesRemoved_buildAllRoutes_shouldAddMediaDevice() {
+        final MediaRoute2Info info = mock(MediaRoute2Info.class);
+        when(info.getId()).thenReturn(TEST_ID);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info);
+        when(mRouterManager.getAllRoutes()).thenReturn(routes);
+
+        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+        assertThat(mediaDevice).isNull();
+
+        mInfoMediaManager.mPackageName = "";
+        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+
+        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 3d67ba0..3611dfe 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -25,13 +25,17 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 
 import com.android.settingslib.bluetooth.A2dpProfile;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.HearingAidProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,11 +44,14 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
 
 import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
 public class LocalMediaManagerTest {
 
     private static final String TEST_DEVICE_ID_1 = "device_id_1";
@@ -69,11 +76,15 @@
 
     private Context mContext;
     private LocalMediaManager mLocalMediaManager;
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
+        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
 
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
@@ -81,6 +92,7 @@
 
         mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
                 mInfoMediaManager, "com.test.packagename");
+        mLocalMediaManager.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     }
 
     @Test
@@ -419,4 +431,50 @@
                 MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
         assertThat(activeDevices).containsExactly(device3);
     }
+
+    @Test
+    public void onDeviceAttributesChanged_shouldBeCalled() {
+        mLocalMediaManager.registerCallback(mCallback);
+
+        mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();
+
+        verify(mCallback).onDeviceAttributesChanged();
+    }
+
+    @Test
+    public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() {
+        final List<MediaDevice> devices = new ArrayList<>();
+        final MediaDevice device1 = mock(MediaDevice.class);
+        final MediaDevice device2 = mock(MediaDevice.class);
+        final MediaDevice device3 = mock(MediaDevice.class);
+        mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+        devices.add(device1);
+        devices.add(device2);
+        mLocalMediaManager.mMediaDevices.add(device3);
+        mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+        final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
+        final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+        final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
+        bluetoothDevices.add(bluetoothDevice);
+        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
+
+        when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
+        when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
+        when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(cachedDevice.isConnected()).thenReturn(false);
+
+        when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+        when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+        when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
+
+        assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+        mLocalMediaManager.registerCallback(mCallback);
+        mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
+
+        assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+        verify(mCallback).onDeviceListUpdate(any());
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
index 906dba4..015ce149 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.testutils.shadow;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 
@@ -29,6 +30,7 @@
 public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBluetoothAdapter {
 
     private List<Integer> mSupportedProfiles;
+    private List<BluetoothDevice> mMostRecentlyConnectedDevices;
     private BluetoothProfile.ServiceListener mServiceListener;
 
     @Implementation
@@ -50,4 +52,13 @@
     public void setSupportedProfiles(List<Integer> supportedProfiles) {
         mSupportedProfiles = supportedProfiles;
     }
+
+    @Implementation
+    protected List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+        return mMostRecentlyConnectedDevices;
+    }
+
+    public void setMostRecentlyConnectedDevices(List<BluetoothDevice> list) {
+        mMostRecentlyConnectedDevices = list;
+    }
 }
diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 3787727..76bea31 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -23,15 +23,10 @@
     <!-- A notification is shown when the user's softap config has been changed due to underlying
      hardware restrictions. This is the notifications's title.
      [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change">Changes to your hotspot settings</string>
+    <string name="wifi_softap_config_change">Hotspot settings have changed</string>
 
     <!-- A notification is shown when the user's softap config has been changed due to underlying
          hardware restrictions. This is the notification's summary message.
          [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change_summary">Your hotspot band has changed.</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-         hardware restrictions. This is the notification's full message.
-         [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change_detailed">This device doesn\u2019t support your preference for 5GHz only. Instead, this device will use the 5GHz band when available.</string>
+    <string name="wifi_softap_config_change_summary">Tap to see details</string>
 </resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 8037266..c5d4fa9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -126,6 +126,7 @@
         VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255));
         VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
index 1ee5f90..ca841a5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
@@ -57,7 +57,6 @@
         Resources resources = context.getResources();
         CharSequence title = resources.getText(R.string.wifi_softap_config_change);
         CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
-        CharSequence content = resources.getText(R.string.wifi_softap_config_change_detailed);
         int color = resources.getColor(
                 android.R.color.system_notification_accent_color, context.getTheme());
 
@@ -73,7 +72,6 @@
                 .setLocalOnly(true)
                 .setColor(color)
                 .setStyle(new Notification.BigTextStyle()
-                        .bigText(content)
                         .setBigContentTitle(title)
                         .setSummaryText(contentSummary))
                 .setAutoCancel(true)
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cc2c92b..d821050 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -253,7 +253,8 @@
     <!-- Permission required for CTS test - ShortcutManagerUsageTest -->
     <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
 
-     <!-- Permission required for CTS test - UsageStatsTest -->
+    <!-- Permissions required for CTS test - UsageStatsTest -->
+    <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/>
     <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
 
     <!-- Permissions required to test ambient display. -->
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 1814593..530823a 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -606,26 +606,21 @@
 
         BugreportInfo info = new BugreportInfo(mContext, baseName, name,
                 shareTitle, shareDescription, bugreportType, mBugreportsDir);
-        ParcelFileDescriptor bugreportFd;
-        ParcelFileDescriptor screenshotFd;
-
-        try {
-            bugreportFd = info.createAndGetBugreportFd();
-            if (bugreportFd == null) {
-                Log.e(TAG, "Bugreport parcel file descriptor is null.");
-                return;
-            }
-            screenshotFd = info.createAndGetDefaultScreenshotFd();
-            if (screenshotFd == null) {
-                Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file");
-                FileUtils.closeQuietly(bugreportFd);
-                info.bugreportFile.delete();
-                return;
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Error in generating bugreport files: ", e);
+        ParcelFileDescriptor bugreportFd = info.getBugreportFd();
+        if (bugreportFd == null) {
+            Log.e(TAG, "Failed to start bugreport generation as "
+                    + " bugreport parcel file descriptor is null.");
             return;
         }
+        ParcelFileDescriptor screenshotFd = info.getDefaultScreenshotFd();
+        if (screenshotFd == null) {
+            Log.e(TAG, "Failed to start bugreport generation as"
+                    + " screenshot parcel file descriptor is null. Deleting bugreport file");
+            FileUtils.closeQuietly(bugreportFd);
+            info.bugreportFile.delete();
+            return;
+        }
+
         mBugreportManager = (BugreportManager) mContext.getSystemService(
                 Context.BUGREPORT_SERVICE);
         final Executor executor = ActivityThread.currentActivityThread().getExecutor();
@@ -639,7 +634,7 @@
             mBugreportManager.startBugreport(bugreportFd, screenshotFd,
                     new BugreportParams(bugreportType), executor, bugreportCallback);
         } catch (RuntimeException e) {
-            Log.i(TAG, "error in generating bugreports: ", e);
+            Log.i(TAG, "Error in generating bugreports: ", e);
             // The binder call didn't go through successfully, so need to close the fds.
             // If the calls went through API takes ownership.
             FileUtils.closeQuietly(bugreportFd);
@@ -657,11 +652,15 @@
         return null;
     }
 
-    private static void createReadWriteFile(File file) throws IOException {
-        if (!file.exists()) {
-            file.createNewFile();
-            file.setReadable(true, true);
-            file.setWritable(true, true);
+    private static void createReadWriteFile(File file) {
+        try {
+            if (!file.exists()) {
+                file.createNewFile();
+                file.setReadable(true, true);
+                file.setWritable(true, true);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error in creating bugreport file: ", e);
         }
     }
 
@@ -1836,23 +1835,23 @@
 
         void createBugreportFile(File bugreportsDir) {
             bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+            createReadWriteFile(bugreportFile);
         }
 
         void createScreenshotFile(File bugreportsDir) {
             File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
             addScreenshot(screenshotFile);
+            createReadWriteFile(screenshotFile);
         }
 
-        ParcelFileDescriptor createAndGetBugreportFd() throws IOException {
-            createReadWriteFile(bugreportFile);
+        ParcelFileDescriptor getBugreportFd() {
             return getFd(bugreportFile);
         }
 
-        ParcelFileDescriptor createAndGetDefaultScreenshotFd() throws IOException {
+        ParcelFileDescriptor getDefaultScreenshotFd() {
             if (screenshotFiles.isEmpty()) {
                 return null;
             }
-            createReadWriteFile(screenshotFiles.get(0));
             return getFd(screenshotFiles.get(0));
         }
 
diff --git a/packages/SystemUI/res/color/control_background.xml b/packages/SystemUI/res/color/control_background.xml
index 646fe5d..977310c 100644
--- a/packages/SystemUI/res/color/control_background.xml
+++ b/packages/SystemUI/res/color/control_background.xml
@@ -2,5 +2,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:color="@color/control_default_background" />
-  <item android:color="@color/GM2_blue_50" />
+  <item android:color="@color/GM2_blue_200"
+        android:alpha="0.2" />
 </selector>
diff --git a/packages/SystemUI/res/color/control_foreground.xml b/packages/SystemUI/res/color/control_foreground.xml
index bf028f1..339f1e2 100644
--- a/packages/SystemUI/res/color/control_foreground.xml
+++ b/packages/SystemUI/res/color/control_foreground.xml
@@ -2,5 +2,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false"
          android:color="@color/control_default_foreground" />
-   <item android:color="@color/GM2_blue_700" />
+   <item android:color="@color/GM2_blue_200" />
  </selector>
diff --git a/packages/SystemUI/res/color/light_background.xml b/packages/SystemUI/res/color/light_background.xml
index 2effd99..1299464 100644
--- a/packages/SystemUI/res/color/light_background.xml
+++ b/packages/SystemUI/res/color/light_background.xml
@@ -2,5 +2,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:color="@color/control_default_background" />
-  <item android:color="@color/GM2_yellow_50" />
+  <item android:color="@color/GM2_yellow_200"
+        android:alpha="0.2" />
 </selector>
diff --git a/packages/SystemUI/res/color/light_foreground.xml b/packages/SystemUI/res/color/light_foreground.xml
index 8143028..7c02075 100644
--- a/packages/SystemUI/res/color/light_foreground.xml
+++ b/packages/SystemUI/res/color/light_foreground.xml
@@ -2,5 +2,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:color="@color/control_default_foreground" />
-  <item android:color="@color/GM2_orange_900" />
+  <item android:color="@color/GM2_yellow_200" />
 </selector>
diff --git a/packages/SystemUI/res/color/thermo_cool_background.xml b/packages/SystemUI/res/color/thermo_cool_background.xml
index 646fe5d..977310c 100644
--- a/packages/SystemUI/res/color/thermo_cool_background.xml
+++ b/packages/SystemUI/res/color/thermo_cool_background.xml
@@ -2,5 +2,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:color="@color/control_default_background" />
-  <item android:color="@color/GM2_blue_50" />
+  <item android:color="@color/GM2_blue_200"
+        android:alpha="0.2" />
 </selector>
diff --git a/packages/SystemUI/res/color/thermo_cool_foreground.xml b/packages/SystemUI/res/color/thermo_cool_foreground.xml
index bf028f1..339f1e2 100644
--- a/packages/SystemUI/res/color/thermo_cool_foreground.xml
+++ b/packages/SystemUI/res/color/thermo_cool_foreground.xml
@@ -2,5 +2,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false"
          android:color="@color/control_default_foreground" />
-   <item android:color="@color/GM2_blue_700" />
+   <item android:color="@color/GM2_blue_200" />
  </selector>
diff --git a/packages/SystemUI/res/color/thermo_heat_background.xml b/packages/SystemUI/res/color/thermo_heat_background.xml
index 6f29ed5..2709ebe 100644
--- a/packages/SystemUI/res/color/thermo_heat_background.xml
+++ b/packages/SystemUI/res/color/thermo_heat_background.xml
@@ -2,5 +2,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:color="@color/control_default_background" />
-  <item android:color="@color/GM2_red_50" />
+  <item android:color="@color/GM2_red_200"
+        android:alpha="0.2" />
 </selector>
diff --git a/packages/SystemUI/res/color/thermo_heat_foreground.xml b/packages/SystemUI/res/color/thermo_heat_foreground.xml
index 72f4b8d..ffcf550 100644
--- a/packages/SystemUI/res/color/thermo_heat_foreground.xml
+++ b/packages/SystemUI/res/color/thermo_heat_foreground.xml
@@ -2,5 +2,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:color="@color/control_default_foreground" />
-  <item android:color="@color/GM2_red_700" />
+  <item android:color="@color/GM2_red_200" />
 </selector>
diff --git a/packages/SystemUI/res/drawable/control_background.xml b/packages/SystemUI/res/drawable/control_background.xml
index b246ea0..29b4efa 100644
--- a/packages/SystemUI/res/drawable/control_background.xml
+++ b/packages/SystemUI/res/drawable/control_background.xml
@@ -19,7 +19,7 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
   <item>
     <shape>
-      <solid android:color="?android:attr/colorBackgroundFloating"/>
+      <solid android:color="@color/control_default_background" />
       <corners android:radius="@dimen/control_corner_radius" />
     </shape>
   </item>
diff --git a/packages/SystemUI/res/drawable/ic_screen_record_background.xml b/packages/SystemUI/res/drawable/ic_screen_record_background.xml
new file mode 100644
index 0000000..9195305
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_screen_record_background.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorError"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M10,0L14,0A10,10 0,0 1,24 10L24,10A10,10 0,0 1,14 20L10,20A10,10 0,0 1,0 10L0,10A10,10 0,0 1,10 0z"
+        android:fillColor="@android:color/white"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record.xml
new file mode 100644
index 0000000..486af9e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record.xml
@@ -0,0 +1,16 @@
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record_1.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record_1.xml
new file mode 100644
index 0000000..ab2314e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record_1.xml
@@ -0,0 +1,18 @@
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable
+    level="1"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record_2.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record_2.xml
new file mode 100644
index 0000000..8764ff9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record_2.xml
@@ -0,0 +1,18 @@
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable
+    level="2"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record_3.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record_3.xml
new file mode 100644
index 0000000..0ff4d9a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record_3.xml
@@ -0,0 +1,18 @@
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable
+    level="3"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 95f205a..481c4db 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -14,8 +14,45 @@
   ~ limitations under the License
   -->
 
-<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/bubble_overflow_recycler"
-    android:layout_gravity="center_horizontal"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bubble_overflow_container"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"/>
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:layout_gravity="center_horizontal">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/bubble_overflow_recycler"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <LinearLayout
+        android:id="@+id/bubble_overflow_empty_state"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center">
+
+        <TextView
+            android:id="@+id/bubble_overflow_empty_title"
+            android:text="@string/bubble_overflow_empty_title"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+            android:textColor="?android:attr/textColorSecondary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"/>
+
+        <TextView
+            android:id="@+id/bubble_overflow_empty_subtitle"
+            android:fontFamily="@*android:string/config_bodyFontFamily"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+            android:textColor="?android:attr/textColorSecondary"
+            android:text="@string/bubble_overflow_empty_subtitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index f90012d..4fe21e5 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -2,15 +2,17 @@
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/global_actions_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
   <androidx.constraintlayout.widget.ConstraintLayout
       android:id="@+id/global_actions_grid_root"
       android:layout_width="match_parent"
-      android:layout_height="wrap_content"
+      android:layout_height="match_parent"
       android:clipChildren="false"
       android:clipToPadding="false"
+      android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
       android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
 
     <com.android.systemui.globalactions.GlobalActionsFlatLayout
@@ -25,65 +27,20 @@
         android:gravity="top | center_horizontal"
         android:clipChildren="false"
         android:clipToPadding="false"
-        android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
-        android:layout_marginTop="@dimen/global_actions_top_margin"
-        android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+        android:layout_marginTop="@dimen/global_actions_top_margin">
       <LinearLayout
-          android:layout_height="wrap_content"
+          android:id="@android:id/list"
           android:layout_width="wrap_content"
-          android:layoutDirection="ltr"
-          android:clipChildren="false"
-          android:clipToPadding="false"
-          android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin">
-        <!-- For separated items-->
-        <LinearLayout
-            android:id="@+id/separated_button"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
-            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
-            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
-            android:orientation="vertical"
-            android:gravity="center"
-            android:translationZ="@dimen/global_actions_translate"
-            />
-        <!-- Grid of action items -->
-        <com.android.systemui.globalactions.ListGridLayout
-            android:id="@android:id/list"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:gravity="right"
-            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
-            android:translationZ="@dimen/global_actions_translate"
-            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
-            >
-          <LinearLayout
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:visibility="gone"
-              android:layoutDirection="locale"
-              />
-          <LinearLayout
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:visibility="gone"
-              android:layoutDirection="locale"
-              />
-          <LinearLayout
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:visibility="gone"
-              android:layoutDirection="locale"
-              />
-        </com.android.systemui.globalactions.ListGridLayout>
-      </LinearLayout>
+          android:layout_height="wrap_content"
+          android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+          android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+          android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+          android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+          android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+          android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+          android:orientation="horizontal"
+          android:gravity="left"
+          android:translationZ="@dimen/global_actions_translate" />
     </com.android.systemui.globalactions.GlobalActionsFlatLayout>
 
     <LinearLayout
@@ -110,8 +67,7 @@
         android:layout_marginLeft="@dimen/global_actions_grid_horizontal_padding"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/global_actions_panel">
-
-    </LinearLayout>
+        app:layout_constraintTop_toBottomOf="@id/global_actions_panel"
+        app:layout_constraintBottom_toBottomOf="parent" />
   </androidx.constraintlayout.widget.ConstraintLayout>
 </ScrollView>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index d0151ff..0b74a11a 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -76,6 +76,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:src="@android:color/white"
+        android:elevation="8dp"
         android:visibility="gone"/>
     <com.android.systemui.screenshot.ScreenshotSelectorView
         android:id="@+id/global_screenshot_selector"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 09058f2..43ceb4e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -201,6 +201,7 @@
     <color name="GM2_grey_900">#202124</color>
 
     <color name="GM2_red_50">#FCE8E6</color>
+    <color name="GM2_red_200">#F6AEA9</color>
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#B71C1C</color>
     <color name="GM2_red_700">#C5221F</color>
@@ -213,6 +214,7 @@
     <color name="GM2_blue_700">#1967D2</color>
 
     <color name="GM2_yellow_50">#FEF7E0</color>
+    <color name="GM2_yellow_200">#FDE293</color>
     <color name="GM2_yellow_500">#FFFBBC04</color>
 
     <color name="GM2_green_500">#FF34A853</color>
@@ -222,6 +224,8 @@
     <color name="magnification_border_color">#FF9900</color>
 
     <!-- controls -->
-    <color name="control_default_foreground">?android:attr/textColorPrimary</color>
-    <color name="control_default_background">?android:attr/colorBackgroundFloating</color>
+    <color name="control_primary_text">@*android:color/foreground_material_dark</color>
+    <color name="control_secondary_text">@*android:color/dim_foreground_dark</color>
+    <color name="control_default_foreground">@*android:color/foreground_material_dark</color>
+    <color name="control_default_background">@*android:color/background_floating_material_dark</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ec56c1f..15575a4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1228,6 +1228,8 @@
     <!-- Screen Record -->
     <dimen name="screenrecord_dialog_padding">18dp</dimen>
     <dimen name="screenrecord_logo_size">24dp</dimen>
+    <dimen name="screenrecord_status_text_size">14sp</dimen>
+    <dimen name="screenrecord_status_icon_radius">5dp</dimen>
 
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6dd89d8..ef9e705 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1780,6 +1780,12 @@
     <!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary -->
     <string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string>
 
+    <!-- [CHAR LIMIT=NONE] Empty overflow title -->
+    <string name="bubble_overflow_empty_title">No recent bubbles</string>
+
+    <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
+    <string name="bubble_overflow_empty_subtitle">Recently dismissed bubbles will appear here for easy retrieval.</string>
+
     <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
     <string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6fedac0..fecb75c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -635,16 +635,16 @@
 
     <style name="TextAppearance.Control.Status">
         <item name="android:textSize">12sp</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">@color/control_primary_text</item>
     </style>
 
     <style name="TextAppearance.Control.Title">
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">@color/control_primary_text</item>
     </style>
     <style name="TextAppearance.Control.Subtitle">
         <item name="android:textSize">12sp</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">@color/control_secondary_text</item>
     </style>
 
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2d288ff..b1d39f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -123,4 +123,9 @@
      */
      void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen,
               in Insets visibleInsets, int taskId) = 21;
+
+    /**
+     * Sets the split-screen divider minimized state
+     */
+    void setSplitScreenMinimized(boolean minimized) = 22;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 27fe37e..dc5cb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -61,6 +61,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationListener;
@@ -329,6 +330,7 @@
     @Inject Lazy<DisplayImeController> mDisplayImeController;
     @Inject Lazy<RecordingController> mRecordingController;
     @Inject Lazy<ProtoTracer> mProtoTracer;
+    @Inject Lazy<Divider> mDivider;
 
     @Inject
     public Dependency() {
@@ -530,6 +532,7 @@
         mProviders.put(AutoHideController.class, mAutoHideController::get);
 
         mProviders.put(RecordingController.class, mRecordingController::get);
+        mProviders.put(Divider.class, mDivider::get);
 
         sDependency = this;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java b/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
deleted file mode 100644
index 5c0df17..0000000
--- a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IDockedStackListener;
-import android.view.WindowManagerGlobal;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Utility wrapper to listen for whether or not a docked stack exists, to be
- * used for things like the different overview icon in that mode.
- */
-public class DockedStackExistsListener {
-
-    private static final String TAG = "DockedStackExistsListener";
-
-    private static ArrayList<WeakReference<Consumer<Boolean>>> sCallbacks = new ArrayList<>();
-    private static boolean mLastExists;
-
-    static {
-        try {
-            WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(
-                    new IDockedStackListener.Stub() {
-                        @Override
-                        public void onDividerVisibilityChanged(boolean b) throws RemoteException {
-
-                        }
-
-                        @Override
-                        public void onDockedStackExistsChanged(boolean exists)
-                                throws RemoteException {
-                            DockedStackExistsListener.onDockedStackExistsChanged(exists);
-                        }
-
-                        @Override
-                        public void onDockedStackMinimizedChanged(boolean b, long l, boolean b1)
-                                throws RemoteException {
-
-                        }
-
-                        @Override
-                        public void onAdjustedForImeChanged(boolean b, long l)
-                                throws RemoteException {
-
-                        }
-
-                        @Override
-                        public void onDockSideChanged(int i) throws RemoteException {
-
-                        }
-                    });
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed registering docked stack exists listener", e);
-        }
-    }
-
-
-    private static void onDockedStackExistsChanged(boolean exists) {
-        mLastExists = exists;
-        synchronized (sCallbacks) {
-            sCallbacks.removeIf(wf -> {
-                Consumer<Boolean> l = wf.get();
-                if (l != null) l.accept(exists);
-                return l == null;
-            });
-        }
-    }
-
-    public static void register(Consumer<Boolean> callback) {
-        callback.accept(mLastExists);
-        synchronized (sCallbacks) {
-            sCallbacks.add(new WeakReference<>(callback));
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index d153fb0..611da0d 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -57,7 +57,10 @@
     }
 
     protected void setSeparatedViewVisibility(boolean visible) {
-        getSeparatedView().setVisibility(visible ? View.VISIBLE : View.GONE);
+        ViewGroup separatedView = getSeparatedView();
+        if (separatedView != null) {
+            separatedView.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e66b9f2..a2ae59e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -22,12 +22,8 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.systemui.tuner.TunablePadding.FLAG_END;
-import static com.android.systemui.tuner.TunablePadding.FLAG_START;
-
 import android.annotation.Dimension;
 import android.app.ActivityManager;
-import android.app.Fragment;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -69,13 +65,7 @@
 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.SecureSetting;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.tuner.TunablePadding;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.leak.RotationUtils;
@@ -86,8 +76,6 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import dagger.Lazy;
-
 /**
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
@@ -102,7 +90,6 @@
     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
     private static final boolean VERBOSE = false;
-    private final Lazy<StatusBar> mStatusBarLazy;
 
     private DisplayManager mDisplayManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -146,12 +133,10 @@
 
     @Inject
     public ScreenDecorations(Context context,
-            Lazy<StatusBar> statusBarLazy,
             @Main Handler handler,
             BroadcastDispatcher broadcastDispatcher,
             TunerService tunerService) {
         super(context);
-        mStatusBarLazy = statusBarLazy;
         mMainHandler = handler;
         mBroadcastDispatcher = broadcastDispatcher;
         mTunerService = tunerService;
@@ -161,7 +146,6 @@
     public void start() {
         mHandler = startHandlerThread();
         mHandler.post(this::startOnScreenDecorationsThread);
-        setupStatusBarPaddingIfNeeded();
     }
 
     @VisibleForTesting
@@ -440,42 +424,6 @@
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
     }
 
-
-    private void setupStatusBarPaddingIfNeeded() {
-        // TODO: This should be moved to a more appropriate place, as it is not related to the
-        // screen decorations overlay.
-        int padding = mContext.getResources().getDimensionPixelSize(
-                R.dimen.rounded_corner_content_padding);
-        if (padding != 0) {
-            setupStatusBarPadding(padding);
-        }
-
-    }
-
-    private void setupStatusBarPadding(int padding) {
-        // Add some padding to all the content near the edge of the screen.
-        StatusBar statusBar = mStatusBarLazy.get();
-        final View notificationShadeWindowView = statusBar.getNotificationShadeWindowView();
-        if (notificationShadeWindowView != null) {
-            TunablePadding.addTunablePadding(
-                    notificationShadeWindowView.findViewById(R.id.keyguard_header),
-                    PADDING, padding, FLAG_END);
-
-            final FragmentHostManager fragmentHostManager =
-                    FragmentHostManager.get(notificationShadeWindowView);
-            fragmentHostManager.addTagListener(QS.TAG,
-                    new TunablePaddingTagListener(padding, R.id.header));
-        }
-
-        final View statusBarWindow = statusBar.getStatusBarWindow();
-        if (statusBarWindow != null) {
-            final FragmentHostManager fragmentHostManager =
-                    FragmentHostManager.get(statusBarWindow);
-            fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
-                    new TunablePaddingTagListener(padding, R.id.status_bar));
-        }
-    }
-
     @VisibleForTesting
     WindowManager.LayoutParams getWindowLayoutParams() {
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
@@ -566,32 +514,6 @@
         view.setLayoutParams(params);
     }
 
-    @VisibleForTesting
-    static class TunablePaddingTagListener implements FragmentListener {
-
-        private final int mPadding;
-        private final int mId;
-        private TunablePadding mTunablePadding;
-
-        public TunablePaddingTagListener(int padding, int id) {
-            mPadding = padding;
-            mId = id;
-        }
-
-        @Override
-        public void onFragmentViewCreated(String tag, Fragment fragment) {
-            if (mTunablePadding != null) {
-                mTunablePadding.destroy();
-            }
-            View view = fragment.getView();
-            if (mId != 0) {
-                view = view.findViewById(mId);
-            }
-            mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
-                    FLAG_START | FLAG_END);
-        }
-    }
-
     public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
             RegionInterceptableView {
 
@@ -763,11 +685,11 @@
                 return false;
             }
             if (mStart) {
-                return displayCutout.getSafeInsetLeft() > 0
-                        || displayCutout.getSafeInsetTop() > 0;
+                return !displayCutout.getBoundingRectLeft().isEmpty()
+                        || !displayCutout.getBoundingRectTop().isEmpty();
             } else {
-                return displayCutout.getSafeInsetRight() > 0
-                        || displayCutout.getSafeInsetBottom() > 0;
+                return !displayCutout.getBoundingRectRight().isEmpty()
+                        || !displayCutout.getBoundingRectBottom().isEmpty();
             }
         }
 
@@ -809,15 +731,15 @@
 
         private int getGravity(DisplayCutout displayCutout) {
             if (mStart) {
-                if (displayCutout.getSafeInsetLeft() > 0) {
+                if (!displayCutout.getBoundingRectLeft().isEmpty()) {
                     return Gravity.LEFT;
-                } else if (displayCutout.getSafeInsetTop() > 0) {
+                } else if (!displayCutout.getBoundingRectTop().isEmpty()) {
                     return Gravity.TOP;
                 }
             } else {
-                if (displayCutout.getSafeInsetRight() > 0) {
+                if (!displayCutout.getBoundingRectRight().isEmpty()) {
                     return Gravity.RIGHT;
-                } else if (displayCutout.getSafeInsetBottom() > 0) {
+                } else if (!displayCutout.getBoundingRectBottom().isEmpty()) {
                     return Gravity.BOTTOM;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 0f7f1be..023b74b 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -19,7 +19,6 @@
 
 import android.annotation.StringRes;
 import android.content.Context;
-import android.view.WindowManager;
 import android.widget.Toast;
 
 public class SysUIToast {
@@ -29,10 +28,7 @@
     }
 
     public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
-        Toast toast = Toast.makeText(context, text, duration);
-        toast.getWindowParams().privateFlags |=
-                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        return toast;
+        return Toast.makeText(context, text, duration);
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 68b05e3..9de1040 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricPrompt;
@@ -33,6 +34,8 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -126,18 +129,18 @@
         mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
     }
 
-    private void setTextOrHide(TextView view, String string) {
-        if (TextUtils.isEmpty(string)) {
+    private void setTextOrHide(TextView view, CharSequence text) {
+        if (TextUtils.isEmpty(text)) {
             view.setVisibility(View.GONE);
         } else {
-            view.setText(string);
+            view.setText(text);
         }
 
         Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
     }
 
-    private void setText(TextView view, String string) {
-        view.setText(string);
+    private void setText(TextView view, CharSequence text) {
+        view.setText(text);
     }
 
     void setEffectiveUserId(int effectiveUserId) {
@@ -173,11 +176,9 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE));
-        setTextOrHide(mSubtitleView,
-                mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE));
-        setTextOrHide(mDescriptionView,
-                mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
+        setText(mTitleView, getTitle(mBiometricPromptBundle));
+        setTextOrHide(mSubtitleView, getSubtitle(mBiometricPromptBundle));
+        setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle));
 
         final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
         final Drawable image;
@@ -279,4 +280,28 @@
             }
         }
     }
+
+    @Nullable
+    private static CharSequence getTitle(@NonNull Bundle bundle) {
+        final CharSequence credentialTitle =
+                bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE);
+        return credentialTitle != null ? credentialTitle
+                : bundle.getCharSequence(BiometricPrompt.KEY_TITLE);
+    }
+
+    @Nullable
+    private static CharSequence getSubtitle(@NonNull Bundle bundle) {
+        final CharSequence credentialSubtitle =
+                bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE);
+        return credentialSubtitle != null ? credentialSubtitle
+                : bundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
+    }
+
+    @Nullable
+    private static CharSequence getDescription(@NonNull Bundle bundle) {
+        final CharSequence credentialDescription =
+                bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION);
+        return credentialDescription != null ? credentialDescription
+                : bundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 45705b7..cdeb5c3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -38,9 +38,11 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.io.FileDescriptor;
@@ -50,7 +52,7 @@
 /**
  * Encapsulates the data and UI elements of a bubble.
  */
-class Bubble {
+class Bubble implements BubbleViewProvider {
     private static final String TAG = "Bubble";
 
     private NotificationEntry mEntry;
@@ -148,12 +150,12 @@
     }
 
     @Nullable
-    BadgedImageView getIconView() {
+    public BadgedImageView getIconView() {
         return mIconView;
     }
 
     @Nullable
-    BubbleExpandedView getExpandedView() {
+    public BubbleExpandedView getExpandedView() {
         return mExpandedView;
     }
 
@@ -226,6 +228,10 @@
         mIconView.update(this);
     }
 
+    void setInflated(boolean inflated) {
+        mInflated = inflated;
+    }
+
     /**
      * Set visibility of bubble in the expanded state.
      *
@@ -234,7 +240,7 @@
      * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
      * and setting {@code false} actually means rendering the expanded view in transparent.
      */
-    void setContentVisibility(boolean visibility) {
+    public void setContentVisibility(boolean visibility) {
         if (mExpandedView != null) {
             mExpandedView.setContentVisibility(visibility);
         }
@@ -477,4 +483,36 @@
     public int hashCode() {
         return Objects.hash(mKey);
     }
+
+    public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) {
+        if (this.getEntry() == null
+                || this.getEntry().getSbn() == null) {
+            SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+                    null /* package name */,
+                    null /* notification channel */,
+                    0 /* notification ID */,
+                    0 /* bubble position */,
+                    bubbleCount,
+                    action,
+                    normalX,
+                    normalY,
+                    false /* unread bubble */,
+                    false /* on-going bubble */,
+                    false /* isAppForeground (unused) */);
+        } else {
+            StatusBarNotification notification = this.getEntry().getSbn();
+            SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+                    notification.getPackageName(),
+                    notification.getNotification().getChannelId(),
+                    notification.getId(),
+                    index,
+                    bubbleCount,
+                    action,
+                    normalX,
+                    normalY,
+                    this.showInShade(),
+                    this.isOngoing(),
+                    false /* isAppForeground (unused) */);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 05838ab..1f94dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -72,9 +72,7 @@
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController.BubbleExpandListener;
-import com.android.systemui.bubbles.BubbleController.BubbleStateChangeListener;
-import com.android.systemui.bubbles.BubbleController.NotifCallback;
+import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PinnedStackListenerForwarder;
@@ -105,16 +103,12 @@
 import java.util.HashSet;
 import java.util.List;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Bubbles are a special type of content that can "float" on top of other apps or System UI.
  * Bubbles can be expanded to show more content.
  *
  * The controller manages addition, removal, and visible state of bubbles on screen.
  */
-@Singleton
 public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -277,7 +271,6 @@
         }
     }
 
-    @Inject
     public BubbleController(Context context,
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarStateController statusBarStateController,
@@ -298,6 +291,9 @@
                 notifPipeline, featureFlags, dumpController);
     }
 
+    /**
+     * Injected constructor. See {@link BubbleModule}.
+     */
     public BubbleController(Context context,
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarStateController statusBarStateController,
@@ -749,7 +745,8 @@
     }
 
     void promoteBubbleFromOverflow(Bubble bubble) {
-        mBubbleData.promoteBubbleFromOverflow(bubble);
+        bubble.setInflateSynchronously(mInflateSynchronously);
+        mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 673121f..8a5aad8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -199,16 +199,21 @@
         dispatchPendingChanges();
     }
 
-    public void promoteBubbleFromOverflow(Bubble bubble) {
+    public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack,
+            BubbleIconFactory factory) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
         }
-        mOverflowBubbles.remove(bubble);
-        doAdd(bubble);
-        setSelectedBubbleInternal(bubble);
+
         // Preserve new order for next repack, which sorts by last updated time.
         bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
-        trim();
+        setSelectedBubbleInternal(bubble);
+        mOverflowBubbles.remove(bubble);
+
+        bubble.inflate(
+                b -> notificationEntryUpdated(bubble, /* suppressFlyout */
+                        false, /* showInShade */ true),
+                mContext, stack, factory);
         dispatchPendingChanges();
     }
 
@@ -445,6 +450,10 @@
             mOverflowBubbles.add(0, bubbleToRemove);
             if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
                 // Remove oldest bubble.
+                if (DEBUG_BUBBLE_DATA) {
+                    Log.d(TAG, "Overflow full. Remove bubble: " + mOverflowBubbles.get(
+                            mOverflowBubbles.size() - 1));
+                }
                 mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
             }
         }
@@ -511,7 +520,7 @@
         if (Objects.equals(bubble, mSelectedBubble)) {
             return;
         }
-        if (bubble != null && !mBubbles.contains(bubble)) {
+        if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
             Log.e(TAG, "Cannot select bubble which doesn't exist!"
                     + " (" + bubble + ") bubbles=" + mBubbles);
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0d5261d..fe191f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -294,7 +294,8 @@
         ta.recycle();
 
         mPointerDrawable.setTint(bgColor);
-        if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
+        if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+                mContext.getResources())) {
             mActivityView.setCornerRadius(cornerRadius);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
new file mode 100644
index 0000000..6cf1086
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static android.view.View.GONE;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * Class for showing aged out bubbles.
+ */
+public class BubbleOverflow implements BubbleViewProvider {
+
+    private ImageView mOverflowBtn;
+    private BubbleExpandedView mOverflowExpandedView;
+    private LayoutInflater mInflater;
+    private Context mContext;
+
+    public BubbleOverflow(Context context) {
+        mContext = context;
+        mInflater = LayoutInflater.from(context);
+    }
+
+    public void setUpOverflow(ViewGroup parentViewGroup) {
+        mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate(
+                R.layout.bubble_expanded_view, parentViewGroup /* root */,
+                false /* attachToRoot */);
+        mOverflowExpandedView.setOverflow(true);
+
+        mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button,
+                parentViewGroup /* root */,
+                false /* attachToRoot */);
+
+        setOverflowBtnTheme();
+        mOverflowBtn.setVisibility(GONE);
+    }
+
+    ImageView getBtn() {
+        return mOverflowBtn;
+    }
+
+    void setBtnVisible(int visible) {
+        mOverflowBtn.setVisibility(visible);
+    }
+
+    // TODO(b/149146374) Propagate theme change to bubbles in overflow.
+    void setOverflowBtnTheme() {
+        TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{android.R.attr.colorBackgroundFloating});
+        int bgColor = ta.getColor(0, Color.WHITE /* default */);
+        ta.recycle();
+
+        InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28);
+        ColorDrawable bg = new ColorDrawable(bgColor);
+        AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
+        mOverflowBtn.setImageDrawable(adaptiveIcon);
+    }
+
+
+    public BubbleExpandedView getExpandedView() {
+        return mOverflowExpandedView;
+    }
+
+    public void setContentVisibility(boolean visible) {
+        mOverflowExpandedView.setContentVisibility(visible);
+    }
+
+    public void logUIEvent(int bubbleCount, int action, float normalX, float normalY,
+            int index) {
+        // TODO(b/149133814) Log overflow UI events.
+    }
+
+    public View getIconView() {
+        return mOverflowBtn;
+    }
+
+    public String getKey() {
+        return BubbleOverflowActivity.KEY;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2d55a1d..756c598 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -26,7 +26,10 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -44,8 +47,10 @@
  * Must be public to be accessible to androidx...AppComponentFactory
  */
 public class BubbleOverflowActivity extends Activity {
+    public static final String KEY = "Overflow";
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
 
+    private LinearLayout mEmptyState;
     private BubbleController mBubbleController;
     private BubbleOverflowAdapter mAdapter;
     private RecyclerView mRecyclerView;
@@ -64,6 +69,7 @@
         setBackgroundColor();
 
         mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
+        mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
         mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
         mRecyclerView.setLayoutManager(
                 new GridLayoutManager(getApplicationContext(),
@@ -73,9 +79,9 @@
                 mBubbleController::promoteBubbleFromOverflow);
         mRecyclerView.setAdapter(mAdapter);
 
-        updateData(mBubbleController.getOverflowBubbles());
+        onDataChanged(mBubbleController.getOverflowBubbles());
         mBubbleController.setOverflowCallback(() -> {
-            updateData(mBubbleController.getOverflowBubbles());
+            onDataChanged(mBubbleController.getOverflowBubbles());
         });
     }
 
@@ -87,7 +93,7 @@
         findViewById(android.R.id.content).setBackgroundColor(bgColor);
     }
 
-    void updateData(List<Bubble> bubbles) {
+    void onDataChanged(List<Bubble> bubbles) {
         mOverflowBubbles.clear();
         if (bubbles.size() > mMaxBubbles) {
             mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size()));
@@ -96,6 +102,12 @@
         }
         mAdapter.notifyDataSetChanged();
 
+        if (mOverflowBubbles.isEmpty()) {
+            mEmptyState.setVisibility(View.VISIBLE);
+        } else {
+            mEmptyState.setVisibility(View.GONE);
+        }
+
         if (DEBUG_OVERFLOW) {
             Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
                     mOverflowBubbles, /*selected*/ null));
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bce172b..9a6295a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,8 +33,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
@@ -42,13 +40,9 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.InsetDrawable;
 import android.os.Bundle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.DisplayCutout;
@@ -63,7 +57,6 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
@@ -199,7 +192,7 @@
     private int mPointerHeight;
     private int mStatusBarHeight;
     private int mImeOffset;
-    private Bubble mExpandedBubble;
+    private BubbleViewProvider mExpandedBubble;
     private boolean mIsExpanded;
 
     /** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -322,8 +315,8 @@
     private Runnable mAfterMagnet;
 
     private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
-    private BubbleExpandedView mOverflowExpandedView;
-    private ImageView mOverflowBtn;
+
+    private BubbleOverflow mBubbleOverflow;
 
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer) {
@@ -333,7 +326,6 @@
         mInflater = LayoutInflater.from(context);
         mTouchHandler = new BubbleTouchHandler(this, data, context);
         setOnTouchListener(mTouchHandler);
-        mInflater = LayoutInflater.from(context);
 
         Resources res = getResources();
         mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -407,12 +399,8 @@
                         .setStiffness(SpringForce.STIFFNESS_LOW)
                         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
-            if (mIsExpanded) {
-                if (mExpandedBubble == null) {
-                    mOverflowExpandedView.updateView();
-                } else {
-                    mExpandedBubble.getExpandedView().updateView();
-                }
+            if (mIsExpanded && mExpandedBubble != null) {
+                mExpandedBubble.getExpandedView().updateView();
             }
         });
 
@@ -420,8 +408,12 @@
         setFocusable(true);
         mBubbleContainer.bringToFront();
 
+        mBubbleOverflow = new BubbleOverflow(mContext);
         if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
-            setUpOverflow();
+            mBubbleOverflow.setUpOverflow(this);
+            mBubbleContainer.addView(mBubbleOverflow.getBtn(), 0,
+                    new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
         }
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
@@ -432,9 +424,7 @@
                     // Update the insets after we're done translating otherwise position
                     // calculation for them won't be correct.
                     () -> {
-                        if (mExpandedBubble == null) {
-                            mOverflowExpandedView.updateInsets(insets);
-                        } else {
+                        if (mExpandedBubble != null) {
                             mExpandedBubble.getExpandedView().updateInsets(insets);
                         }
                     });
@@ -449,9 +439,7 @@
                     // Reposition & adjust the height for new orientation
                     if (mIsExpanded) {
                         mExpandedViewContainer.setTranslationY(getExpandedViewY());
-                        if (mExpandedBubble == null) {
-                            mOverflowExpandedView.updateView();
-                        } else {
+                        if (mExpandedBubble != null) {
                             mExpandedBubble.getExpandedView().updateView();
                         }
                     }
@@ -516,36 +504,8 @@
         });
     }
 
-    private void setUpOverflow() {
-        mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate(
-                R.layout.bubble_expanded_view, this /* root */, false /* attachToRoot */);
-        mOverflowExpandedView.setOverflow(true);
-
-        mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button,
-                this /* root */,
-                false /* attachToRoot */);
-
-        mBubbleContainer.addView(mOverflowBtn, 0,
-                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-
-        TypedArray ta = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.colorBackgroundFloating});
-        int bgColor = ta.getColor(0, Color.WHITE /* default */);
-        ta.recycle();
-
-        InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28);
-        ColorDrawable bg = new ColorDrawable(bgColor);
-        AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
-        mOverflowBtn.setImageDrawable(adaptiveIcon);
-
-        mOverflowBtn.setVisibility(GONE);
-    }
-
     void showExpandedViewContents(int displayId) {
-        if (mOverflowExpandedView != null
-                && mOverflowExpandedView.getVirtualDisplayId() == displayId) {
-            mOverflowExpandedView.setContentVisibility(true);
-        } else if (mExpandedBubble != null
+        if (mExpandedBubble != null
                 && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
             mExpandedBubble.setContentVisibility(true);
         }
@@ -568,6 +528,9 @@
      */
     public void onThemeChanged() {
         setUpFlyout();
+        if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
+            mBubbleOverflow.setOverflowBtnTheme();
+        }
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -749,15 +712,22 @@
     /**
      * The {@link BadgedImageView} that is expanded, null if one does not exist.
      */
-    BadgedImageView getExpandedBubbleView() {
+    View getExpandedBubbleView() {
         return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
     }
 
     /**
      * The {@link Bubble} that is expanded, null if one does not exist.
      */
+    @Nullable
     Bubble getExpandedBubble() {
-        return mExpandedBubble;
+        if (mExpandedBubble == null
+                || (BubbleExperimentConfig.allowBubbleOverflow(mContext)
+                    && mExpandedBubble.getIconView() == mBubbleOverflow.getBtn()
+                    && mExpandedBubble.getKey() == BubbleOverflowActivity.KEY)) {
+            return null;
+        }
+        return (Bubble) mExpandedBubble;
     }
 
     // via BubbleData.Listener
@@ -795,6 +765,7 @@
         if (removedIndex >= 0) {
             mBubbleContainer.removeViewAt(removedIndex);
             bubble.cleanupExpandedState();
+            bubble.setInflated(false);
             logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -810,7 +781,7 @@
             if (DEBUG_BUBBLE_STACK_VIEW) {
                 Log.d(TAG, "Show overflow button.");
             }
-            mOverflowBtn.setVisibility(VISIBLE);
+            mBubbleOverflow.setBtnVisible(VISIBLE);
             if (apply) {
                 mExpandedAnimationController.expandFromStack(() -> {
                     updatePointerPosition();
@@ -820,7 +791,7 @@
             if (DEBUG_BUBBLE_STACK_VIEW) {
                 Log.d(TAG, "Collapsed. Hide overflow button.");
             }
-            mOverflowBtn.setVisibility(GONE);
+            mBubbleOverflow.setBtnVisible(GONE);
         }
     }
 
@@ -841,7 +812,7 @@
     }
 
     void showOverflow() {
-        setSelectedBubble(null);
+        setSelectedBubble(mBubbleOverflow);
     }
 
     /**
@@ -850,14 +821,14 @@
      * position of any bubble.
      */
     // via BubbleData.Listener
-    public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
+    public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
         }
         if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
             return;
         }
-        final Bubble previouslySelected = mExpandedBubble;
+        final BubbleViewProvider previouslySelected = mExpandedBubble;
         mExpandedBubble = bubbleToSelect;
 
         if (mIsExpanded) {
@@ -866,14 +837,11 @@
             // expanded view becomes visible on the screen. See b/126856255
             mExpandedViewContainer.setAlpha(0.0f);
             mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
-                if (previouslySelected == null) {
-                    mOverflowExpandedView.setContentVisibility(false);
-                } else {
-                    previouslySelected.setContentVisibility(false);
-                }
+                previouslySelected.setContentVisibility(false);
                 updateExpandedBubble();
                 updatePointerPosition();
                 requestUpdate();
+
                 logBubbleEvent(previouslySelected,
                         SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
                 logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
@@ -933,8 +901,8 @@
         if (mIsExpanded) {
             if (isIntersecting(mBubbleContainer, x, y)) {
                 if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
-                        && isIntersecting(mOverflowBtn, x, y)) {
-                    return mOverflowBtn;
+                        && isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
+                    return mBubbleOverflow.getBtn();
                 }
                 // Could be tapping or dragging a bubble while expanded
                 for (int i = 0; i < getBubbleCount(); i++) {
@@ -1022,7 +990,7 @@
 
     private void animateCollapse() {
         mIsExpanded = false;
-        final Bubble previouslySelected = mExpandedBubble;
+        final BubbleViewProvider previouslySelected = mExpandedBubble;
         beforeExpandedViewAnimation();
 
         if (DEBUG_BUBBLE_STACK_VIEW) {
@@ -1038,11 +1006,7 @@
                 () -> {
                     mBubbleContainer.setActiveController(mStackAnimationController);
                     afterExpandedViewAnimation();
-                    if (previouslySelected == null) {
-                        mOverflowExpandedView.setContentVisibility(false);
-                    } else {
-                        previouslySelected.setContentVisibility(false);
-                    }
+                    previouslySelected.setContentVisibility(false);
                 });
 
         mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
@@ -1085,7 +1049,7 @@
                 mExpandedAnimateYDistance);
     }
 
-    private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
+    private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) {
         if (mExpandListener != null && bubble != null) {
             mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
         }
@@ -1605,11 +1569,8 @@
             Log.d(TAG, "updateExpandedBubble()");
         }
         mExpandedViewContainer.removeAllViews();
-        if (mIsExpanded) {
-            BubbleExpandedView bev = mOverflowExpandedView;
-            if (mExpandedBubble != null) {
-                bev = mExpandedBubble.getExpandedView();
-            }
+        if (mIsExpanded && mExpandedBubble != null) {
+            BubbleExpandedView bev = mExpandedBubble.getExpandedView();
             mExpandedViewContainer.addView(bev);
             bev.populateExpandedView();
             mExpandedViewContainer.setVisibility(VISIBLE);
@@ -1628,9 +1589,7 @@
             if (!mExpandedViewYAnim.isRunning()) {
                 // We're not animating so set the value
                 mExpandedViewContainer.setTranslationY(y);
-                if (mExpandedBubble == null) {
-                    mOverflowExpandedView.updateView();
-                } else {
+                if (mExpandedBubble != null) {
                     mExpandedBubble.getExpandedView().updateView();
                 }
             } else {
@@ -1685,15 +1644,16 @@
     /**
      * Finds the bubble index within the stack.
      *
-     * @param bubble the bubble to look up.
+     * @param provider the bubble view provider with the bubble to look up.
      * @return the index of the bubble view within the bubble stack. The range of the position
      * is between 0 and the bubble count minus 1.
      */
-    int getBubbleIndex(@Nullable Bubble bubble) {
-        if (bubble == null) {
+    int getBubbleIndex(@Nullable BubbleViewProvider provider) {
+        if (provider == null || provider.getKey() == BubbleOverflowActivity.KEY) {
             return 0;
         }
-        return mBubbleContainer.indexOfChild(bubble.getIconView());
+        Bubble b = (Bubble) provider;
+        return mBubbleContainer.indexOfChild(b.getIconView());
     }
 
     /**
@@ -1725,36 +1685,12 @@
      *               the user interaction is not specific to one bubble.
      * @param action the user interaction enum.
      */
-    private void logBubbleEvent(@Nullable Bubble bubble, int action) {
-        if (bubble == null || bubble.getEntry() == null
-                || bubble.getEntry().getSbn() == null) {
-            SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
-                    null /* package name */,
-                    null /* notification channel */,
-                    0 /* notification ID */,
-                    0 /* bubble position */,
-                    getBubbleCount(),
-                    action,
-                    getNormalizedXPosition(),
-                    getNormalizedYPosition(),
-                    false /* unread bubble */,
-                    false /* on-going bubble */,
-                    false /* isAppForeground (unused) */);
-        } else {
-            StatusBarNotification notification = bubble.getEntry().getSbn();
-            SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
-                    notification.getPackageName(),
-                    notification.getNotification().getChannelId(),
-                    notification.getId(),
-                    getBubbleIndex(bubble),
-                    getBubbleCount(),
-                    action,
-                    getNormalizedXPosition(),
-                    getNormalizedYPosition(),
-                    bubble.showInShade(),
-                    bubble.isOngoing(),
-                    false /* isAppForeground (unused) */);
+    private void logBubbleEvent(@Nullable BubbleViewProvider bubble, int action) {
+        if (bubble == null) {
+            return;
         }
+        bubble.logUIEvent(getBubbleCount(), action, getNormalizedXPosition(),
+                getNormalizedYPosition(), getBubbleIndex(bubble));
     }
 
     /**
@@ -1762,14 +1698,10 @@
      * a back key down/up event pair is forwarded to the bubble Activity.
      */
     boolean performBackPressIfNeeded() {
-        if (!isExpanded()) {
+        if (!isExpanded() || mExpandedBubble == null) {
             return false;
         }
-        if (mExpandedBubble == null) {
-            return mOverflowExpandedView.performBackPressIfNeeded();
-        } else {
-            return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
-        }
+        return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
     }
 
     /** For debugging only */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
new file mode 100644
index 0000000..59fc435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.view.View;
+
+/**
+ * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
+ */
+interface BubbleViewProvider {
+    BubbleExpandedView getExpandedView();
+    void setContentVisibility(boolean visible);
+    View getIconView();
+    void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);
+    String getKey();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
new file mode 100644
index 0000000..0337ee3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** */
+@Module
+public interface BubbleModule {
+
+    /**
+     */
+    @Singleton
+    @Provides
+    static BubbleController newBubbleController(
+            Context context,
+            NotificationShadeWindowController notificationShadeWindowController,
+            StatusBarStateController statusBarStateController,
+            ShadeController shadeController,
+            BubbleData data,
+            ConfigurationController configurationController,
+            NotificationInterruptionStateProvider interruptionStateProvider,
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager,
+            NotificationEntryManager entryManager,
+            NotifPipeline notifPipeline,
+            FeatureFlags featureFlags,
+            DumpController dumpController) {
+        return new BubbleController(
+                context,
+                notificationShadeWindowController,
+                statusBarStateController,
+                shadeController,
+                data,
+                /* synchronizer */null,
+                configurationController,
+                interruptionStateProvider,
+                zenModeController,
+                notifUserManager,
+                groupManager,
+                entryManager,
+                notifPipeline,
+                featureFlags,
+                dumpController);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index e09d20b..9ec71c7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -86,7 +86,7 @@
         private const val MSG_ACTION = 2
         private const val MSG_UNBIND = 3
         private const val BIND_RETRY_DELAY = 1000L // ms
-        private const val LOAD_TIMEOUT = 5000L // ms
+        private const val LOAD_TIMEOUT_SECONDS = 30L // seconds
         private const val MAX_BIND_RETRIES = 5
         private const val MAX_CONTROLS_REQUEST = 100000L
         private const val DEBUG = true
@@ -231,7 +231,7 @@
             // Don't accept load callbacks after this
             lastLoadCallback = null
             unbindService()
-        }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+        }, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
 
         invokeOrQueue(::load, Message.Load)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
index 44f3fbc..275c778 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
@@ -16,6 +16,20 @@
 
 package com.android.systemui.controls.ui
 
+/**
+ * All controls need to respond to changes in state and handle user-generated events.
+ * Implementations of this interface provide these different means by adding their own
+ * event handlers, and will update the control ui as they see fit.
+ */
 interface Behavior {
-    fun apply(cvh: ControlViewHolder, cws: ControlWithState)
+
+    /**
+     * Only called once per instance
+     */
+    fun initialize(cvh: ControlViewHolder)
+
+    /**
+     * Will be invoked on every update provided to the Control
+     */
+    fun bind(cws: ControlWithState)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 55c1b6a..feaea7c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -17,14 +17,15 @@
 package com.android.systemui.controls.ui
 
 import android.content.Context
+import android.graphics.BlendMode
 import android.graphics.drawable.ClipDrawable
-import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.Icon
 import android.graphics.drawable.LayerDrawable
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
 import android.service.controls.templates.ControlTemplate
 import android.service.controls.templates.TemperatureControlTemplate
+import android.service.controls.templates.ThumbnailTemplate
 import android.service.controls.templates.ToggleRangeTemplate
 import android.service.controls.templates.ToggleTemplate
 import android.view.View
@@ -36,12 +37,15 @@
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.R
 
+import kotlin.reflect.KClass
+
 private const val UPDATE_DELAY_IN_MILLIS = 3000L
 
 class ControlViewHolder(
     val layout: ViewGroup,
     val controlsController: ControlsController,
-    val uiExecutor: DelayableExecutor
+    val uiExecutor: DelayableExecutor,
+    val bgExecutor: DelayableExecutor
 ) {
     val icon: ImageView = layout.requireViewById(R.id.icon)
     val status: TextView = layout.requireViewById(R.id.status)
@@ -50,15 +54,14 @@
     val subtitle: TextView = layout.requireViewById(R.id.subtitle)
     val context: Context = layout.getContext()
     val clipLayer: ClipDrawable
-    val gd: GradientDrawable
     lateinit var cws: ControlWithState
     var cancelUpdate: Runnable? = null
+    var behavior: Behavior? = null
 
     init {
         val ld = layout.getBackground() as LayerDrawable
         ld.mutate()
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
-        gd = clipLayer.getDrawable() as GradientDrawable
     }
 
     fun bindData(cws: ControlWithState) {
@@ -83,7 +86,14 @@
             })
         }
 
-        findBehavior(status, template).apply(this, cws)
+        val clazz = findBehavior(status, template)
+        if (behavior == null || behavior!!::class != clazz) {
+            // Behavior changes can signal a change in template from the app or
+            // first time setup
+            behavior = clazz.java.newInstance()
+            behavior?.initialize(this)
+        }
+        behavior?.bind(cws)
     }
 
     fun actionResponse(@ControlAction.ResponseResult response: Int) {
@@ -115,20 +125,14 @@
         controlsController.action(cws.ci, action)
     }
 
-    private fun findBehavior(status: Int, template: ControlTemplate): Behavior {
+    private fun findBehavior(status: Int, template: ControlTemplate): KClass<out Behavior> {
         return when {
-            status == Control.STATUS_UNKNOWN -> UnknownBehavior()
-            template is ToggleTemplate -> ToggleBehavior()
-            template is ToggleRangeTemplate -> ToggleRangeBehavior()
-            template is TemperatureControlTemplate -> TemperatureControlBehavior()
-            else -> {
-                object : Behavior {
-                    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
-                        cvh.status.setText(cws.control?.getStatusText())
-                        cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
-                    }
-                }
-            }
+            status == Control.STATUS_UNKNOWN -> UnknownBehavior::class
+            template is ToggleTemplate -> ToggleBehavior::class
+            template is ToggleRangeTemplate -> ToggleRangeBehavior::class
+            template is TemperatureControlTemplate -> TemperatureControlBehavior::class
+            template is ThumbnailTemplate -> StaticBehavior::class
+            else -> DefaultBehavior::class
         }
     }
 
@@ -141,7 +145,10 @@
         icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
         icon.setImageTintList(fg)
 
-        gd.setColor(bg)
+        clipLayer.getDrawable().apply {
+            setTintBlendMode(BlendMode.HUE)
+            setTintList(bg)
+        }
     }
 
     fun setEnabled(enabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 9e6636f..98cdf28 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -223,7 +223,7 @@
             val item = inflater.inflate(
                 R.layout.controls_base_item, lastRow, false) as ViewGroup
             lastRow.addView(item)
-            val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor)
+            val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor, bgExecutor)
             val key = ControlKey(it.component, it.controlId)
             cvh.bindData(controlsById.getValue(key))
             controlViewsById.put(key, cvh)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt
new file mode 100644
index 0000000..af581e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+
+/**
+ * Use a path to add mask for corners around the drawable, to match the radius
+ * of the underlying shape.
+ */
+class CornerDrawable(val wrapped: Drawable, val cornerRadius: Float) : DrawableWrapper(wrapped) {
+    val path: Path = Path()
+
+    init {
+        val b = getBounds()
+        updatePath(RectF(b))
+    }
+
+    override fun draw(canvas: Canvas) {
+        canvas.clipPath(path)
+        super.draw(canvas)
+    }
+
+    override fun setBounds(l: Int, t: Int, r: Int, b: Int) {
+        updatePath(RectF(l.toFloat(), t.toFloat(), r.toFloat(), b.toFloat()))
+        super.setBounds(l, t, r, b)
+    }
+
+    override fun setBounds(r: Rect) {
+        updatePath(RectF(r))
+        super.setBounds(r)
+    }
+
+    private fun updatePath(r: RectF) {
+        path.reset()
+        path.addRoundRect(r, cornerRadius, cornerRadius, Path.Direction.CW)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
new file mode 100644
index 0000000..1747929
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+class DefaultBehavior : Behavior {
+    lateinit var cvh: ControlViewHolder
+
+    override fun initialize(cvh: ControlViewHolder) {
+        this.cvh = cvh
+    }
+
+    override fun bind(cws: ControlWithState) {
+        cvh.status.setText(cws.control?.getStatusText() ?: "")
+        cvh.setEnabled(false)
+        cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StaticBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StaticBehavior.kt
new file mode 100644
index 0000000..c006d6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StaticBehavior.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.graphics.drawable.ClipDrawable
+import android.graphics.drawable.LayerDrawable
+import android.service.controls.Control
+import android.service.controls.templates.ThumbnailTemplate
+
+import com.android.systemui.R
+import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
+
+/**
+ * Used for controls that cannot be interacted with. Information is presented to the user
+ * but no actions can be taken. If using a ThumbnailTemplate, the background image will
+ * be changed.
+ */
+class StaticBehavior() : Behavior {
+    lateinit var control: Control
+    lateinit var cvh: ControlViewHolder
+
+    override fun initialize(cvh: ControlViewHolder) {
+        this.cvh = cvh
+    }
+
+    override fun bind(cws: ControlWithState) {
+        this.control = cws.control!!
+
+        cvh.status.setText(control.getStatusText())
+
+        val ld = cvh.layout.getBackground() as LayerDrawable
+        val clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
+
+        clipLayer.setLevel(MAX_LEVEL)
+        cvh.setEnabled(true)
+        cvh.applyRenderInfo(RenderInfo.lookup(control.getDeviceType(), true))
+
+        val template = control.getControlTemplate()
+        if (template is ThumbnailTemplate) {
+            cvh.bgExecutor.execute {
+                // clear the default tinting in favor of only using alpha
+                val drawable = template.getThumbnail().loadDrawable(cvh.context)
+                drawable.setTintList(null)
+                drawable.setAlpha((0.45 * 255).toInt())
+                cvh.uiExecutor.execute {
+                    val radius = cvh.context.getResources()
+                            .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat()
+                    clipLayer.setDrawable(CornerDrawable(drawable, radius))
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index 7661c6f..239d2e5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -16,12 +16,10 @@
 
 package com.android.systemui.controls.ui
 
-import android.content.Context
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.service.controls.Control
 import android.service.controls.templates.TemperatureControlTemplate
-import android.widget.TextView
 
 import com.android.systemui.R
 import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
@@ -32,15 +30,15 @@
     lateinit var control: Control
     lateinit var cvh: ControlViewHolder
     lateinit var template: TemperatureControlTemplate
-    lateinit var status: TextView
-    lateinit var context: Context
 
-    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
-        this.control = cws.control!!
+    override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        status = cvh.status
+    }
 
-        status.setText(control.getStatusText())
+    override fun bind(cws: ControlWithState) {
+        this.control = cws.control!!
+
+        cvh.status.setText(control.getStatusText())
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
index 4c35d26..d306d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -16,11 +16,9 @@
 
 package com.android.systemui.controls.ui
 
-import android.content.Context
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.view.View
-import android.widget.TextView
 import android.service.controls.Control
 import android.service.controls.templates.ToggleTemplate
 
@@ -33,21 +31,21 @@
     lateinit var template: ToggleTemplate
     lateinit var control: Control
     lateinit var cvh: ControlViewHolder
-    lateinit var context: Context
-    lateinit var status: TextView
 
-    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
-        this.control = cws.control!!
+    override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        status = cvh.status
-
-        status.setText(control.getStatusText())
-
-        template = control.getControlTemplate() as ToggleTemplate
+        cvh.setEnabled(false)
 
         cvh.layout.setOnClickListener(View.OnClickListener() {
             ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
         })
+    }
+
+    override fun bind(cws: ControlWithState) {
+        this.control = cws.control!!
+
+        cvh.status.setText(control.getStatusText())
+        template = control.getControlTemplate() as ToggleTemplate
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 8d65ca3..45d7397 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -51,17 +51,13 @@
         private const val DEFAULT_FORMAT = "%.1f"
     }
 
-    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
-        this.control = cws.control!!
+    override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-
-        statusExtra = cvh.statusExtra
         status = cvh.status
-
-        status.setText(control.getStatusText())
-
         context = status.getContext()
 
+        cvh.setEnabled(false)
+
         val gestureListener = ToggleRangeGestureListener(cvh.layout)
         val gestureDetector = GestureDetector(context, gestureListener)
         cvh.layout.setOnTouchListener({ v: View, e: MotionEvent ->
@@ -77,6 +73,13 @@
 
             return@setOnTouchListener false
         })
+    }
+
+    override fun bind(cws: ControlWithState) {
+        this.control = cws.control!!
+
+        statusExtra = cvh.statusExtra
+        status.setText(control.getStatusText())
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
index 5a6e5b4..1f33b85 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
@@ -17,8 +17,14 @@
 package com.android.systemui.controls.ui
 
 class UnknownBehavior : Behavior {
-    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
-        cvh.status.setText("Loading...")
+    lateinit var cvh: ControlViewHolder
+
+    override fun initialize(cvh: ControlViewHolder) {
+        this.cvh = cvh
+    }
+
+    override fun bind(cws: ControlWithState) {
+        cvh.status.setText(cvh.context.getString(com.android.internal.R.string.loading))
         cvh.setEnabled(false)
         cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index bf501ce..7c0033c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -27,7 +27,6 @@
 import android.os.ServiceManager;
 import android.util.DisplayMetrics;
 import android.view.Choreographer;
-import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
 
@@ -46,8 +45,6 @@
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -162,16 +159,6 @@
 
     @Singleton
     @Provides
-    public AutoHideController provideAutoHideController(Context context,
-            @Main Handler mainHandler,
-            NotificationRemoteInputManager notificationRemoteInputManager,
-            IWindowManager iWindowManager) {
-        return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
-                iWindowManager);
-    }
-
-    @Singleton
-    @Provides
     public ActivityManagerWrapper provideActivityManagerWrapper() {
         return ActivityManagerWrapper.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 5de88e1..413a522 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -24,17 +24,19 @@
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.accessibility.WindowMagnification;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.pip.PipUI;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.shortcut.ShortcutKeyDispatcher;
 import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.InstantAppNotifier;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarModule;
 import com.android.systemui.statusbar.tv.TvStatusBar;
 import com.android.systemui.theme.ThemeOverlayController;
 import com.android.systemui.toast.ToastUI;
@@ -49,7 +51,8 @@
 /**
  * SystemUI objects that are injectable should go here.
  */
-@Module(includes = {RecentsModule.class, StatusBarModule.class})
+@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,
+        KeyguardModule.class})
 public abstract class SystemUIBinder {
     /** Inject into AuthController. */
     @Binds
@@ -142,7 +145,7 @@
     @ClassKey(StatusBar.class)
     public abstract SystemUI bindsStatusBar(StatusBar sysui);
 
-   /** Inject into SystemActions. */
+    /** Inject into SystemActions. */
     @Binds
     @IntoMap
     @ClassKey(SystemActions.class)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 91aeb22..030b504 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -35,10 +35,8 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowBlurController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
@@ -69,7 +67,6 @@
             AssistModule.class,
             ConcurrencyModule.class,
             LogModule.class,
-            NotificationsModule.class,
             PeopleHubModule.class,
         },
         subcomponents = {StatusBarComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index d298b99..9203e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -64,12 +64,12 @@
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
+import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -88,13 +88,13 @@
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.view.RotationPolicy;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.MultiListLayout;
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -110,6 +110,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -155,6 +156,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final ContentResolver mContentResolver;
     private final Resources mResources;
+    private final ConfigurationController mConfigurationController;
     private final UserManager mUserManager;
     private final TrustManager mTrustManager;
     private final IActivityManager mIActivityManager;
@@ -186,6 +188,8 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private GlobalActionsPanelPlugin mPanelPlugin;
     private ControlsUiController mControlsUiController;
+    private final IWindowManager mIWindowManager;
+    private final Executor mBackgroundExecutor;
 
     /**
      * @param context everything needs a context :(
@@ -204,7 +208,8 @@
             BlurUtils blurUtils, SysuiColorExtractor colorExtractor,
             IStatusBarService statusBarService,
             NotificationShadeWindowController notificationShadeWindowController,
-            ControlsUiController controlsUiController) {
+            ControlsUiController controlsUiController, IWindowManager iWindowManager,
+            @Background Executor backgroundExecutor) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -215,6 +220,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mContentResolver = contentResolver;
         mResources = resources;
+        mConfigurationController = configurationController;
         mUserManager = userManager;
         mTrustManager = trustManager;
         mIActivityManager = iActivityManager;
@@ -225,6 +231,8 @@
         mStatusBarService = statusBarService;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mControlsUiController = controlsUiController;
+        mIWindowManager = iWindowManager;
+        mBackgroundExecutor = backgroundExecutor;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -249,7 +257,7 @@
         mScreenshotHelper = new ScreenshotHelper(context);
         mScreenRecordHelper = new ScreenRecordHelper(context);
 
-        configurationController.addCallback(this);
+        mConfigurationController.addCallback(this);
 
         mActivityStarter = activityStarter;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@@ -335,45 +343,7 @@
         } else {
             mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
         }
-        mAirplaneModeOn = new ToggleAction(
-                R.drawable.ic_lock_airplane_mode,
-                R.drawable.ic_lock_airplane_mode_off,
-                R.string.global_actions_toggle_airplane_mode,
-                R.string.global_actions_airplane_mode_on_status,
-                R.string.global_actions_airplane_mode_off_status) {
-
-            void onToggle(boolean on) {
-                if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
-                    mIsWaitingForEcmExit = true;
-                    // Launch ECM exit dialog
-                    Intent ecmDialogIntent =
-                            new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
-                    ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    mContext.startActivity(ecmDialogIntent);
-                } else {
-                    changeAirplaneModeSystemSetting(on);
-                }
-            }
-
-            @Override
-            protected void changeStateFromPress(boolean buttonOn) {
-                if (!mHasTelephony) return;
-
-                // In ECM mode airplane state cannot be changed
-                if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
-                    mState = buttonOn ? State.TurningOn : State.TurningOff;
-                    mAirplaneState = mState;
-                }
-            }
-
-            public boolean showDuringKeyguard() {
-                return true;
-            }
-
-            public boolean showBeforeProvisioning() {
-                return false;
-            }
-        };
+        mAirplaneModeOn = new AirplaneModeAction();
         onAirplaneModeChanged();
 
         mItems = new ArrayList<Action>();
@@ -495,7 +465,7 @@
     }
 
     public void destroy() {
-        Dependency.get(ConfigurationController.class).removeCallback(this);
+        mConfigurationController.removeCallback(this);
     }
 
     private final class PowerAction extends SinglePressAction implements LongPressAction {
@@ -537,7 +507,7 @@
 
         @Override
         public boolean shouldBeSeparated() {
-            return !shouldShowControls();
+            return true;
         }
 
         @Override
@@ -836,14 +806,12 @@
 
             @Override
             public void onPress() {
-                new LockPatternUtils(mContext)
-                        .requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
-                                UserHandle.USER_ALL);
+                mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+                        UserHandle.USER_ALL);
                 try {
-                    WindowManagerGlobal.getWindowManagerService().lockNow(null);
+                    mIWindowManager.lockNow(null);
                     // Lock profiles (if any) on the background thread.
-                    final Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
-                    bgHandler.post(() -> lockProfiles());
+                    mBackgroundExecutor.execute(() -> lockProfiles());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error while trying to lock device.", e);
                 }
@@ -1333,6 +1301,48 @@
         }
     }
 
+    private class AirplaneModeAction extends ToggleAction {
+        AirplaneModeAction() {
+            super(
+                R.drawable.ic_lock_airplane_mode,
+                R.drawable.ic_lock_airplane_mode_off,
+                R.string.global_actions_toggle_airplane_mode,
+                R.string.global_actions_airplane_mode_on_status,
+                R.string.global_actions_airplane_mode_off_status);
+        }
+        void onToggle(boolean on) {
+            if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
+                mIsWaitingForEcmExit = true;
+                // Launch ECM exit dialog
+                Intent ecmDialogIntent =
+                        new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+                ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(ecmDialogIntent);
+            } else {
+                changeAirplaneModeSystemSetting(on);
+            }
+        }
+
+        @Override
+        protected void changeStateFromPress(boolean buttonOn) {
+            if (!mHasTelephony) return;
+
+            // In ECM mode airplane state cannot be changed
+            if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
+                mState = buttonOn ? State.TurningOn : State.TurningOff;
+                mAirplaneState = mState;
+            }
+        }
+
+        public boolean showDuringKeyguard() {
+            return true;
+        }
+
+        public boolean showBeforeProvisioning() {
+            return false;
+        }
+    }
+
     private class SilentModeToggleAction extends ToggleAction {
         public SilentModeToggleAction() {
             super(R.drawable.ic_audio_vol_mute,
@@ -1555,6 +1565,7 @@
 
         private ControlsUiController mControlsUiController;
         private ViewGroup mControlsView;
+        private ViewGroup mContainerView;
 
         ActionsDialog(Context context, MyAdapter adapter,
                 GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils,
@@ -1672,6 +1683,14 @@
                 mScrimAlpha = ScrimController.BUSY_SCRIM_ALPHA;
             }
             getWindow().setBackgroundDrawable(mBackgroundDrawable);
+
+            if (mControlsView != null) {
+                mContainerView = findViewById(com.android.systemui.R.id.global_actions_container);
+                mContainerView.setOnTouchListener((v, e) -> {
+                    dismiss();
+                    return true;
+                });
+            }
         }
 
         private void fixNavBarClipping() {
@@ -1894,14 +1913,6 @@
         return isPanelDebugModeEnabled(context);
     }
 
-
-    /**
-     * Determines whether the Global Actions menu should use a separated view for emergency actions.
-     */
-    private static boolean shouldUseSeparatedView() {
-        return true;
-    }
-
     private boolean shouldShowControls() {
         return !mKeyguardManager.isDeviceLocked()
                 && mControlsUiController.getAvailable();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
index 6749f1d..f102561 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,164 +23,62 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.HardwareBgDrawable;
+import com.android.systemui.R;
 
 /**
- * Single row implementation of the button layout created by the global actions dialog.
+ * Flat, single-row implementation of the button layout created by the global actions dialog.
  */
 public class GlobalActionsFlatLayout extends GlobalActionsLayout {
+    private static final int MAX_ITEMS = 4;
     public GlobalActionsFlatLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mBackgroundsSet = true;
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        // backgrounds set only once, the first time onMeasure is called after inflation
-        // if (getListView() != null && !mBackgroundsSet) {
-        //     setBackgrounds();
-        //     mBackgroundsSet = true;
-        // }
-    }
-
     @VisibleForTesting
-    protected void setupListView() {
-        ListGridLayout listView = getListView();
-        listView.setExpectedCount(Math.min(2, mAdapter.countListItems()));
-        listView.setReverseSublists(shouldReverseSublists());
-        listView.setReverseItems(shouldReverseListItems());
-        listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns());
+    protected boolean shouldReverseListItems() {
+        int rotation = getCurrentRotation();
+        if (rotation == ROTATION_NONE) {
+            return false;
+        }
+        if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            return rotation == ROTATION_LANDSCAPE;
+        }
+        return rotation == ROTATION_SEASCAPE;
     }
 
     @Override
-    public void onUpdateList() {
-        setupListView();
-        super.onUpdateList();
-        updateSeparatedItemSize();
-    }
-
-    /**
-     * If the separated view contains only one item, expand the bounds of that item to take up the
-     * entire view, so that the whole thing is touch-able.
-     */
-    @VisibleForTesting
-    protected void updateSeparatedItemSize() {
-        ViewGroup separated = getSeparatedView();
-        if (separated.getChildCount() == 0) {
-            return;
-        }
-        View firstChild = separated.getChildAt(0);
-        ViewGroup.LayoutParams childParams = firstChild.getLayoutParams();
-
-        if (separated.getChildCount() == 1) {
-            childParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            childParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
-        } else {
-            childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
-            childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
-        }
-    }
-
-    @Override
-    protected ListGridLayout getListView() {
-        return (ListGridLayout) super.getListView();
-    }
-
-    @Override
-    protected void removeAllListViews() {
-        ListGridLayout list = getListView();
-        if (list != null) {
-            list.removeAllItems();
-        }
+    protected HardwareBgDrawable getBackgroundDrawable(int backgroundColor) {
+        return null;
     }
 
     @Override
     protected void addToListView(View v, boolean reverse) {
-        ListGridLayout list = getListView();
-        if (list != null) {
-            list.addItem(v);
+        // only add items to the list view if we haven't hit our max yet
+        if (getListView().getChildCount() < MAX_ITEMS) {
+            super.addToListView(v, reverse);
         }
     }
 
-    @Override
-    public void removeAllItems() {
-        ViewGroup separatedList = getSeparatedView();
-        ListGridLayout list = getListView();
-        if (separatedList != null) {
-            separatedList.removeAllViews();
-        }
-        if (list != null) {
-            list.removeAllItems();
-        }
-    }
-
-    /**
-     * Determines whether the ListGridLayout should fill sublists in the reverse order.
-     * Used to account for sublist ordering changing between landscape and seascape views.
-     */
     @VisibleForTesting
-    protected boolean shouldReverseSublists() {
-        if (getCurrentRotation() == ROTATION_SEASCAPE) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Determines whether the ListGridLayout should fill rows first instead of columns.
-     * Used to account for vertical/horizontal changes due to landscape or seascape rotations.
-     */
-    @VisibleForTesting
-    protected boolean shouldSwapRowsAndColumns() {
-        if (getCurrentRotation() == ROTATION_NONE) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    protected boolean shouldReverseListItems() {
-        int rotation = getCurrentRotation();
-        boolean reverse = false; // should we add items to parents in the reverse order?
-        if (rotation == ROTATION_NONE
-                || rotation == ROTATION_SEASCAPE) {
-            reverse = !reverse; // if we're in portrait or seascape, reverse items
-        }
-        if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            reverse = !reverse; // if we're in an RTL language, reverse items (again)
-        }
-        return reverse;
+    protected float getGridItemSize() {
+        return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height);
     }
 
     @VisibleForTesting
     protected float getAnimationDistance() {
-        int rows = getListView().getRowCount();
-        float gridItemSize = getContext().getResources().getDimension(
-                com.android.systemui.R.dimen.global_actions_grid_item_height);
-        return rows * gridItemSize / 2;
+        return getGridItemSize() / 2;
     }
 
     @Override
     public float getAnimationOffsetX() {
-        switch (getCurrentRotation()) {
-            case ROTATION_LANDSCAPE:
-                return getAnimationDistance();
-            case ROTATION_SEASCAPE:
-                return -getAnimationDistance();
-            default: // Portrait
-                return 0;
-        }
+        return 0;
     }
 
     @Override
     public float getAnimationOffsetY() {
-        if (getCurrentRotation() == ROTATION_NONE) {
-            return getAnimationDistance();
-        }
-        return 0;
+        return -getAnimationDistance();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java
index f755a93..183ee45 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java
@@ -42,16 +42,30 @@
     }
 
     private void setBackgrounds() {
-        int gridBackgroundColor = getResources().getColor(
+        ViewGroup listView = getListView();
+        int listBgColor = getResources().getColor(
                 R.color.global_actions_grid_background, null);
-        int separatedBackgroundColor = getResources().getColor(
-                R.color.global_actions_separated_background, null);
-        HardwareBgDrawable listBackground  = new HardwareBgDrawable(true, true, getContext());
-        HardwareBgDrawable separatedBackground = new HardwareBgDrawable(true, true, getContext());
-        listBackground.setTint(gridBackgroundColor);
-        separatedBackground.setTint(separatedBackgroundColor);
-        getListView().setBackground(listBackground);
-        getSeparatedView().setBackground(separatedBackground);
+        HardwareBgDrawable listBackground = getBackgroundDrawable(listBgColor);
+        if (listBackground != null) {
+            listView.setBackground(listBackground);
+        }
+
+        ViewGroup separatedView = getSeparatedView();
+
+        if (separatedView != null) {
+            int separatedBgColor = getResources().getColor(
+                    R.color.global_actions_separated_background, null);
+            HardwareBgDrawable separatedBackground = getBackgroundDrawable(separatedBgColor);
+            if (separatedBackground != null) {
+                getSeparatedView().setBackground(separatedBackground);
+            }
+        }
+    }
+
+    protected HardwareBgDrawable getBackgroundDrawable(int backgroundColor) {
+        HardwareBgDrawable background = new HardwareBgDrawable(true, true, getContext());
+        background.setTint(backgroundColor);
+        return background;
     }
 
     @Override
@@ -74,10 +88,16 @@
     }
 
     protected void addToSeparatedView(View v, boolean reverse) {
-        if (reverse) {
-            getSeparatedView().addView(v, 0);
+        ViewGroup separated = getSeparatedView();
+        if (separated != null) {
+            if (reverse) {
+                separated.addView(v, 0);
+            } else {
+                separated.addView(v);
+            }
         } else {
-            getSeparatedView().addView(v);
+            // if no separated view exists, just use the list view
+            addToListView(v, reverse);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 83a6d75..b1db7df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -86,6 +86,7 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -100,9 +101,6 @@
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 
 /**
@@ -146,7 +144,6 @@
  * directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI
  * thread of the keyguard.
  */
-@Singleton
 public class KeyguardViewMediator extends SystemUI {
     private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
     private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
@@ -688,7 +685,9 @@
         }
     };
 
-    @Inject
+    /**
+     * Injected constructor. See {@link KeyguardModule}.
+     */
     public KeyguardViewMediator(
             Context context,
             FalsingManager falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
new file mode 100644
index 0000000..2c023ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.dagger;
+
+import android.content.Context;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module providing {@link StatusBar}.
+ */
+@Module
+public class KeyguardModule {
+    /**
+     * Provides our instance of KeyguardViewMediator which is considered optional.
+     */
+    @Provides
+    @Singleton
+    public static KeyguardViewMediator newKeyguardViewMediator(
+            Context context,
+            FalsingManager falsingManager,
+            LockPatternUtils lockPatternUtils,
+            BroadcastDispatcher broadcastDispatcher,
+            NotificationShadeWindowController notificationShadeWindowController,
+            Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManagerLazy,
+            DismissCallbackRegistry dismissCallbackRegistry,
+            @UiBackground Executor uiBgExecutor) {
+        return new KeyguardViewMediator(
+                context,
+                falsingManager,
+                lockPatternUtils,
+                broadcastDispatcher,
+                notificationShadeWindowController,
+                statusBarKeyguardViewManagerLazy,
+                dismissCallbackRegistry,
+                uiBgExecutor);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 029ab43..a827f59 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -19,6 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.NonNull;
+import android.util.Log;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -37,6 +38,9 @@
 @Singleton
 public class SysUiState implements Dumpable {
 
+    private static final String TAG = SysUiState.class.getSimpleName();
+    public static final boolean DEBUG = true;
+
     private @QuickStepContract.SystemUiStateFlags int mFlags;
     private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
     private int mFlagsToSet = 0;
@@ -76,6 +80,7 @@
     private void updateFlags(int displayId) {
         if (displayId != DEFAULT_DISPLAY) {
             // Ignore non-default displays for now
+            Log.w(TAG, "Ignoring flag update for display: " + displayId, new Throwable());
             return;
         }
 
@@ -87,6 +92,9 @@
 
     /** Notify all those who are registered that the state has changed. */
     private void notifyAndSetSystemUiStateChanged(int newFlags, int oldFlags) {
+        if (DEBUG) {
+            Log.d(TAG, "SysUiState changed: old=" + oldFlags + " new=" + newFlags);
+        }
         if (newFlags != oldFlags) {
             mCallbacks.forEach(callback -> callback.onSystemUiStateChanged(newFlags));
             mFlags = newFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index d4af154..8cd70cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -61,9 +61,9 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.qs.QSDetail.Callback;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.statusbar.phone.StatusIconContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.DateView;
@@ -146,6 +146,7 @@
         }
     };
     private boolean mHasTopCutout = false;
+    private int mRoundedCornerPadding = 0;
 
     @Inject
     public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -325,6 +326,9 @@
         Resources resources = mContext.getResources();
         updateMinimumHeight();
 
+        mRoundedCornerPadding = resources.getDimensionPixelSize(
+                R.dimen.rounded_corner_content_padding);
+
         // Update height for a few views, especially due to landscape mode restricting space.
         mHeaderTextContainerView.getLayoutParams().height =
                 resources.getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
@@ -432,16 +436,23 @@
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         DisplayCutout cutout = insets.getDisplayCutout();
-        Pair<Integer, Integer> padding = PhoneStatusBarView.cornerCutoutMargins(
-                cutout, getDisplay());
-        if (padding == null) {
-            mSystemIconsView.setPaddingRelative(
-                    getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start), 0,
-                    getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 0);
-        } else {
-            mSystemIconsView.setPadding(padding.first, 0, padding.second, 0);
 
-        }
+        // Handle padding of QuickStatusBarHeader
+        Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins(
+                cutout, getDisplay());
+        Pair<Integer, Integer> padding =
+                StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
+                        cutout, cornerCutoutPadding, mRoundedCornerPadding);
+        setPadding(padding.first, 0, padding.second, getPaddingBottom());
+
+        // Handle padding of SystemIconsView
+        final int waterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
+        mSystemIconsView.setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start),
+                waterfallTopInset,
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
+                0);
+
         return super.onApplyWindowInsets(insets);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index ab8de26..5c1d332 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -105,8 +105,8 @@
     fun logTileUpdated(tileSpec: String, state: QSTile.State) {
         log(VERBOSE, {
             str1 = tileSpec
-            str2 = state.label.toString()
-            str3 = state.icon.toString()
+            str2 = state.label?.toString()
+            str3 = state.icon?.toString()
             int1 = state.state
             if (state is QSTile.SignalState) {
                 bool1 = true
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 88a30a1..84891ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -31,20 +31,26 @@
 /**
  * Quick settings tile for screen recording
  */
-public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
+        implements RecordingController.RecordingStateChangeCallback {
     private static final String TAG = "ScreenRecordTile";
     private RecordingController mController;
     private long mMillisUntilFinished = 0;
+    private Callback mCallback = new Callback();
 
     @Inject
     public ScreenRecordTile(QSHost host, RecordingController controller) {
         super(host);
         mController = controller;
+        mController.observe(this, mCallback);
     }
 
     @Override
     public BooleanState newTileState() {
-        return new BooleanState();
+        BooleanState state = new BooleanState();
+        state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+        state.handlesLongClick = false;
+        return state;
     }
 
     @Override
@@ -59,24 +65,13 @@
         refreshState();
     }
 
-    /**
-     * Refresh tile state
-     * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
-     */
-    public void refreshState(long millisUntilFinished) {
-        mMillisUntilFinished = millisUntilFinished;
-        refreshState();
-    }
-
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         boolean isStarting = mController.isStarting();
         boolean isRecording = mController.isRecording();
 
-        state.label = mContext.getString(R.string.quick_settings_screen_record_label);
         state.value = isRecording || isStarting;
         state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
-        state.handlesLongClick = false;
 
         if (isRecording) {
             state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
@@ -125,4 +120,22 @@
         Log.d(TAG, "Stopping recording from tile");
         mController.stopRecording();
     }
+
+    private final class Callback implements RecordingController.RecordingStateChangeCallback {
+        @Override
+        public void onCountdown(long millisUntilFinished) {
+            mMillisUntilFinished = millisUntilFinished;
+            refreshState();
+        }
+
+        @Override
+        public void onRecordingStart() {
+            refreshState();
+        }
+
+        @Override
+        public void onRecordingEnd() {
+            refreshState();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 573ea4d..34cad51 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -352,6 +352,8 @@
             try {
                 Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
+                        AccessibilityManager.ACCESSIBILITY_BUTTON);
                 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -378,6 +380,14 @@
                     taskId, mHandler, null);
         }
 
+        @Override
+        public void setSplitScreenMinimized(boolean minimized) {
+            Divider divider = mDividerOptional.get();
+            if (divider != null) {
+                divider.setMinimized(minimized);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -408,6 +418,9 @@
     private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            if (SysUiState.DEBUG) {
+                Log.d(TAG_OPS, "Overview proxy service connected");
+            }
             mConnectionBackoffAttempts = 0;
             mHandler.removeCallbacks(mDeferredConnectionCallback);
             try {
@@ -560,6 +573,10 @@
                 mNavBarController.getDefaultNavigationBarFragment();
         final NavigationBarView navBarView =
                 mNavBarController.getNavigationBarView(mContext.getDisplayId());
+        if (SysUiState.DEBUG) {
+            Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+                    + " navBarView=" + navBarView);
+        }
 
         if (navBarFragment != null) {
             navBarFragment.updateSystemUiStateFlags(-1);
@@ -574,6 +591,10 @@
     }
 
     private void notifySystemUiStateFlags(int flags) {
+        if (SysUiState.DEBUG) {
+            Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
+                    + mOverviewProxy + " flags=" + flags);
+        }
         try {
             if (mOverviewProxy != null) {
                 mOverviewProxy.onSystemUiStateChanged(flags);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 188501e..6ad9c40 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -24,6 +24,9 @@
 import android.util.Log;
 
 import com.android.systemui.qs.tiles.ScreenRecordTile;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.ArrayList;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -32,7 +35,8 @@
  * Helper class to initiate a screen recording
  */
 @Singleton
-public class RecordingController {
+public class RecordingController
+        implements CallbackController<RecordingController.RecordingStateChangeCallback> {
     private static final String TAG = "RecordingController";
     private static final String SYSUI_PACKAGE = "com.android.systemui";
     private static final String SYSUI_SCREENRECORD_LAUNCHER =
@@ -41,10 +45,11 @@
     private final Context mContext;
     private boolean mIsStarting;
     private boolean mIsRecording;
-    private ScreenRecordTile mTileToUpdate;
     private PendingIntent mStopIntent;
     private CountDownTimer mCountDownTimer = null;
 
+    private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>();
+
     /**
      * Create a new RecordingController
      * @param context Context for the controller
@@ -63,10 +68,7 @@
         final Intent intent = new Intent();
         intent.setComponent(launcherComponent);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
         mContext.startActivity(intent);
-
-        mTileToUpdate = tileToUpdate;
     }
 
     /**
@@ -82,16 +84,21 @@
         mCountDownTimer = new CountDownTimer(ms, 1000) {
             @Override
             public void onTick(long millisUntilFinished) {
-                refreshTile(millisUntilFinished);
+                for (RecordingStateChangeCallback cb : mListeners) {
+                    cb.onCountdown(millisUntilFinished);
+                }
             }
 
             @Override
             public void onFinish() {
                 mIsStarting = false;
                 mIsRecording = true;
-                refreshTile();
+                for (RecordingStateChangeCallback cb : mListeners) {
+                    cb.onRecordingEnd();
+                }
                 try {
                     startIntent.send();
+                    Log.d(TAG, "sent start intent");
                 } catch (PendingIntent.CanceledException e) {
                     Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
                 }
@@ -101,18 +108,6 @@
         mCountDownTimer.start();
     }
 
-    private void refreshTile() {
-        refreshTile(0);
-    }
-
-    private void refreshTile(long millisUntilFinished) {
-        if (mTileToUpdate != null) {
-            mTileToUpdate.refreshState(millisUntilFinished);
-        } else {
-            Log.e(TAG, "No tile to refresh");
-        }
-    }
-
     /**
      * Cancel a countdown in progress. This will not stop the recording if it already started.
      */
@@ -123,7 +118,10 @@
             Log.e(TAG, "Timer was null");
         }
         mIsStarting = false;
-        refreshTile();
+
+        for (RecordingStateChangeCallback cb : mListeners) {
+            cb.onRecordingEnd();
+        }
     }
 
     /**
@@ -152,7 +150,10 @@
         } catch (PendingIntent.CanceledException e) {
             Log.e(TAG, "Error stopping: " + e.getMessage());
         }
-        refreshTile();
+
+        for (RecordingStateChangeCallback cb : mListeners) {
+            cb.onRecordingEnd();
+        }
     }
 
     /**
@@ -161,6 +162,44 @@
      */
     public void updateState(boolean isRecording) {
         mIsRecording = isRecording;
-        refreshTile();
+        for (RecordingStateChangeCallback cb : mListeners) {
+            if (isRecording) {
+                cb.onRecordingStart();
+            } else {
+                cb.onRecordingEnd();
+            }
+        }
+    }
+
+    @Override
+    public void addCallback(RecordingStateChangeCallback listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(RecordingStateChangeCallback listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * A callback for changes in the screen recording state
+     */
+    public interface RecordingStateChangeCallback {
+        /**
+         * Called when a countdown to recording has updated
+         *
+         * @param millisUntilFinished Time in ms remaining in the countdown
+         */
+        default void onCountdown(long millisUntilFinished) {}
+
+        /**
+         * Called when a screen recording has started
+         */
+        default void onRecordingStart() {}
+
+        /**
+         * Called when a screen recording has ended
+         */
+        default void onRecordingEnd() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 197fe21..9e1e347 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -27,7 +27,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
@@ -58,6 +57,7 @@
 import android.provider.DeviceConfig;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.view.Display;
 import android.view.LayoutInflater;
@@ -68,6 +68,7 @@
 import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
@@ -136,13 +137,11 @@
 
     private static final String TAG = "GlobalScreenshot";
 
-    private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
-    private static final int SCREENSHOT_DROP_IN_DURATION = 430;
-    private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
-    private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
-    private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
-    private static final float BACKGROUND_ALPHA = 0.5f;
-    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f;
+    private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
+    private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
+    private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
+    private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
+    private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
     private static final float ROUNDED_CORNER_RADIUS = .05f;
     private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
     private static final int MESSAGE_CORNER_TIMEOUT = 2;
@@ -166,18 +165,21 @@
     private final FrameLayout mDismissButton;
 
     private Bitmap mScreenBitmap;
-    private AnimatorSet mScreenshotAnimation;
+    private Animator mScreenshotAnimation;
 
     private float mScreenshotOffsetXPx;
     private float mScreenshotOffsetYPx;
     private float mScreenshotHeightPx;
-    private float mCornerScale;
     private float mDismissButtonSize;
+    private float mCornerSizeX;
 
     private AsyncTask<Void, Void, Void> mSaveInBgTask;
 
     private MediaActionSound mCameraSound;
 
+    // standard material ease
+    private final Interpolator mFastOutSlowIn;
+
     private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -227,6 +229,8 @@
         mScreenshotLayout.setFocusable(true);
         mScreenshotSelectorView.setFocusable(true);
         mScreenshotSelectorView.setFocusableInTouchMode(true);
+        mScreenshotView.setPivotX(0);
+        mScreenshotView.setPivotY(0);
 
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -250,10 +254,12 @@
         mScreenshotOffsetYPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_y);
         mScreenshotHeightPx =
                 resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y);
-        mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale)
-                / (float) mDisplayMetrics.widthPixels;
         mDismissButtonSize = resources.getDimensionPixelSize(
                 R.dimen.screenshot_dismiss_button_tappable_size);
+        mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
+
+        mFastOutSlowIn =
+                AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
 
         // Setup the Camera shutter sound
         mCameraSound = new MediaActionSound();
@@ -305,7 +311,9 @@
         int width = crop.width();
         int height = crop.height();
 
-        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, null);
+        Rect screenRect = new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
+
+        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect);
     }
 
     private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) {
@@ -325,7 +333,7 @@
         mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
 
         // Start the post-screenshot animation
-        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+        startAnimation(finisher, screenRect.width(), screenRect.height(),
                 screenRect);
     }
 
@@ -406,7 +414,6 @@
         mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
 
         // Clear any references to the bitmap
-        mScreenBitmap = null;
         mScreenshotView.setImageBitmap(null);
         mActionsContainer.setVisibility(View.GONE);
         mBackgroundView.setVisibility(View.GONE);
@@ -430,21 +437,8 @@
 
         // Add the view for the animation
         mScreenshotView.setImageBitmap(mScreenBitmap);
-        mScreenshotLayout.requestFocus();
 
-        // Setup the animation with the screenshot just taken
-        if (mScreenshotAnimation != null) {
-            if (mScreenshotAnimation.isStarted()) {
-                mScreenshotAnimation.end();
-            }
-            mScreenshotAnimation.removeAllListeners();
-        }
-
-        ValueAnimator screenshotDropInAnim = screenRect != null ? createRectAnimation(screenRect)
-                : createScreenshotDropInAnimation();
-        ValueAnimator screenshotToCornerAnimation = createScreenshotToCornerAnimation(w, h);
-        mScreenshotAnimation = new AnimatorSet();
-        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotToCornerAnimation);
+        mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect);
 
         saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
             @Override
@@ -487,147 +481,64 @@
         });
     }
 
-    private ValueAnimator createRectAnimation(Rect rect) {
-        mScreenshotView.setAdjustViewBounds(true);
-        mScreenshotView.setMaxHeight(rect.height());
-        mScreenshotView.setMaxWidth(rect.width());
+    private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
+        float cornerScale = mCornerSizeX / (float) width;
 
-        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
-                / SCREENSHOT_DROP_IN_DURATION);
-        final float flashDurationPct = 2f * flashPeakDurationPct;
-        final Interpolator scaleInterpolator = x -> {
-            // We start scaling when the flash is at it's peak
-            if (x < flashPeakDurationPct) {
-                return 0;
+        AnimatorSet dropInAnimation = new AnimatorSet();
+        ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
+        flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
+        flashInAnimator.setInterpolator(mFastOutSlowIn);
+        flashInAnimator.addUpdateListener(animation ->
+                mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
+
+        ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
+        flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
+        flashOutAnimator.setInterpolator(mFastOutSlowIn);
+        flashOutAnimator.addUpdateListener(animation ->
+                mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
+
+        final PointF startPos = new PointF((float) bounds.left, (float) bounds.top);
+        final PointF finalPos = new PointF(mScreenshotOffsetXPx,
+                mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale);
+
+        ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
+        toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
+        float xPositionPct =
+                SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+        float scalePct =
+                SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+        toCorner.addUpdateListener(animation -> {
+            float t = animation.getAnimatedFraction();
+            if (t < scalePct) {
+                float scale = MathUtils.lerp(
+                        1, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
+                mScreenshotView.setScaleX(scale);
+                mScreenshotView.setScaleY(scale);
             }
-            return (x - flashDurationPct) / (1f - flashDurationPct);
-        };
 
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
+            if (t < xPositionPct) {
+                mScreenshotView.setX(MathUtils.lerp(
+                        startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)));
+            }
+            mScreenshotView.setY(MathUtils.lerp(
+                    startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)));
+        });
+
+        toCorner.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mBackgroundView.setAlpha(0f);
-                mBackgroundView.setVisibility(View.VISIBLE);
-                mScreenshotView.setAlpha(0f);
-                mScreenshotView.setElevation(0f);
-                mScreenshotView.setTranslationX(0f);
-                mScreenshotView.setTranslationY(0f);
-                mScreenshotView.setScaleX(1f);
-                mScreenshotView.setScaleY(1f);
+                super.onAnimationStart(animation);
                 mScreenshotView.setVisibility(View.VISIBLE);
             }
         });
-        anim.addUpdateListener(animation -> {
-            float t = (Float) animation.getAnimatedValue();
-            mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
-            mScreenshotView.setAlpha(t);
-        });
-        return anim;
-    }
 
-    private ValueAnimator createScreenshotDropInAnimation() {
-        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
-                / SCREENSHOT_DROP_IN_DURATION);
-        final float flashDurationPct = 2f * flashPeakDurationPct;
-        final Interpolator flashAlphaInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float x) {
-                // Flash the flash view in and out quickly
-                if (x <= flashDurationPct) {
-                    return (float) Math.sin(Math.PI * (x / flashDurationPct));
-                }
-                return 0;
-            }
-        };
-        final Interpolator scaleInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float x) {
-                // We start scaling when the flash is at it's peak
-                if (x < flashPeakDurationPct) {
-                    return 0;
-                }
-                return (x - flashDurationPct) / (1f - flashDurationPct);
-            }
-        };
+        mScreenshotFlash.setAlpha(0f);
+        mScreenshotFlash.setVisibility(View.VISIBLE);
 
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mBackgroundView.setAlpha(0f);
-                mBackgroundView.setVisibility(View.VISIBLE);
-                mScreenshotView.setAlpha(0f);
-                mScreenshotView.setTranslationX(0f);
-                mScreenshotView.setTranslationY(0f);
-                mScreenshotView.setScaleX(1);
-                mScreenshotView.setScaleY(1);
-                mScreenshotView.setVisibility(View.VISIBLE);
-                mScreenshotFlash.setAlpha(0f);
-                mScreenshotFlash.setVisibility(View.VISIBLE);
-            }
+        dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
+        dropInAnimation.play(flashOutAnimator).with(toCorner);
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mScreenshotFlash.setVisibility(View.GONE);
-            }
-        });
-        anim.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float t = (Float) animation.getAnimatedValue();
-                float scaleT = 1 - (scaleInterpolator.getInterpolation(t)
-                        * (1 - SCREENSHOT_DROP_IN_MIN_SCALE));
-                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
-                mScreenshotView.setAlpha(t);
-                mScreenshotView.setScaleX(scaleT);
-                mScreenshotView.setScaleY(scaleT);
-                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
-            }
-        });
-        return anim;
-    }
-
-    private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
-
-        final float scaleDurationPct =
-                (float) SCREENSHOT_DROP_OUT_SCALE_DURATION / SCREENSHOT_DROP_OUT_DURATION;
-        final Interpolator scaleInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float x) {
-                if (x < scaleDurationPct) {
-                    // Decelerate, and scale the input accordingly
-                    return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
-                }
-                return 1f;
-            }
-        };
-
-        // Determine the bounds of how to scale
-        float halfScreenWidth = w / 2f;
-        float halfScreenHeight = h / 2f;
-        final PointF finalPos = new PointF(
-                -halfScreenWidth + mCornerScale * halfScreenWidth + mScreenshotOffsetXPx,
-                halfScreenHeight - mCornerScale * halfScreenHeight - mScreenshotOffsetYPx);
-
-        // Animate the screenshot to the bottom left corner
-        anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
-        anim.addUpdateListener(animation -> {
-            float t = (Float) animation.getAnimatedValue();
-            float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE)
-                    - scaleInterpolator.getInterpolation(t)
-                    * (SCREENSHOT_DROP_IN_MIN_SCALE - mCornerScale);
-            mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-            mScreenshotView.setScaleX(scaleT);
-            mScreenshotView.setScaleY(scaleT);
-            mScreenshotView.setTranslationX(t * finalPos.x);
-            mScreenshotView.setTranslationY(t * finalPos.y);
-        });
-        anim.addListener(new AnimatorListenerAdapter() {
+        dropInAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
@@ -638,7 +549,8 @@
                 mDismissButton.setVisibility(View.VISIBLE);
             }
         });
-        return anim;
+
+        return dropInAnimation;
     }
 
     private ValueAnimator createScreenshotActionsShadeAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 92236ae..7de70f5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -219,11 +219,6 @@
             mParams.mActionsReadyListener.onActionsReady(null, null, null);
         }
 
-        // Recycle the bitmap data
-        if (image != null) {
-            image.recycle();
-        }
-
         return null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 5ae0954..4f20492 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -22,10 +22,8 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.RemoteException;
-import android.util.Log;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
-import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.policy.DividerSnapAlgorithm;
@@ -94,29 +92,24 @@
     }
 
     private void handleDockKey(long shortcutCode) {
-        try {
-            int dockSide = mWindowManagerService.getDockedStackSide();
-            if (dockSide == WindowManager.DOCKED_INVALID) {
-                // Split the screen
-                mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
-                        ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
-            } else {
-                // If there is already a docked window, we respond by resizing the docking pane.
-                DividerView dividerView = mDivider.getView();
-                DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
-                int dividerPosition = dividerView.getCurrentPosition();
-                DividerSnapAlgorithm.SnapTarget currentTarget =
-                        snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
-                DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
-                        ? snapAlgorithm.getPreviousTarget(currentTarget)
-                        : snapAlgorithm.getNextTarget(currentTarget);
-                dividerView.startDragging(true /* animate */, false /* touching */);
-                dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
-                        true /* logMetrics */);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "handleDockKey() failed.");
+        if (mDivider == null || !mDivider.inSplitMode()) {
+            // Split the screen
+            mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
+                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
+        } else {
+            // If there is already a docked window, we respond by resizing the docking pane.
+            DividerView dividerView = mDivider.getView();
+            DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
+            int dividerPosition = dividerView.getCurrentPosition();
+            DividerSnapAlgorithm.SnapTarget currentTarget =
+                    snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
+            DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
+                    ? snapAlgorithm.getPreviousTarget(currentTarget)
+                    : snapAlgorithm.getNextTarget(currentTarget);
+            dividerView.startDragging(true /* animate */, false /* touching */);
+            dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
+                    true /* logMetrics */);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 90cc0e57..ba9eb4a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,69 +17,281 @@
 package com.android.systemui.stackdivider;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.util.Log;
-import android.view.IDockedStackListener;
+import android.util.Slog;
+import android.view.IWindowContainer;
 import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.View;
-import android.view.WindowManagerGlobal;
+import android.view.WindowContainerTransaction;
 
+import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.wm.DisplayChangeController;
+import com.android.systemui.wm.DisplayController;
+import com.android.systemui.wm.DisplayImeController;
+import com.android.systemui.wm.DisplayLayout;
+import com.android.systemui.wm.SystemWindows;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.Optional;
+import java.util.function.Consumer;
+
+import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Controls the docked stack divider.
  */
-public class Divider extends SystemUI implements DividerView.DividerCallbacks {
+@Singleton
+public class Divider extends SystemUI implements DividerView.DividerCallbacks,
+        DisplayController.OnDisplaysChangedListener {
     private static final String TAG = "Divider";
+
+    static final boolean DEBUG = true;
+
+    static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+
     private final Optional<Lazy<Recents>> mRecentsOptionalLazy;
 
     private DividerWindowManager mWindowManager;
     private DividerView mView;
     private final DividerState mDividerState = new DividerState();
-    private DockDividerVisibilityListener mDockDividerVisibilityListener;
     private boolean mVisible = false;
     private boolean mMinimized = false;
     private boolean mAdjustedForIme = false;
     private boolean mHomeStackResizable = false;
     private ForcedResizableInfoActivityController mForcedResizableController;
+    private SystemWindows mSystemWindows;
+    final SurfaceSession mSurfaceSession = new SurfaceSession();
+    private DisplayController mDisplayController;
+    private DisplayImeController mImeController;
 
-    public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
+    // Keeps track of real-time split geometry including snap positions and ime adjustments
+    private SplitDisplayLayout mSplitLayout;
+
+    // Transient: this contains the layout calculated for a new rotation requested by WM. This is
+    // kept around so that we can wait for a matching configuration change and then use the exact
+    // layout that we sent back to WM.
+    private SplitDisplayLayout mRotateSplitLayout;
+
+    private Handler mHandler;
+    private KeyguardStateController mKeyguardStateController;
+
+    private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners =
+            new ArrayList<>();
+
+    private SplitScreenTaskOrganizer mSplits = new SplitScreenTaskOrganizer(this);
+
+    private DisplayChangeController.OnDisplayChangingListener mRotationController =
+            (display, fromRotation, toRotation, t) -> {
+                DisplayLayout displayLayout =
+                        new DisplayLayout(mDisplayController.getDisplayLayout(display));
+                SplitDisplayLayout sdl = new SplitDisplayLayout(mContext, displayLayout, mSplits);
+                sdl.rotateTo(toRotation);
+                mRotateSplitLayout = sdl;
+                int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
+                        : mView.getCurrentPosition();
+                DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
+                final DividerSnapAlgorithm.SnapTarget target =
+                        snap.calculateNonDismissingSnapTarget(position);
+                sdl.resizeSplits(target.position, t);
+
+                if (inSplitMode()) {
+                    WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
+                }
+            };
+
+    private IWindowContainer mLastImeTarget = null;
+    private boolean mShouldAdjustForIme = false;
+
+    private DisplayImeController.ImePositionProcessor mImePositionProcessor =
+            new DisplayImeController.ImePositionProcessor() {
+                private int mStartTop = 0;
+                private int mFinalTop = 0;
+                @Override
+                public void onImeStartPositioning(int displayId, int imeTop, int finalImeTop,
+                        boolean showing, SurfaceControl.Transaction t) {
+                    mStartTop = imeTop;
+                    mFinalTop = finalImeTop;
+                    if (showing) {
+                        try {
+                            mLastImeTarget = ActivityTaskManager.getTaskOrganizerController()
+                                    .getImeTarget(displayId);
+                            mShouldAdjustForIme = !mSplitLayout.mDisplayLayout.isLandscape()
+                                    && (mLastImeTarget.asBinder()
+                                    == mSplits.mSecondary.token.asBinder());
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to get IME target", e);
+                        }
+                    }
+                    if (!mShouldAdjustForIme) {
+                        setAdjustedForIme(false);
+                        return;
+                    }
+                    mView.setAdjustedForIme(showing, showing
+                            ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
+                            : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
+                    // Reposition the server's secondary split position so that it evaluates
+                    // insets properly.
+                    WindowContainerTransaction wct = new WindowContainerTransaction();
+                    if (showing) {
+                        mSplitLayout.updateAdjustedBounds(finalImeTop, imeTop, finalImeTop);
+                        wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary);
+                    } else {
+                        wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
+                    }
+                    try {
+                        ActivityTaskManager.getTaskOrganizerController()
+                                .applyContainerTransaction(wct, null /* organizer */);
+                    } catch (RemoteException e) {
+                    }
+                    setAdjustedForIme(showing);
+                }
+
+                @Override
+                public void onImePositionChanged(int displayId, int imeTop,
+                        SurfaceControl.Transaction t) {
+                    if (!mShouldAdjustForIme) {
+                        return;
+                    }
+                    mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop);
+                    mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
+                            mSplitLayout.mAdjustedSecondary);
+                    final boolean showing = mFinalTop < mStartTop;
+                    final float progress = ((float) (imeTop - mStartTop)) / (mFinalTop - mStartTop);
+                    final float fraction = showing ? progress : 1.f - progress;
+                    mView.setResizeDimLayer(t, true /* primary */, fraction * 0.3f);
+                }
+
+                @Override
+                public void onImeEndPositioning(int displayId, int imeTop,
+                        boolean showing, SurfaceControl.Transaction t) {
+                    if (!mShouldAdjustForIme) {
+                        return;
+                    }
+                    mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop);
+                    mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
+                            mSplitLayout.mAdjustedSecondary);
+                    mView.setResizeDimLayer(t, true /* primary */, showing ? 0.3f : 0.f);
+                }
+            };
+
+    public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy,
+            DisplayController displayController, SystemWindows systemWindows,
+            DisplayImeController imeController, Handler handler,
+            KeyguardStateController keyguardStateController) {
         super(context);
+        mDisplayController = displayController;
+        mSystemWindows = systemWindows;
+        mImeController = imeController;
+        mHandler = handler;
+        mKeyguardStateController = keyguardStateController;
         mRecentsOptionalLazy = recentsOptionalLazy;
+        mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
     }
 
     @Override
     public void start() {
-        mWindowManager = new DividerWindowManager(mContext);
-        update(mContext.getResources().getConfiguration());
-        mDockDividerVisibilityListener = new DockDividerVisibilityListener();
-        try {
-            WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(
-                    mDockDividerVisibilityListener);
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to register docked stack listener", e);
-        }
-        mForcedResizableController = new ForcedResizableInfoActivityController(mContext);
+        mWindowManager = new DividerWindowManager(mSystemWindows);
+        mDisplayController.addDisplayWindowListener(this);
+        // Hide the divider when keyguard is showing. Even though keyguard/statusbar is above
+        // everything, it is actually transparent except for notifications, so we still need to
+        // hide any surfaces that are below it.
+        // TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
+        mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+            @Override
+            public void onUnlockedChanged() {
+
+            }
+
+            @Override
+            public void onKeyguardShowingChanged() {
+                if (!inSplitMode() || mView == null || mView.getViewRootImpl() == null
+                        || mView.getViewRootImpl().getSurfaceControl() == null) {
+                    return;
+                }
+                mView.setHidden(mKeyguardStateController.isShowing());
+            }
+
+            @Override
+            public void onKeyguardFadingAwayChanged() {
+
+            }
+        });
+        // Don't initialize the divider or anything until we get the default display.
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
+    public void onDisplayAdded(int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+        mSplitLayout = new SplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
+                mDisplayController.getDisplayLayout(displayId), mSplits);
+        mImeController.addPositionProcessor(mImePositionProcessor);
+        mDisplayController.addDisplayChangingController(mRotationController);
+        try {
+            mSplits.init(ActivityTaskManager.getTaskOrganizerController(), mSurfaceSession);
+            // Set starting tile bounds based on middle target
+            final WindowContainerTransaction tct = new WindowContainerTransaction();
+            int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+            mSplitLayout.resizeSplits(midPos, tct);
+            ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(tct,
+                    null /* organizer */);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to register docked stack listener", e);
+        }
+        update(mDisplayController.getDisplayContext(displayId).getResources().getConfiguration());
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+        mSplitLayout = new SplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
+                mDisplayController.getDisplayLayout(displayId), mSplits);
+        if (mRotateSplitLayout == null) {
+            int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+            final WindowContainerTransaction tct = new WindowContainerTransaction();
+            mSplitLayout.resizeSplits(midPos, tct);
+            try {
+                ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(tct,
+                        null /* organizer */);
+            } catch (RemoteException e) {
+            }
+        } else if (mRotateSplitLayout != null
+                && mSplitLayout.mDisplayLayout.rotation()
+                        == mRotateSplitLayout.mDisplayLayout.rotation()) {
+            mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
+            mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
+            mRotateSplitLayout = null;
+        }
         update(newConfig);
     }
 
+    Handler getHandler() {
+        return mHandler;
+    }
+
     public DividerView getView() {
         return mView;
     }
@@ -92,18 +304,25 @@
         return mHomeStackResizable;
     }
 
+    /** {@code true} if this is visible */
+    public boolean inSplitMode() {
+        return mView != null && mView.getVisibility() == View.VISIBLE;
+    }
+
     private void addDivider(Configuration configuration) {
+        Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
         mView = (DividerView)
-                LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
-        mView.injectDependencies(mWindowManager, mDividerState, this);
+                LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
+        DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
+        mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout);
         mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
         mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
-        final int size = mContext.getResources().getDimensionPixelSize(
+        final int size = dctx.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
-        final int width = landscape ? size : MATCH_PARENT;
-        final int height = landscape ? MATCH_PARENT : size;
-        mWindowManager.add(mView, width, height);
+        final int width = landscape ? size : displayLayout.width();
+        final int height = landscape ? displayLayout.height() : size;
+        mWindowManager.add(mView, width, height, mContext.getDisplayId());
     }
 
     private void removeDivider() {
@@ -116,65 +335,86 @@
     private void update(Configuration configuration) {
         removeDivider();
         addDivider(configuration);
-        if (mMinimized) {
+        if (mMinimized && mView != null) {
             mView.setMinimizedDockStack(true, mHomeStackResizable);
             updateTouchable();
         }
     }
 
-    private void updateVisibility(final boolean visible) {
-        mView.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mVisible != visible) {
-                    mVisible = visible;
-                    mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+    void updateVisibility(final boolean visible) {
+        if (mVisible != visible) {
+            mVisible = visible;
+            mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
 
-                    // Update state because animations won't finish.
-                    mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
-                }
+            if (visible) {
+                mView.enterSplitMode(mHomeStackResizable);
+                // Update state because animations won't finish.
+                mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
+            } else {
+                mView.exitSplitMode();
+                // un-minimize so that next entry triggers minimize anim.
+                mView.setMinimizedDockStack(false /* minimized */, mHomeStackResizable);
             }
-        });
+            // Notify existence listeners
+            synchronized (mDockedStackExistsListeners) {
+                mDockedStackExistsListeners.removeIf(wf -> {
+                    Consumer<Boolean> l = wf.get();
+                    if (l != null) l.accept(visible);
+                    return l == null;
+                });
+            }
+        }
+    }
+
+    private void setHomeStackResizable(boolean resizable) {
+        if (mHomeStackResizable == resizable) {
+            return;
+        }
+        mHomeStackResizable = resizable;
+        if (!inSplitMode()) {
+            return;
+        }
+        WindowManagerProxy.applyHomeTasksMinimized(mSplitLayout, mSplits.mSecondary.token);
     }
 
     private void updateMinimizedDockedStack(final boolean minimized, final long animDuration,
             final boolean isHomeStackResizable) {
-        mView.post(new Runnable() {
-            @Override
-            public void run() {
-                mHomeStackResizable = isHomeStackResizable;
-                if (mMinimized != minimized) {
-                    mMinimized = minimized;
-                    updateTouchable();
-                    if (animDuration > 0) {
-                        mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
-                    } else {
-                        mView.setMinimizedDockStack(minimized, isHomeStackResizable);
-                    }
-                }
+        setHomeStackResizable(isHomeStackResizable);
+        if (animDuration > 0) {
+            mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
+        } else {
+            mView.setMinimizedDockStack(minimized, isHomeStackResizable);
+        }
+        updateTouchable();
+    }
+
+    /** Switch to minimized state if appropriate */
+    public void setMinimized(final boolean minimized) {
+        mHandler.post(() -> {
+            if (!inSplitMode()) {
+                return;
             }
+            if (mMinimized == minimized) {
+                return;
+            }
+            mMinimized = minimized;
+            mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable);
+            updateTouchable();
         });
     }
 
-    private void notifyDockedStackExistsChanged(final boolean exists) {
-        mView.post(new Runnable() {
-            @Override
-            public void run() {
-                mForcedResizableController.notifyDockedStackExistsChanged(exists);
-            }
-        });
+    void setAdjustedForIme(boolean adjustedForIme) {
+        if (mAdjustedForIme == adjustedForIme) {
+            return;
+        }
+        mAdjustedForIme = adjustedForIme;
+        updateTouchable();
     }
 
     private void updateTouchable() {
         mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
     }
 
-    public void onRecentsActivityStarting() {
-        if (mView != null) {
-            mView.onRecentsActivityStarting();
-        }
-    }
-
     /**
      * Workaround for b/62528361, at the time recents has drawn, it may happen before a
      * configuration change to the Divider, and internally, the event will be posted to the
@@ -206,6 +446,9 @@
     }
 
     public void onAppTransitionFinished() {
+        if (mView == null) {
+            return;
+        }
         mForcedResizableController.onAppTransitionFinished();
     }
 
@@ -231,46 +474,66 @@
         pw.print("  mAdjustedForIme="); pw.println(mAdjustedForIme);
     }
 
-    class DockDividerVisibilityListener extends IDockedStackListener.Stub {
+    long getAnimDuration() {
+        float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE,
+                mContext.getResources().getFloat(
+                        com.android.internal.R.dimen
+                                .config_appTransitionAnimationDurationScaleDefault));
+        final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION;
+        return (long) (transitionDuration * transitionScale);
+    }
 
-        @Override
-        public void onDividerVisibilityChanged(boolean visible) throws RemoteException {
-            updateVisibility(visible);
+    /** Register a listener that gets called whenever the existence of the divider changes */
+    public void registerInSplitScreenListener(Consumer<Boolean> listener) {
+        listener.accept(inSplitMode());
+        synchronized (mDockedStackExistsListeners) {
+            mDockedStackExistsListeners.add(new WeakReference<>(listener));
         }
+    }
 
-        @Override
-        public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
-            notifyDockedStackExistsChanged(exists);
+    void startEnterSplit() {
+        // Set resizable directly here because applyEnterSplit already resizes home stack.
+        mHomeStackResizable = WindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
+    }
+
+    void ensureMinimizedSplit() {
+        final boolean wasMinimized = mMinimized;
+        mMinimized = true;
+        setHomeStackResizable(mSplits.mSecondary.isResizable());
+        if (!inSplitMode()) {
+            // Wasn't in split-mode yet, so enter now.
+            if (DEBUG) {
+                Log.d(TAG, " entering split mode with minimized=true");
+            }
+            updateVisibility(true /* visible */);
+        } else if (!wasMinimized) {
+            if (DEBUG) {
+                Log.d(TAG, " in split mode, but minimizing ");
+            }
+            // Was already in split-mode, update just minimized state.
+            updateMinimizedDockedStack(mMinimized, getAnimDuration(),
+                    mHomeStackResizable);
         }
+    }
 
-        @Override
-        public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
-                boolean isHomeStackResizable) throws RemoteException {
-            mHomeStackResizable = isHomeStackResizable;
-            updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable);
+    void ensureNormalSplit() {
+        if (!inSplitMode()) {
+            // Wasn't in split-mode, so enter now.
+            if (DEBUG) {
+                Log.d(TAG, " enter split mode unminimized ");
+            }
+            mMinimized = false;
+            updateVisibility(true /* visible */);
         }
-
-        @Override
-        public void onAdjustedForImeChanged(boolean adjustedForIme, long animDuration)
-                throws RemoteException {
-            mView.post(() -> {
-                if (mAdjustedForIme != adjustedForIme) {
-                    mAdjustedForIme = adjustedForIme;
-                    updateTouchable();
-                    if (!mMinimized) {
-                        if (animDuration > 0) {
-                            mView.setAdjustedForIme(adjustedForIme, animDuration);
-                        } else {
-                            mView.setAdjustedForIme(adjustedForIme);
-                        }
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onDockSideChanged(final int newDockSide) throws RemoteException {
-            mView.post(() -> mView.notifyDockSideChanged(newDockSide));
+        if (mMinimized) {
+            // Was in minimized state, so leave that.
+            if (DEBUG) {
+                Log.d(TAG, " in split mode already, but unminimizing ");
+            }
+            mMinimized = false;
+            updateMinimizedDockedStack(mMinimized, getAnimDuration(),
+                    mHomeStackResizable);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
index 49f4d5e..f3b2553 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
@@ -17,8 +17,14 @@
 package com.android.systemui.stackdivider;
 
 import android.content.Context;
+import android.os.Handler;
 
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.wm.DisplayController;
+import com.android.systemui.wm.DisplayImeController;
+import com.android.systemui.wm.SystemWindows;
 
 import java.util.Optional;
 
@@ -35,7 +41,11 @@
 public class DividerModule {
     @Singleton
     @Provides
-    static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
-        return new Divider(context, recentsOptionalLazy);
+    static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy,
+            DisplayController displayController, SystemWindows systemWindows,
+            DisplayImeController imeController, @Main Handler handler,
+            KeyguardStateController keyguardStateController) {
+        return new Divider(context, recentsOptionalLazy, displayController, systemWindows,
+                imeController, handler, keyguardStateController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 9fe6e84..fdd04b9 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -16,12 +16,8 @@
 
 package com.android.systemui.stackdivider;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 
@@ -40,10 +36,11 @@
 import android.util.AttributeSet;
 import android.view.Choreographer;
 import android.view.Display;
-import android.view.DisplayInfo;
 import android.view.InsetsState;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.View.OnTouchListener;
@@ -75,6 +72,7 @@
  */
 public class DividerView extends FrameLayout implements OnTouchListener,
         OnComputeInternalInsetsListener {
+    private static final String TAG = "DividerView";
 
     public interface DividerCallbacks {
         void onDraggingStart();
@@ -123,14 +121,11 @@
     private int mTouchSlop;
     private boolean mBackgroundLifted;
     private boolean mIsInMinimizeInteraction;
-    private SnapTarget mSnapTargetBeforeMinimized;
+    SnapTarget mSnapTargetBeforeMinimized;
 
     private int mDividerInsets;
     private final Display mDefaultDisplay;
-    private int mDisplayWidth;
-    private int mDisplayHeight;
-    private int mDisplayRotation;
-    private int mDividerWindowWidth;
+
     private int mDividerSize;
     private int mTouchElevation;
     private int mLongPressEntraceAnimDuration;
@@ -147,8 +142,7 @@
     private DividerWindowManager mWindowManager;
     private VelocityTracker mVelocityTracker;
     private FlingAnimationUtils mFlingAnimationUtils;
-    private DividerSnapAlgorithm mSnapAlgorithm;
-    private DividerSnapAlgorithm mMinimizedSnapAlgorithm;
+    private SplitDisplayLayout mSplitLayout;
     private DividerCallbacks mCallback;
     private final Rect mStableInsets = new Rect();
 
@@ -163,6 +157,10 @@
     private DividerState mState;
     private final SurfaceFlingerVsyncChoreographer mSfChoreographer;
 
+    private SplitScreenTaskOrganizer mTiles;
+    boolean mFirstLayout = true;
+    int mDividerPositionX;
+    int mDividerPositionY;
 
     // The view is removed or in the process of been removed from the system.
     private boolean mRemoved;
@@ -172,7 +170,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_RESIZE_STACK:
-                    resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
+                    resizeStackSurfaces(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
                     break;
                 default:
                     super.handleMessage(msg);
@@ -228,16 +226,17 @@
         public boolean performAccessibilityAction(View host, int action, Bundle args) {
             int currentPosition = getCurrentPosition();
             SnapTarget nextTarget = null;
+            DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm();
             if (action == R.id.action_move_tl_full) {
-                nextTarget = mSnapAlgorithm.getDismissEndTarget();
+                nextTarget = snapAlgorithm.getDismissEndTarget();
             } else if (action == R.id.action_move_tl_70) {
-                nextTarget = mSnapAlgorithm.getLastSplitTarget();
+                nextTarget = snapAlgorithm.getLastSplitTarget();
             } else if (action == R.id.action_move_tl_50) {
-                nextTarget = mSnapAlgorithm.getMiddleTarget();
+                nextTarget = snapAlgorithm.getMiddleTarget();
             } else if (action == R.id.action_move_tl_30) {
-                nextTarget = mSnapAlgorithm.getFirstSplitTarget();
+                nextTarget = snapAlgorithm.getFirstSplitTarget();
             } else if (action == R.id.action_move_rb_full) {
-                nextTarget = mSnapAlgorithm.getDismissStartTarget();
+                nextTarget = snapAlgorithm.getDismissStartTarget();
             }
             if (nextTarget != null) {
                 startDragging(true /* animate */, false /* touching */);
@@ -284,11 +283,11 @@
         mBackground = findViewById(R.id.docked_divider_background);
         mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
         mHandle.setOnTouchListener(this);
-        mDividerWindowWidth = getResources().getDimensionPixelSize(
+        final int dividerWindowWidth = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         mDividerInsets = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_insets);
-        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+        mDividerSize = dividerWindowWidth - 2 * mDividerInsets;
         mTouchElevation = getResources().getDimensionPixelSize(
                 R.dimen.docked_stack_divider_lift_elevation);
         mLongPressEntraceAnimDuration = getResources().getInteger(
@@ -296,7 +295,6 @@
         mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
         mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f);
-        updateDisplayInfo();
         boolean landscape = getResources().getConfiguration().orientation
                 == Configuration.ORIENTATION_LANDSCAPE;
         mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
@@ -314,6 +312,7 @@
                 && !mIsInMinimizeInteraction) {
             saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
         }
+        mFirstLayout = true;
     }
 
     void onDividerRemoved() {
@@ -341,17 +340,17 @@
                 || mStableInsets.bottom != insets.getStableInsetBottom()) {
             mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
                     insets.getStableInsetRight(), insets.getStableInsetBottom());
-            if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) {
-                mSnapAlgorithm = null;
-                mMinimizedSnapAlgorithm = null;
-                initializeSnapAlgorithm();
-            }
         }
         return super.onApplyWindowInsets(insets);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (mFirstLayout) {
+            // Wait for first layout so that the ViewRootImpl surface has been created.
+            initializeSurfaceState();
+            mFirstLayout = false;
+        }
         super.onLayout(changed, left, top, right, bottom);
         int minimizeLeft = 0;
         int minimizeTop = 0;
@@ -372,19 +371,16 @@
     }
 
     public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
-            DividerCallbacks callback) {
+            DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl) {
         mWindowManager = windowManager;
         mState = dividerState;
         mCallback = callback;
-
-        // Set the previous position ratio before minimized state after attaching this divider
-        if (mStableInsets.isEmpty()) {
-            WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
-        }
+        mTiles = tiles;
+        mSplitLayout = sdl;
 
         if (mState.mRatioPositionBeforeMinimized == 0) {
             // Set the middle target as the initial state
-            mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget();
+            mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
         } else {
             repositionSnapTargetBeforeMinimized();
         }
@@ -411,18 +407,34 @@
         return mOtherTaskRect;
     }
 
+    private boolean inSplitMode() {
+        return getVisibility() == VISIBLE;
+    }
+
+    /** Unlike setVisible, this directly hides the surface without changing view visibility. */
+    void setHidden(boolean hidden) {
+        post(() -> {
+            if (!isViewSurfaceValid()) {
+                return;
+            }
+            Transaction t = mTiles.getTransaction();
+            if (hidden) {
+                t.hide(getViewRootImpl().getSurfaceControl());
+            } else {
+                t.show(getViewRootImpl().getSurfaceControl());
+            }
+            t.apply();
+            mTiles.releaseTransaction(t);
+        });
+    }
+
     public boolean startDragging(boolean animate, boolean touching) {
         cancelFlingAnimation();
         if (touching) {
             mHandle.setTouching(true, animate);
         }
-        mDockSide = mWindowManagerProxy.getDockSide();
+        mDockSide = mSplitLayout.getPrimarySplitSide();
 
-        // Update snap algorithm if rotation has occurred
-        if (mDisplayRotation != mDefaultDisplay.getRotation()) {
-            updateDisplayInfo();
-        }
-        initializeSnapAlgorithm();
         mWindowManagerProxy.setResizing(true);
         if (touching) {
             mWindowManager.setSlippery(false);
@@ -431,7 +443,7 @@
         if (mCallback != null) {
             mCallback.onDraggingStart();
         }
-        return mDockSide != WindowManager.DOCKED_INVALID;
+        return inSplitMode();
     }
 
     public void stopDragging(int position, float velocity, boolean avoidDismissStart,
@@ -467,38 +479,22 @@
     }
 
     private void updateDockSide() {
-        mDockSide = mWindowManagerProxy.getDockSide();
+        mDockSide = mSplitLayout.getPrimarySplitSide();
         mMinimizedShadow.setDockSide(mDockSide);
     }
 
-    private void initializeSnapAlgorithm() {
-        if (mSnapAlgorithm == null) {
-            mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
-                    mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets, mDockSide);
-            if (mSnapTargetBeforeMinimized != null && mSnapTargetBeforeMinimized.isMiddleTarget) {
-                mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget();
-            }
-        }
-        if (mMinimizedSnapAlgorithm == null) {
-            mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
-                    mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
-                    mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
-        }
-    }
-
     public DividerSnapAlgorithm getSnapAlgorithm() {
-        initializeSnapAlgorithm();
-        return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm :
-                mSnapAlgorithm;
+        return mDockedStackMinimized
+                && mHomeStackResizable ? mSplitLayout.getMinimizedSnapAlgorithm()
+                        : mSplitLayout.getSnapAlgorithm();
     }
 
     public int getCurrentPosition() {
-        getLocationOnScreen(mTempInt2);
-        if (isHorizontalDivision()) {
-            return mTempInt2[1] + mDividerInsets;
-        } else {
-            return mTempInt2[0] + mDividerInsets;
-        }
+        return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX;
+    }
+
+    public boolean isMinimized() {
+        return mDockedStackMinimized;
     }
 
     @Override
@@ -557,25 +553,25 @@
     }
 
     private void logResizeEvent(SnapTarget snapTarget) {
-        if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
+        if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) {
             MetricsLogger.action(
                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
                             ? LOG_VALUE_UNDOCK_MAX_OTHER
                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
-        } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) {
+        } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) {
             MetricsLogger.action(
                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
                             ? LOG_VALUE_UNDOCK_MAX_OTHER
                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
-        } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) {
+        } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
                     LOG_VALUE_RESIZE_50_50);
-        } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) {
+        } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
                     dockSideTopLeft(mDockSide)
                             ? LOG_VALUE_RESIZE_DOCKED_SMALLER
                             : LOG_VALUE_RESIZE_DOCKED_LARGER);
-        } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) {
+        } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
                     dockSideTopLeft(mDockSide)
                             ? LOG_VALUE_RESIZE_DOCKED_LARGER
@@ -625,12 +621,16 @@
                         : snapTarget.taskPosition,
                 snapTarget));
         Runnable endAction = () -> {
-            commitSnapFlags(snapTarget);
+            boolean dismissed = commitSnapFlags(snapTarget);
             mWindowManagerProxy.setResizing(false);
             updateDockSide();
             mCurrentAnimator = null;
             mEntranceAnimationRunning = false;
             mExitAnimationRunning = false;
+            if (!dismissed) {
+                WindowManagerProxy.applyResizeSplits((mIsInMinimizeInteraction
+                        ? mSnapTargetBeforeMinimized : snapTarget).position, mSplitLayout);
+            }
             if (mCallback != null) {
                 mCallback.onDraggingEnd();
             }
@@ -642,12 +642,13 @@
                 // position isn't negative.
                 final SnapTarget saveTarget;
                 if (snapTarget.position < 0) {
-                    saveTarget = mSnapAlgorithm.getMiddleTarget();
+                    saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
                 } else {
                     saveTarget = snapTarget;
                 }
-                if (saveTarget.position != mSnapAlgorithm.getDismissEndTarget().position
-                        && saveTarget.position != mSnapAlgorithm.getDismissStartTarget().position) {
+                final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm();
+                if (saveTarget.position != snapAlgo.getDismissEndTarget().position
+                        && saveTarget.position != snapAlgo.getDismissStartTarget().position) {
                     saveSnapTargetBeforeMinimized(saveTarget);
                 }
             }
@@ -701,11 +702,11 @@
         }
     }
 
-    private void commitSnapFlags(SnapTarget target) {
+    private boolean commitSnapFlags(SnapTarget target) {
         if (target.flag == SnapTarget.FLAG_NONE) {
-            return;
+            return false;
         }
-        boolean dismissOrMaximize;
+        final boolean dismissOrMaximize;
         if (target.flag == SnapTarget.FLAG_DISMISS_START) {
             dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
                     || mDockSide == WindowManager.DOCKED_TOP;
@@ -713,12 +714,13 @@
             dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
                     || mDockSide == WindowManager.DOCKED_BOTTOM;
         }
-        if (dismissOrMaximize) {
-            mWindowManagerProxy.dismissDockedStack();
-        } else {
-            mWindowManagerProxy.maximizeDockedStack();
-        }
-        mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
+        mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, dismissOrMaximize);
+        Transaction t = mTiles.getTransaction();
+        setResizeDimLayer(t, true /* primary */, 0f);
+        setResizeDimLayer(t, false /* primary */, 0f);
+        t.apply();
+        mTiles.releaseTransaction(t);
+        return true;
     }
 
     private void liftBackground() {
@@ -765,6 +767,28 @@
         mBackgroundLifted = false;
     }
 
+    private void initializeSurfaceState() {
+        int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+        // Recalculate the split-layout's internal tile bounds
+        mSplitLayout.resizeSplits(midPos);
+        Transaction t = mTiles.getTransaction();
+        if (mDockedStackMinimized) {
+            int position = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget().position;
+            calculateBoundsForPosition(position, mDockSide, mDockedRect);
+            calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
+                    mOtherRect);
+            mDividerPositionX = mDividerPositionY = position;
+            resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary,
+                    mOtherRect, mSplitLayout.mSecondary);
+        } else {
+            resizeSplitSurfaces(t, mSplitLayout.mPrimary, null,
+                    mSplitLayout.mSecondary, null);
+        }
+        setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
+        setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */);
+        t.apply();
+        mTiles.releaseTransaction(t);
+    }
 
     public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
         mHomeStackResizable = isHomeStackResizable;
@@ -789,15 +813,11 @@
             mDockedStackMinimized = minimized;
         } else if (mDockedStackMinimized != minimized) {
             mDockedStackMinimized = minimized;
-            if (mDisplayRotation != mDefaultDisplay.getRotation()) {
+            if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
                 // Splitscreen to minimize is about to starts after rotating landscape to seascape,
                 // update insets, display info and snap algorithm targets
                 WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
                 repositionSnapTargetBeforeMinimized();
-                updateDisplayInfo();
-            } else {
-                mMinimizedSnapAlgorithm = null;
-                initializeSnapAlgorithm();
             }
             if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
                 cancelFlingAnimation();
@@ -805,15 +825,51 @@
                     // Relayout to recalculate the divider shadow when minimizing
                     requestLayout();
                     mIsInMinimizeInteraction = true;
-                    resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget());
+                    resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget());
                 } else {
-                    resizeStack(mSnapTargetBeforeMinimized);
+                    resizeStackSurfaces(mSnapTargetBeforeMinimized);
                     mIsInMinimizeInteraction = false;
                 }
             }
         }
     }
 
+    void enterSplitMode(boolean isHomeStackResizable) {
+        post(() -> {
+            if (!isViewSurfaceValid()) {
+                return;
+            }
+            Transaction t = mTiles.getTransaction();
+            t.show(getViewRootImpl().getSurfaceControl()).apply();
+            mTiles.releaseTransaction(t);
+        });
+        if (isHomeStackResizable) {
+            SnapTarget miniMid = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget();
+            if (mDockedStackMinimized) {
+                mDividerPositionY = mDividerPositionX = miniMid.position;
+            }
+        }
+    }
+
+    private boolean isViewSurfaceValid() {
+        return getViewRootImpl() != null && getViewRootImpl().getSurfaceControl() != null
+                && getViewRootImpl().getSurfaceControl().isValid();
+    }
+
+    void exitSplitMode() {
+        // Reset tile bounds
+        post(() -> {
+            if (!isViewSurfaceValid()) {
+                return;
+            }
+            Transaction t = mTiles.getTransaction();
+            t.hide(getViewRootImpl().getSurfaceControl()).apply();
+            mTiles.releaseTransaction(t);
+        });
+        int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+        WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
+    }
+
     public void setMinimizedDockStack(boolean minimized, long animDuration,
             boolean isHomeStackResizable) {
         mHomeStackResizable = isHomeStackResizable;
@@ -844,14 +900,12 @@
             mDockedStackMinimized = minimized;
         } else if (mDockedStackMinimized != minimized) {
             mIsInMinimizeInteraction = true;
-            mMinimizedSnapAlgorithm = null;
             mDockedStackMinimized = minimized;
-            initializeSnapAlgorithm();
             stopDragging(minimized
                             ? mSnapTargetBeforeMinimized.position
                             : getCurrentPosition(),
                     minimized
-                            ? mMinimizedSnapAlgorithm.getMiddleTarget()
+                            ? mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget()
                             : mSnapTargetBeforeMinimized,
                     animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
             setAdjustedForIme(false, animDuration);
@@ -865,18 +919,6 @@
                 .start();
     }
 
-    public void setAdjustedForIme(boolean adjustedForIme) {
-        updateDockSide();
-        mHandle.setAlpha(adjustedForIme ? 0f : 1f);
-        if (!adjustedForIme) {
-            resetBackground();
-        } else if (mDockSide == WindowManager.DOCKED_TOP) {
-            mBackground.setPivotY(0);
-            mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
-        }
-        mAdjustedForIme = adjustedForIme;
-    }
-
     public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
         updateDockSide();
         mHandle.animate()
@@ -902,7 +944,8 @@
     private void saveSnapTargetBeforeMinimized(SnapTarget target) {
         mSnapTargetBeforeMinimized = target;
         mState.mRatioPositionBeforeMinimized = (float) target.position /
-                (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth);
+                (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
+                        : mSplitLayout.mDisplayLayout.width());
     }
 
     private void resetBackground() {
@@ -916,51 +959,17 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        updateDisplayInfo();
-    }
-
-    public void notifyDockSideChanged(int newDockSide) {
-        int oldDockSide = mDockSide;
-        mDockSide = newDockSide;
-        mMinimizedShadow.setDockSide(mDockSide);
-        requestLayout();
-
-        // Update the snap position to the new docked side with correct insets
-        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
-        mMinimizedSnapAlgorithm = null;
-        initializeSnapAlgorithm();
-
-        if (oldDockSide == DOCKED_LEFT && mDockSide == DOCKED_RIGHT
-                || oldDockSide == DOCKED_RIGHT && mDockSide == DOCKED_LEFT) {
-            repositionSnapTargetBeforeMinimized();
-        }
-
-        // Landscape to seascape rotation requires minimized to resize docked app correctly
-        if (mHomeStackResizable && mDockedStackMinimized) {
-            resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget());
-        }
     }
 
     private void repositionSnapTargetBeforeMinimized() {
         int position = (int) (mState.mRatioPositionBeforeMinimized *
-                (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth));
-        mSnapAlgorithm = null;
-        initializeSnapAlgorithm();
+                (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
+                        : mSplitLayout.mDisplayLayout.width()));
 
         // Set the snap target before minimized but do not save until divider is attached and not
         // minimized because it does not know its minimized state yet.
-        mSnapTargetBeforeMinimized = mSnapAlgorithm.calculateNonDismissingSnapTarget(position);
-    }
-
-    private void updateDisplayInfo() {
-        mDisplayRotation = mDefaultDisplay.getRotation();
-        final DisplayInfo info = new DisplayInfo();
-        mDefaultDisplay.getDisplayInfo(info);
-        mDisplayWidth = info.logicalWidth;
-        mDisplayHeight = info.logicalHeight;
-        mSnapAlgorithm = null;
-        mMinimizedSnapAlgorithm = null;
-        initializeSnapAlgorithm();
+        mSnapTargetBeforeMinimized =
+                mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position);
     }
 
     private int calculatePosition(int touchX, int touchY) {
@@ -994,8 +1003,9 @@
     }
 
     public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
-        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
-                mDisplayHeight, mDividerSize);
+        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect,
+                mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(),
+                mDividerSize);
     }
 
     public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) {
@@ -1005,16 +1015,61 @@
         mSfChoreographer.scheduleAtSfVsync(mHandler, message);
     }
 
-    private void resizeStack(SnapTarget taskSnapTarget) {
-        resizeStack(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget);
+    private void resizeStackSurfaces(SnapTarget taskSnapTarget) {
+        resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget);
     }
 
-    public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
+    void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) {
+        post(() -> resizeSplitSurfaces(t, dockedRect, null, otherRect, null));
+    }
+
+    private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect,
+            Rect otherRect, Rect otherTaskRect) {
+        dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
+        otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
+
+        mDividerPositionX = dockedRect.right;
+        mDividerPositionY = dockedRect.bottom;
+
+        t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
+        Rect crop = new Rect(dockedRect);
+        crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
+                -Math.min(dockedTaskRect.top - dockedRect.top, 0));
+        t.setWindowCrop(mTiles.mPrimarySurface, crop);
+        t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top);
+        crop.set(otherRect);
+        crop.offsetTo(-(otherTaskRect.left - otherRect.left),
+                -(otherTaskRect.top - otherRect.top));
+        t.setWindowCrop(mTiles.mSecondarySurface, crop);
+        SurfaceControl dividerCtrl = getViewRootImpl() != null
+                ? getViewRootImpl().getSurfaceControl() : null;
+        if (dividerCtrl != null && dividerCtrl.isValid()) {
+            if (isHorizontalDivision()) {
+                t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets);
+            } else {
+                t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
+            }
+        }
+    }
+
+    void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
+        SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim;
+        if (alpha <= 0.f) {
+            t.hide(dim);
+        } else {
+            t.setAlpha(dim, alpha);
+            t.show(dim);
+        }
+    }
+
+    void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget) {
         if (mRemoved) {
             // This divider view has been removed so shouldn't have any additional influence.
             return;
         }
         calculateBoundsForPosition(position, mDockSide, mDockedRect);
+        calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
+                mOtherRect);
 
         if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
             return;
@@ -1025,6 +1080,7 @@
             mBackground.invalidate();
         }
 
+        Transaction t = mTiles.getTransaction();
         mLastResizeRect.set(mDockedRect);
         if (mHomeStackResizable && mIsInMinimizeInteraction) {
             calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
@@ -1037,8 +1093,10 @@
                 mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
                         - mDockedTaskRect.left + mDividerSize, 0);
             }
-            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
-                    mOtherTaskRect, null);
+            resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect,
+                    mOtherTaskRect);
+            t.apply();
+            mTiles.releaseTransaction(t);
             return;
         }
 
@@ -1052,8 +1110,7 @@
             }
             calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
                     mOtherTaskRect);
-            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
-                    mOtherTaskRect, null);
+            resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
         } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
             calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
             mDockedInsetRect.set(mDockedTaskRect);
@@ -1066,8 +1123,7 @@
             if (mDockSide == DOCKED_RIGHT) {
                 mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0);
             }
-            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
-                    mOtherTaskRect, mOtherInsetRect);
+            resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
         } else if (taskPosition != TASK_POSITION_SAME) {
             calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
                     mOtherRect);
@@ -1078,7 +1134,8 @@
                     restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
             calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
             calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
-            mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
+            mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(),
+                    mSplitLayout.mDisplayLayout.height());
             alignTopLeft(mDockedRect, mDockedTaskRect);
             alignTopLeft(mOtherRect, mOtherTaskRect);
             mDockedInsetRect.set(mDockedTaskRect);
@@ -1094,15 +1151,15 @@
                     taskPositionDocked);
             applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
                     taskPositionOther);
-            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
-                    mOtherTaskRect, mOtherInsetRect);
+            resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
         } else {
-            mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
+            resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null);
         }
         SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
         float dimFraction = getDimFraction(position, closestDismissTarget);
-        mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
-                getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
+        setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction);
+        t.apply();
+        mTiles.releaseTransaction(t);
     }
 
     private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1156,10 +1213,12 @@
     private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
             SnapTarget snapTarget) {
         if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
-            return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
+            return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position,
+                    mStartPosition);
         } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
                 && dockSideBottomRight(dockSide)) {
-            return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
+            return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position,
+                    mStartPosition);
         } else {
             return taskPosition;
         }
@@ -1171,19 +1230,19 @@
     private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
             int position, int taskPosition) {
         float fraction = Math.min(1, Math.max(0,
-                mSnapAlgorithm.calculateDismissingFraction(position)));
+                mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position)));
         SnapTarget dismissTarget = null;
         SnapTarget splitTarget = null;
         int start = 0;
-        if (position <= mSnapAlgorithm.getLastSplitTarget().position
+        if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
                 && dockSideTopLeft(dockSide)) {
-            dismissTarget = mSnapAlgorithm.getDismissStartTarget();
-            splitTarget = mSnapAlgorithm.getFirstSplitTarget();
+            dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
+            splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget();
             start = taskPosition;
-        } else if (position >= mSnapAlgorithm.getLastSplitTarget().position
+        } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
                 && dockSideBottomRight(dockSide)) {
-            dismissTarget = mSnapAlgorithm.getDismissEndTarget();
-            splitTarget = mSnapAlgorithm.getLastSplitTarget();
+            dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget();
+            splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget();
             start = splitTarget.position;
         }
         if (dismissTarget != null && fraction > 0f
@@ -1236,14 +1295,10 @@
         }
     }
 
-    private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
-        if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
+    private boolean isDismissTargetPrimary(SnapTarget dismissTarget) {
+        return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
-                        && dockSideBottomRight(mDockSide))) {
-            return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-        } else {
-            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
+                        && dockSideBottomRight(mDockSide));
     }
 
     /**
@@ -1297,7 +1352,7 @@
     }
 
     void onDockedFirstAnimationFrame() {
-        saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
+        saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget());
     }
 
     void onDockedTopTask() {
@@ -1307,8 +1362,9 @@
         updateDockSide();
         mEntranceAnimationRunning = true;
 
-        resizeStack(calculatePositionForInsetBounds(), mSnapAlgorithm.getMiddleTarget().position,
-                mSnapAlgorithm.getMiddleTarget());
+        resizeStackSurfaces(calculatePositionForInsetBounds(),
+                mSplitLayout.getSnapAlgorithm().getMiddleTarget().position,
+                mSplitLayout.getSnapAlgorithm().getMiddleTarget());
     }
 
     void onRecentsDrawn() {
@@ -1337,13 +1393,12 @@
     }
 
     void onUndockingTask() {
-        int dockSide = mWindowManagerProxy.getDockSide();
-        if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable
-                || !mDockedStackMinimized)) {
+        int dockSide = mSplitLayout.getPrimarySplitSide();
+        if (inSplitMode() && (mHomeStackResizable || !mDockedStackMinimized)) {
             startDragging(false /* animate */, false /* touching */);
             SnapTarget target = dockSideTopLeft(dockSide)
-                    ? mSnapAlgorithm.getDismissEndTarget()
-                    : mSnapAlgorithm.getDismissStartTarget();
+                    ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget()
+                    : mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
 
             // Don't start immediately - give a little bit time to settle the drag resize change.
             mExitAnimationRunning = true;
@@ -1354,8 +1409,7 @@
     }
 
     private int calculatePositionForInsetBounds() {
-        mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
-        mTmpRect.inset(mStableInsets);
+        mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect);
         return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
index 2486d653..bd843b0 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -26,12 +26,13 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 
-import android.content.Context;
 import android.graphics.PixelFormat;
 import android.os.Binder;
 import android.view.View;
 import android.view.WindowManager;
 
+import com.android.systemui.wm.SystemWindows;
+
 /**
  * Manages the window parameters of the docked stack divider.
  */
@@ -39,15 +40,16 @@
 
     private static final String WINDOW_TITLE = "DockedStackDivider";
 
-    private final WindowManager mWindowManager;
+    private final SystemWindows mSystemWindows;
     private WindowManager.LayoutParams mLp;
     private View mView;
 
-    public DividerWindowManager(Context ctx) {
-        mWindowManager = ctx.getSystemService(WindowManager.class);
+    public DividerWindowManager(SystemWindows systemWindows) {
+        mSystemWindows = systemWindows;
     }
 
-    public void add(View view, int width, int height) {
+    /** Add a divider view */
+    public void add(View view, int width, int height, int displayId) {
         mLp = new WindowManager.LayoutParams(
                 width, height, TYPE_DOCK_DIVIDER,
                 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
@@ -60,13 +62,13 @@
         view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-        mWindowManager.addView(view, mLp);
+        mSystemWindows.addView(view, mLp, displayId, TYPE_DOCK_DIVIDER);
         mView = view;
     }
 
     public void remove() {
         if (mView != null) {
-            mWindowManager.removeView(mView);
+            mSystemWindows.removeView(mView);
         }
         mView = null;
     }
@@ -81,7 +83,7 @@
             changed = true;
         }
         if (changed) {
-            mWindowManager.updateViewLayout(mView, mLp);
+            mSystemWindows.updateViewLayout(mView, mLp);
         }
     }
 
@@ -95,7 +97,7 @@
             changed = true;
         }
         if (changed) {
-            mWindowManager.updateViewLayout(mView, mLp);
+            mSystemWindows.updateViewLayout(mView, mLp);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index c6ac309..db7996e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,6 +31,8 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
+import java.util.function.Consumer;
+
 /**
  * Controller that decides when to show the {@link ForcedResizableInfoActivity}.
  */
@@ -52,6 +54,12 @@
         }
     };
 
+    private final Consumer<Boolean> mDockedStackExistsListener = exists -> {
+        if (!exists) {
+            mPackagesShownInSession.clear();
+        }
+    };
+
     /** Record of force resized task that's pending to be handled. */
     private class PendingTaskRecord {
         int taskId;
@@ -67,7 +75,7 @@
         }
     }
 
-    public ForcedResizableInfoActivityController(Context context) {
+    public ForcedResizableInfoActivityController(Context context, Divider divider) {
         mContext = context;
         ActivityManagerWrapper.getInstance().registerTaskStackListener(
                 new TaskStackChangeListener() {
@@ -87,12 +95,7 @@
                         activityLaunchOnSecondaryDisplayFailed();
                     }
                 });
-    }
-
-    public void notifyDockedStackExistsChanged(boolean exists) {
-        if (!exists) {
-            mPackagesShownInSession.clear();
-        }
+        divider.registerInSplitScreenListener(mDockedStackExistsListener);
     }
 
     public void onAppTransitionFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
new file mode 100644
index 0000000..b19f560
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stackdivider;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.TypedValue;
+import android.view.WindowContainerTransaction;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.wm.DisplayLayout;
+
+/**
+ * Handles split-screen related internal display layout. In general, this represents the
+ * WM-facing understanding of the splits.
+ */
+public class SplitDisplayLayout {
+    /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
+     * restrict IME adjustment so that a min portion of top stack remains visible.*/
+    private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
+
+    private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
+
+    SplitScreenTaskOrganizer mTiles;
+    DisplayLayout mDisplayLayout;
+    Context mContext;
+
+    // Lazy stuff
+    boolean mResourcesValid = false;
+    int mDividerSize;
+    int mDividerSizeInactive;
+    private DividerSnapAlgorithm mSnapAlgorithm = null;
+    private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null;
+    Rect mPrimary = null;
+    Rect mSecondary = null;
+    Rect mAdjustedPrimary = null;
+    Rect mAdjustedSecondary = null;
+
+    public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskOrganizer taskTiles) {
+        mTiles = taskTiles;
+        mDisplayLayout = dl;
+        mContext = ctx;
+    }
+
+    void rotateTo(int newRotation) {
+        mDisplayLayout.rotateTo(mContext.getResources(), newRotation);
+        final Configuration config = new Configuration();
+        config.unset();
+        config.orientation = mDisplayLayout.getOrientation();
+        Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+        tmpRect.inset(mDisplayLayout.nonDecorInsets());
+        config.windowConfiguration.setAppBounds(tmpRect);
+        tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+        tmpRect.inset(mDisplayLayout.stableInsets());
+        config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density());
+        config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density());
+        mContext = mContext.createConfigurationContext(config);
+        mSnapAlgorithm = null;
+        mMinimizedSnapAlgorithm = null;
+        mResourcesValid = false;
+    }
+
+    private void updateResources() {
+        if (mResourcesValid) {
+            return;
+        }
+        mResourcesValid = true;
+        Resources res = mContext.getResources();
+        mDividerSize = DockedDividerUtils.getDividerSize(res,
+                DockedDividerUtils.getDividerInsets(res));
+        mDividerSizeInactive = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics());
+    }
+
+    int getPrimarySplitSide() {
+        return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
+    }
+
+    boolean isMinimized() {
+        return mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_HOME
+                || mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS;
+    }
+
+    DividerSnapAlgorithm getSnapAlgorithm() {
+        if (mSnapAlgorithm == null) {
+            updateResources();
+            boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
+            mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
+                    mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
+                    isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide());
+        }
+        return mSnapAlgorithm;
+    }
+
+    DividerSnapAlgorithm getMinimizedSnapAlgorithm() {
+        if (mMinimizedSnapAlgorithm == null) {
+            updateResources();
+            boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
+            mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
+                    mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
+                    isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(),
+                    true /* isMinimized */);
+        }
+        return mMinimizedSnapAlgorithm;
+    }
+
+    void resizeSplits(int position) {
+        mPrimary = mPrimary == null ? new Rect() : mPrimary;
+        mSecondary = mSecondary == null ? new Rect() : mSecondary;
+        calcSplitBounds(position, mPrimary, mSecondary);
+    }
+
+    void resizeSplits(int position, WindowContainerTransaction t) {
+        resizeSplits(position);
+        t.setBounds(mTiles.mPrimary.token, mPrimary);
+        t.setBounds(mTiles.mSecondary.token, mSecondary);
+
+        t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
+                getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
+        t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
+                getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
+    }
+
+    void calcSplitBounds(int position, @NonNull Rect outPrimary, @NonNull Rect outSecondary) {
+        int dockSide = getPrimarySplitSide();
+        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outPrimary,
+                mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
+
+        DockedDividerUtils.calculateBoundsForPosition(position,
+                DockedDividerUtils.invertDockSide(dockSide), outSecondary, mDisplayLayout.width(),
+                mDisplayLayout.height(), mDividerSize);
+    }
+
+    Rect calcMinimizedHomeStackBounds() {
+        DividerSnapAlgorithm.SnapTarget miniMid = getMinimizedSnapAlgorithm().getMiddleTarget();
+        Rect homeBounds = new Rect();
+        DockedDividerUtils.calculateBoundsForPosition(miniMid.position,
+                DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds,
+                mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
+        return homeBounds;
+    }
+
+    /**
+     * Updates the adjustment depending on it's current state.
+     */
+    void updateAdjustedBounds(int currImeTop, int startTop, int finalTop) {
+        updateAdjustedBounds(mDisplayLayout, currImeTop, startTop, finalTop, mDividerSize,
+                mDividerSizeInactive, mPrimary, mSecondary);
+    }
+
+    /**
+     * Updates the adjustment depending on it's current state.
+     */
+    private void updateAdjustedBounds(DisplayLayout dl, int currImeTop, int startTop, int finalTop,
+            int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
+        adjustForIME(dl, currImeTop, startTop, finalTop, dividerWidth, dividerWidthInactive,
+                primaryBounds, secondaryBounds);
+    }
+
+    /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */
+    private void adjustForIME(DisplayLayout dl, int currImeTop, int startTop, int finalTop,
+            int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
+        if (mAdjustedPrimary == null) {
+            mAdjustedPrimary = new Rect();
+            mAdjustedSecondary = new Rect();
+        }
+
+        final Rect displayStableRect = new Rect();
+        dl.getStableBounds(displayStableRect);
+
+        final boolean showing = finalTop < startTop;
+        final float progress = ((float) (currImeTop - startTop)) / (finalTop - startTop);
+        final float dividerSquish = showing ? progress : 1.f - progress;
+        final int currDividerWidth =
+                (int) (dividerWidthInactive * dividerSquish + dividerWidth * (1.f - dividerSquish));
+
+        final int minTopStackBottom = displayStableRect.top
+                + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN);
+        final int minImeTop = minTopStackBottom + currDividerWidth;
+
+        // Calculate an offset which shifts the stacks up by the height of the IME, but still
+        // leaves at least 30% of the top stack visible.
+        final int yOffset = Math.max(0, dl.height() - Math.max(currImeTop, minImeTop));
+
+        // TOP
+        // Reduce the offset by an additional small amount to squish the divider bar.
+        mAdjustedPrimary.set(primaryBounds);
+        mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth));
+
+        // BOTTOM
+        mAdjustedSecondary.set(secondaryBounds);
+        mAdjustedSecondary.offset(0, -yOffset);
+    }
+
+    static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl,
+            Rect bounds) {
+        int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(),
+                DockedDividerUtils.getDividerInsets(context.getResources()));
+
+        int minWidth = Integer.MAX_VALUE;
+
+        // Go through all screen orientations and find the orientation in which the task has the
+        // smallest width.
+        Rect tmpRect = new Rect();
+        Rect rotatedDisplayRect = new Rect();
+        Rect displayRect = new Rect(0, 0, dl.width(), dl.height());
+
+        DisplayLayout tmpDL = new DisplayLayout();
+        for (int rotation = 0; rotation < 4; rotation++) {
+            tmpDL.set(dl);
+            tmpDL.rotateTo(context.getResources(), rotation);
+            DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize);
+
+            tmpRect.set(bounds);
+            DisplayLayout.rotateBounds(tmpRect, displayRect, rotation - dl.rotation());
+            rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height());
+            final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect,
+                    tmpDL.getOrientation());
+            final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide,
+                    dividerSize);
+
+            final int snappedPosition =
+                    snap.calculateNonDismissingSnapTarget(position).position;
+            DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect,
+                    tmpDL.width(), tmpDL.height(), dividerSize);
+            Rect insettedDisplay = new Rect(rotatedDisplayRect);
+            insettedDisplay.inset(tmpDL.stableInsets());
+            tmpRect.intersect(insettedDisplay);
+            minWidth = Math.min(tmpRect.width(), minWidth);
+        }
+        return (int) (minWidth / dl.density());
+    }
+
+    static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl,
+            int dividerSize) {
+        final Configuration config = new Configuration();
+        config.unset();
+        config.orientation = dl.getOrientation();
+        Rect tmpRect = new Rect(0, 0, dl.width(), dl.height());
+        tmpRect.inset(dl.nonDecorInsets());
+        config.windowConfiguration.setAppBounds(tmpRect);
+        tmpRect.set(0, 0, dl.width(), dl.height());
+        tmpRect.inset(dl.stableInsets());
+        config.screenWidthDp = (int) (tmpRect.width() / dl.density());
+        config.screenHeightDp = (int) (tmpRect.height() / dl.density());
+        final Context rotationContext = context.createConfigurationContext(config);
+        return new DividerSnapAlgorithm(
+                rotationContext.getResources(), dl.width(), dl.height(), dividerSize,
+                config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets());
+    }
+
+    /**
+     * Get the current primary-split side. Determined by its location of {@param bounds} within
+     * {@param displayRect} but if both are the same, it will try to dock to each side and determine
+     * if allowed in its respected {@param orientation}.
+     *
+     * @param bounds bounds of the primary split task to get which side is docked
+     * @param displayRect bounds of the display that contains the primary split task
+     * @param orientation the origination of device
+     * @return current primary-split side
+     */
+    static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) {
+        if (orientation == ORIENTATION_PORTRAIT) {
+            // Portrait mode, docked either at the top or the bottom.
+            final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top);
+            if (diff < 0) {
+                return DOCKED_BOTTOM;
+            } else {
+                // Top is default
+                return DOCKED_TOP;
+            }
+        } else if (orientation == ORIENTATION_LANDSCAPE) {
+            // Landscape mode, docked either on the left or on the right.
+            final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left);
+            if (diff < 0) {
+                return DOCKED_RIGHT;
+            }
+            return DOCKED_LEFT;
+        }
+        return DOCKED_INVALID;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
new file mode 100644
index 0000000..0a54d2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stackdivider;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ITaskOrganizerController;
+import android.app.WindowConfiguration;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pools;
+import android.view.Display;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub {
+    private static final String TAG = "SplitScreenTaskOrganizer";
+    private static final boolean DEBUG = Divider.DEBUG;
+
+    RunningTaskInfo mPrimary;
+    RunningTaskInfo mSecondary;
+    SurfaceControl mPrimarySurface;
+    SurfaceControl mSecondarySurface;
+    SurfaceControl mPrimaryDim;
+    SurfaceControl mSecondaryDim;
+    final Divider mDivider;
+
+    private final Pools.SynchronizedPool<SurfaceControl.Transaction> mTransactionPool =
+            new Pools.SynchronizedPool<>(4);
+
+    SplitScreenTaskOrganizer(Divider divider) {
+        mDivider = divider;
+    }
+
+    void init(ITaskOrganizerController organizerController, SurfaceSession session)
+            throws RemoteException {
+        organizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        organizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mPrimary = organizerController.createRootTask(Display.DEFAULT_DISPLAY,
+                WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mSecondary = organizerController.createRootTask(Display.DEFAULT_DISPLAY,
+                WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mPrimarySurface = mPrimary.token.getLeash();
+        mSecondarySurface = mSecondary.token.getLeash();
+
+        // Initialize dim surfaces:
+        mPrimaryDim = new SurfaceControl.Builder(session).setParent(mPrimarySurface)
+                .setColorLayer().setName("Primary Divider Dim").build();
+        mSecondaryDim = new SurfaceControl.Builder(session).setParent(mSecondarySurface)
+                .setColorLayer().setName("Secondary Divider Dim").build();
+        SurfaceControl.Transaction t = getTransaction();
+        t.setLayer(mPrimaryDim, Integer.MAX_VALUE);
+        t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f});
+        t.setLayer(mSecondaryDim, Integer.MAX_VALUE);
+        t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
+        t.apply();
+        releaseTransaction(t);
+    }
+
+    SurfaceControl.Transaction getTransaction() {
+        SurfaceControl.Transaction t = mTransactionPool.acquire();
+        if (t == null) {
+            return new SurfaceControl.Transaction();
+        }
+        return t;
+    }
+
+    void releaseTransaction(SurfaceControl.Transaction t) {
+        mTransactionPool.release(t);
+    }
+
+    @Override
+    public void taskAppeared(RunningTaskInfo taskInfo) {
+    }
+
+    @Override
+    public void taskVanished(IWindowContainer container) {
+    }
+
+    @Override
+    public void transactionReady(int id, SurfaceControl.Transaction t) {
+    }
+
+    @Override
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        if (taskInfo.displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+        mDivider.getHandler().post(() -> handleTaskInfoChanged(taskInfo));
+    }
+
+    /**
+     * This is effectively a finite state machine which moves between the various split-screen
+     * presentations based on the contents of the split regions.
+     */
+    private void handleTaskInfoChanged(RunningTaskInfo info) {
+        final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+        final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+        if (info.token.asBinder() == mPrimary.token.asBinder()) {
+            mPrimary = info;
+        } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
+            mSecondary = info;
+        }
+        final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+        final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+        if (DEBUG) {
+            Log.d(TAG, "onTaskInfoChanged " + mPrimary + "  " + mSecondary);
+        }
+        if (primaryIsEmpty || secondaryIsEmpty) {
+            // At-least one of the splits is empty which means we are currently transitioning
+            // into or out-of split-screen mode.
+            if (DEBUG) {
+                Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
+                        + "  " + mSecondary.topActivityType);
+            }
+            if (mDivider.inSplitMode()) {
+                // Was in split-mode, which means we are leaving split, so continue that.
+                // This happens when the stack in the primary-split is dismissed.
+                if (DEBUG) {
+                    Log.d(TAG, "    was in split, so this means leave it "
+                            + mPrimary.topActivityType + "  " + mSecondary.topActivityType);
+                }
+                WindowManagerProxy.applyDismissSplit(this, true /* dismissOrMaximize */);
+                mDivider.updateVisibility(false /* visible */);
+            } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
+                // Wasn't in split-mode (both were empty), but now that the primary split is
+                // populated, we should fully enter split by moving everything else into secondary.
+                // This just tells window-manager to reparent things, the UI will respond
+                // when it gets new task info for the secondary split.
+                if (DEBUG) {
+                    Log.d(TAG, "   was not in split, but primary is populated, so enter it");
+                }
+                mDivider.startEnterSplit();
+            }
+        } else if (mSecondary.topActivityType == ACTIVITY_TYPE_HOME
+                || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS) {
+            // Both splits are populated but the secondary split has a home/recents stack on top,
+            // so enter minimized mode.
+            mDivider.ensureMinimizedSplit();
+        } else {
+            // Both splits are populated by normal activities, so make sure we aren't minimized.
+            mDivider.ensureNormalSplit();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 228aab5..7685733 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,16 +16,25 @@
 
 package com.android.systemui.stackdivider;
 
-import static android.view.WindowManager.DOCKED_INVALID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.Display;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -35,88 +44,20 @@
 public class WindowManagerProxy {
 
     private static final String TAG = "WindowManagerProxy";
+    private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
 
     private static final WindowManagerProxy sInstance = new WindowManagerProxy();
 
     @GuardedBy("mDockedRect")
     private final Rect mDockedRect = new Rect();
-    private final Rect mTempDockedTaskRect = new Rect();
-    private final Rect mTempDockedInsetRect = new Rect();
-    private final Rect mTempOtherTaskRect = new Rect();
-    private final Rect mTempOtherInsetRect = new Rect();
 
     private final Rect mTmpRect1 = new Rect();
-    private final Rect mTmpRect2 = new Rect();
-    private final Rect mTmpRect3 = new Rect();
-    private final Rect mTmpRect4 = new Rect();
-    private final Rect mTmpRect5 = new Rect();
 
     @GuardedBy("mDockedRect")
     private final Rect mTouchableRegion = new Rect();
 
-    private boolean mDimLayerVisible;
-    private int mDimLayerTargetWindowingMode;
-    private float mDimLayerAlpha;
-
     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
 
-    private final Runnable mResizeRunnable = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mDockedRect) {
-                mTmpRect1.set(mDockedRect);
-                mTmpRect2.set(mTempDockedTaskRect);
-                mTmpRect3.set(mTempDockedInsetRect);
-                mTmpRect4.set(mTempOtherTaskRect);
-                mTmpRect5.set(mTempOtherInsetRect);
-            }
-            try {
-                ActivityTaskManager.getService()
-                        .resizeDockedStack(mTmpRect1,
-                                mTmpRect2.isEmpty() ? null : mTmpRect2,
-                                mTmpRect3.isEmpty() ? null : mTmpRect3,
-                                mTmpRect4.isEmpty() ? null : mTmpRect4,
-                                mTmpRect5.isEmpty() ? null : mTmpRect5);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    };
-
-    private final Runnable mDismissRunnable = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                ActivityTaskManager.getService().dismissSplitScreenMode(false /* onTop */);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to remove stack: " + e);
-            }
-        }
-    };
-
-    private final Runnable mMaximizeRunnable = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                ActivityTaskManager.getService().dismissSplitScreenMode(true /* onTop */);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    };
-
-    private final Runnable mDimLayerRunnable = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
-                        mDimLayerTargetWindowingMode, mDimLayerAlpha);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    };
-
     private final Runnable mSetTouchableRegionRunnable = new Runnable() {
         @Override
         public void run() {
@@ -139,40 +80,9 @@
         return sInstance;
     }
 
-    public void resizeDockedStack(Rect docked, Rect tempDockedTaskRect, Rect tempDockedInsetRect,
-            Rect tempOtherTaskRect, Rect tempOtherInsetRect) {
-        synchronized (mDockedRect) {
-            mDockedRect.set(docked);
-            if (tempDockedTaskRect != null) {
-                mTempDockedTaskRect.set(tempDockedTaskRect);
-            } else {
-                mTempDockedTaskRect.setEmpty();
-            }
-            if (tempDockedInsetRect != null) {
-                mTempDockedInsetRect.set(tempDockedInsetRect);
-            } else {
-                mTempDockedInsetRect.setEmpty();
-            }
-            if (tempOtherTaskRect != null) {
-                mTempOtherTaskRect.set(tempOtherTaskRect);
-            } else {
-                mTempOtherTaskRect.setEmpty();
-            }
-            if (tempOtherInsetRect != null) {
-                mTempOtherInsetRect.set(tempOtherInsetRect);
-            } else {
-                mTempOtherInsetRect.setEmpty();
-            }
-        }
-        mExecutor.execute(mResizeRunnable);
-    }
-
-    public void dismissDockedStack() {
-        mExecutor.execute(mDismissRunnable);
-    }
-
-    public void maximizeDockedStack() {
-        mExecutor.execute(mMaximizeRunnable);
+    void dismissOrMaximizeDocked(
+            final SplitScreenTaskOrganizer tiles, final boolean dismissOrMaximize) {
+        mExecutor.execute(() -> applyDismissSplit(tiles, dismissOrMaximize));
     }
 
     public void setResizing(final boolean resizing) {
@@ -188,26 +98,204 @@
         });
     }
 
-    public int getDockSide() {
-        try {
-            return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get dock side: " + e);
-        }
-        return DOCKED_INVALID;
-    }
-
-    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
-        mDimLayerVisible = visible;
-        mDimLayerTargetWindowingMode = targetWindowingMode;
-        mDimLayerAlpha = alpha;
-        mExecutor.execute(mDimLayerRunnable);
-    }
-
+    /** Sets a touch region */
     public void setTouchRegion(Rect region) {
         synchronized (mDockedRect) {
             mTouchableRegion.set(region);
         }
         mExecutor.execute(mSetTouchableRegionRunnable);
     }
+
+    static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
+        WindowContainerTransaction t = new WindowContainerTransaction();
+        splitLayout.resizeSplits(position, t);
+        try {
+            ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(t,
+                    null /* organizer */);
+        } catch (RemoteException e) {
+        }
+    }
+
+    private static boolean getHomeAndRecentsTasks(List<IWindowContainer> out,
+            IWindowContainer parent) {
+        boolean resizable = false;
+        try {
+            List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
+                    ? ActivityTaskManager.getTaskOrganizerController().getRootTasks(
+                            Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
+                    : ActivityTaskManager.getTaskOrganizerController().getChildTasks(parent,
+                            HOME_AND_RECENTS);
+            for (int i = 0, n = rootTasks.size(); i < n; ++i) {
+                final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
+                out.add(ti.token);
+                if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
+                    resizable = ti.isResizable();
+                }
+            }
+        } catch (RemoteException e) {
+        }
+        return resizable;
+    }
+
+    static void applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent) {
+        applyHomeTasksMinimized(layout, parent, null /* transaction */);
+    }
+
+    /**
+     * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
+     * split is minimized. This actually "sticks out" of the secondary split area, but when in
+     * minimized mode, the secondary split gets a 'negative' crop to expose it.
+     */
+    static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent,
+            WindowContainerTransaction t) {
+        // Resize the home/recents stacks to the larger minimized-state size
+        final Rect homeBounds;
+        final ArrayList<IWindowContainer> homeStacks = new ArrayList<>();
+        boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
+        if (isHomeResizable) {
+            homeBounds = layout.calcMinimizedHomeStackBounds();
+        } else {
+            homeBounds = new Rect(0, 0, layout.mDisplayLayout.width(),
+                    layout.mDisplayLayout.height());
+        }
+        WindowContainerTransaction wct = t != null ? t : new WindowContainerTransaction();
+        for (int i = homeStacks.size() - 1; i >= 0; --i) {
+            wct.setBounds(homeStacks.get(i), homeBounds);
+        }
+        if (t != null) {
+            return isHomeResizable;
+        }
+        try {
+            ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct,
+                    null /* organizer */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to resize home stacks ", e);
+        }
+        return isHomeResizable;
+    }
+
+    /**
+     * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
+     * This assumes there is already something in the primary split since that is usually what
+     * triggers a call to this. In the same transaction, this overrides the home task bounds via
+     * {@link #applyHomeTasksMinimized}.
+     *
+     * @return whether the home stack is resizable
+     */
+    static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
+        try {
+            // Set launchtile first so that any stack created after
+            // getAllStackInfos and before reparent (even if unlikely) are placed
+            // correctly.
+            ActivityTaskManager.getTaskOrganizerController().setLaunchRoot(
+                    DEFAULT_DISPLAY, tiles.mSecondary.token);
+            List<ActivityManager.RunningTaskInfo> rootTasks =
+                    ActivityTaskManager.getTaskOrganizerController().getRootTasks(DEFAULT_DISPLAY,
+                            null /* activityTypes */);
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            if (rootTasks.isEmpty()) {
+                return false;
+            }
+            for (int i = rootTasks.size() - 1; i >= 0; --i) {
+                if (rootTasks.get(i).configuration.windowConfiguration.getWindowingMode()
+                        != WINDOWING_MODE_FULLSCREEN) {
+                    continue;
+                }
+                wct.reparent(rootTasks.get(i).token, tiles.mSecondary.token,
+                        true /* onTop */);
+            }
+            boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
+            ActivityTaskManager.getTaskOrganizerController()
+                    .applyContainerTransaction(wct, null /* organizer */);
+            return isHomeResizable;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error moving fullscreen tasks to secondary split: " + e);
+        }
+        return false;
+    }
+
+    private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
+        final int atype = ti.configuration.windowConfiguration.getActivityType();
+        return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
+    }
+
+    /**
+     * Reparents all tile members back to their display and resets home task override bounds.
+     * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
+     *                          split (thus resulting in the top of the secondary split becoming
+     *                          fullscreen. {@code false} resolves the other way.
+     */
+    static void applyDismissSplit(SplitScreenTaskOrganizer tiles, boolean dismissOrMaximize) {
+        try {
+            // Set launch root first so that any task created after getChildContainers and
+            // before reparent (pretty unlikely) are put into fullscreen.
+            ActivityTaskManager.getTaskOrganizerController().setLaunchRoot(Display.DEFAULT_DISPLAY,
+                    null);
+            // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
+            //                 plus specific APIs to clean this up.
+            List<ActivityManager.RunningTaskInfo> primaryChildren =
+                    ActivityTaskManager.getTaskOrganizerController().getChildTasks(
+                            tiles.mPrimary.token, null /* activityTypes */);
+            List<ActivityManager.RunningTaskInfo> secondaryChildren =
+                    ActivityTaskManager.getTaskOrganizerController().getChildTasks(
+                            tiles.mSecondary.token, null /* activityTypes */);
+            // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
+            // as a result, the above will not capture any tasks; yet, we need to clean-up the
+            // home task bounds.
+            List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
+                    ActivityTaskManager.getTaskOrganizerController().getRootTasks(
+                            Display.DEFAULT_DISPLAY, HOME_AND_RECENTS);
+            if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
+                    && freeHomeAndRecents.isEmpty()) {
+                return;
+            }
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            if (dismissOrMaximize) {
+                // Dismissing, so move all primary split tasks first
+                for (int i = primaryChildren.size() - 1; i >= 0; --i) {
+                    wct.reparent(primaryChildren.get(i).token, null /* parent */,
+                            true /* onTop */);
+                }
+                // Don't need to worry about home tasks because they are already in the "proper"
+                // order within the secondary split.
+                for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+                    final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+                    wct.reparent(ti.token, null /* parent */, true /* onTop */);
+                    if (isHomeOrRecentTask(ti)) {
+                        wct.setBounds(ti.token, null);
+                    }
+                }
+            } else {
+                // Maximize, so move non-home secondary split first
+                for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+                    if (isHomeOrRecentTask(secondaryChildren.get(i))) {
+                        continue;
+                    }
+                    wct.reparent(secondaryChildren.get(i).token, null /* parent */,
+                            true /* onTop */);
+                }
+                // Find and place home tasks in-between. This simulates the fact that there was
+                // nothing behind the primary split's tasks.
+                for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+                    final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+                    if (isHomeOrRecentTask(ti)) {
+                        wct.reparent(ti.token, null /* parent */, true /* onTop */);
+                        // reset bounds too
+                        wct.setBounds(ti.token, null);
+                    }
+                }
+                for (int i = primaryChildren.size() - 1; i >= 0; --i) {
+                    wct.reparent(primaryChildren.get(i).token, null /* parent */,
+                            true /* onTop */);
+                }
+            }
+            for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
+                wct.setBounds(freeHomeAndRecents.get(i).token, null);
+            }
+            ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct,
+                    null /* organizer */);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to remove stack: " + e);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 4d6764e..b43fe73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,20 +31,17 @@
 import android.util.Log;
 
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * This class handles listening to notification updates and passing them along to
  * NotificationPresenter to be displayed to the user.
  */
 @SuppressLint("OverrideAbstract")
-@Singleton
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
 
@@ -54,7 +51,9 @@
     private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
     private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
 
-    @Inject
+    /**
+     * Injected constructor. See {@link StatusBarModule}.
+     */
     public NotificationListener(
             Context context,
             NotificationManager notificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index a457266..d0af106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -53,6 +53,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -73,16 +74,12 @@
 import java.util.List;
 import java.util.Set;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 
 /**
  * Handles tasks and state related to media notifications. For example, there is a 'current' media
  * notification, which this class keeps track of.
  */
-@Singleton
 public class NotificationMediaManager implements Dumpable {
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
@@ -101,11 +98,10 @@
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
     }
 
-
     private final NotificationEntryManager mEntryManager;
 
     @Nullable
-    private Lazy<NotificationShadeWindowController> mStatusBarWindowController;
+    private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
 
     @Nullable
     private BiometricUnlockController mBiometricUnlockController;
@@ -176,11 +172,13 @@
         }
     };
 
-    @Inject
+    /**
+     * Injected constructor. See {@link StatusBarModule}.
+     */
     public NotificationMediaManager(
             Context context,
             Lazy<StatusBar> statusBarLazy,
-            Lazy<NotificationShadeWindowController> statusBarWindowController,
+            Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController) {
@@ -194,7 +192,7 @@
                 Context.MEDIA_SESSION_SERVICE);
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBarLazy = statusBarLazy;
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mEntryManager = notificationEntryManager;
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
@@ -525,7 +523,8 @@
             }
         }
 
-        NotificationShadeWindowController windowController = mStatusBarWindowController.get();
+        NotificationShadeWindowController windowController =
+                mNotificationShadeWindowController.get();
         boolean hideBecauseOccluded = mStatusBarLazy.get().isOccluded();
 
         final boolean hasArtwork = artworkDrawable != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f3783c8..ebc2fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -70,9 +71,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 
 /**
@@ -81,7 +79,6 @@
  * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
  * and handling clicks on remote views.
  */
-@Singleton
 public class NotificationRemoteInputManager implements Dumpable {
     public static final boolean ENABLE_REMOTE_INPUT =
             SystemProperties.getBoolean("debug.enable_remote_input", true);
@@ -257,7 +254,9 @@
         }
     };
 
-    @Inject
+    /**
+     * Injected constructor. See {@link StatusBarModule}.
+     */
     public NotificationRemoteInputManager(
             Context context,
             NotificationLockscreenUserManager lockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 37f9f88..7e70c20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -29,6 +29,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -46,9 +47,6 @@
 import java.util.List;
 import java.util.Stack;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
  * on their group structure. For example, if a notification becomes bundled with another,
@@ -56,7 +54,6 @@
  * tell NotificationListContainer which notifications to display, and inform it of changes to those
  * notifications that might affect their display.
  */
-@Singleton
 public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
     private static final String TAG = "NotificationViewHierarchyManager";
 
@@ -94,8 +91,12 @@
     // the problem.
     private boolean mIsHandleDynamicPrivacyChangeScheduled;
 
-    @Inject
-    public NotificationViewHierarchyManager(Context context, @Main Handler mainHandler,
+    /**
+     * Injected constructor. See {@link StatusBarModule}.
+     */
+    public NotificationViewHierarchyManager(
+            Context context,
+            @Main Handler mainHandler,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
@@ -104,8 +105,7 @@
             KeyguardBypassController bypassController,
             BubbleController bubbleController,
             DynamicPrivacyController privacyController,
-            ForegroundServiceSectionController fgsSectionController
-    ) {
+            ForegroundServiceSectionController fgsSectionController) {
         mContext = context;
         mHandler = mainHandler;
         mLockscreenUserManager = notificationLockscreenUserManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScreenRecordDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScreenRecordDrawable.java
new file mode 100644
index 0000000..44ef6b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScreenRecordDrawable.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.util.AttributeSet;
+
+import com.android.systemui.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * The screen record drawable draws a colored background and either a countdown or circle to
+ * indicate that the screen is being recorded.
+ */
+public class ScreenRecordDrawable extends DrawableWrapper {
+    private Drawable mFillDrawable;
+    private int mHorizontalPadding;
+    private int mLevel;
+    private float mTextSize;
+    private float mIconRadius;
+    private Paint mPaint;
+
+    /** No-arg constructor used by drawable inflation. */
+    public ScreenRecordDrawable() {
+        super(null);
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+        setDrawable(r.getDrawable(R.drawable.ic_screen_record_background, theme).mutate());
+        mFillDrawable = r.getDrawable(R.drawable.ic_screen_record_background, theme).mutate();
+        mHorizontalPadding = r.getDimensionPixelSize(R.dimen.status_bar_horizontal_padding);
+
+        mTextSize = r.getDimensionPixelSize(R.dimen.screenrecord_status_text_size);
+        mIconRadius = r.getDimensionPixelSize(R.dimen.screenrecord_status_icon_radius);
+        mLevel = attrs.getAttributeIntValue(null, "level", 0);
+
+        mPaint = new Paint();
+        mPaint.setTextAlign(Paint.Align.CENTER);
+        mPaint.setColor(Color.WHITE);
+        mPaint.setTextSize(mTextSize);
+        mPaint.setFakeBoldText(true);
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mFillDrawable.canApplyTheme() || super.canApplyTheme();
+    }
+
+    @Override
+    public void applyTheme(Resources.Theme t) {
+        super.applyTheme(t);
+        mFillDrawable.applyTheme(t);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mFillDrawable.setBounds(bounds);
+    }
+
+    @Override
+    public boolean onLayoutDirectionChanged(int layoutDirection) {
+        mFillDrawable.setLayoutDirection(layoutDirection);
+        return super.onLayoutDirectionChanged(layoutDirection);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        mFillDrawable.draw(canvas);
+
+        Rect b = mFillDrawable.getBounds();
+        if (mLevel > 0) {
+            String val = String.valueOf(mLevel);
+            Rect textBounds = new Rect();
+            mPaint.getTextBounds(val, 0, val.length(), textBounds);
+            float yOffset = textBounds.height() / 4; // half, and half again since it's centered
+            canvas.drawText(val, b.centerX(), b.centerY() + yOffset, mPaint);
+        } else {
+            canvas.drawCircle(b.centerX(), b.centerY() - mIconRadius / 2, mIconRadius, mPaint);
+        }
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        padding.left += mHorizontalPadding;
+        padding.right += mHorizontalPadding;
+        padding.top = 0;
+        padding.bottom = 0;
+        android.util.Log.d("ScreenRecordDrawable", "set zero top/bottom pad");
+        return true;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        super.setAlpha(alpha);
+        mFillDrawable.setAlpha(alpha);
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        mFillDrawable.setVisible(visible, restart);
+        return super.setVisible(visible, restart);
+    }
+
+    @Override
+    public Drawable mutate() {
+        mFillDrawable.mutate();
+        return super.mutate();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 40f8e39..bb96f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -21,27 +21,26 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 
 import java.util.Set;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Handles when smart replies are added to a notification
  * and clicked upon.
  */
-@Singleton
 public class SmartReplyController {
     private final IStatusBarService mBarService;
     private final NotificationEntryManager mEntryManager;
     private Set<String> mSendingKeys = new ArraySet<>();
     private Callback mCallback;
 
-    @Inject
+    /**
+     * Injected constructor. See {@link StatusBarModule}.
+     */
     public SmartReplyController(NotificationEntryManager entryManager,
             IStatusBarService statusBarService) {
         mBarService = statusBarService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
deleted file mode 100644
index 493482a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-
-import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.tracing.ProtoTracer;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger Module providing common dependencies of StatusBar.
- */
-@Module(includes = {NotificationRowModule.class})
-public class StatusBarDependenciesModule {
-    /**
-     * Provides our instance of CommandQueue which is considered optional.
-     */
-    @Provides
-    @Singleton
-    public CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
-        return new CommandQueue(context, protoTracer);
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
new file mode 100644
index 0000000..0b37c22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.dagger;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.MediaArtworkProcessor;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
+import com.android.systemui.tracing.ProtoTracer;
+
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * This module provides instances needed to construct {@link StatusBar}. These are moved to this
+ * separate from {@link StatusBarModule} module so that components that wish to build their own
+ * version of StatusBar can include just dependencies, without injecting StatusBar itself.
+ */
+@Module
+public interface StatusBarDependenciesModule {
+    /** */
+    @Singleton
+    @Provides
+    static NotificationRemoteInputManager provideNotificationRemoteInputManager(
+            Context context,
+            NotificationLockscreenUserManager lockscreenUserManager,
+            SmartReplyController smartReplyController,
+            NotificationEntryManager notificationEntryManager,
+            Lazy<StatusBar> statusBarLazy,
+            StatusBarStateController statusBarStateController,
+            Handler mainHandler,
+            RemoteInputUriController remoteInputUriController) {
+        return new NotificationRemoteInputManager(
+                context,
+                lockscreenUserManager,
+                smartReplyController,
+                notificationEntryManager,
+                statusBarLazy,
+                statusBarStateController,
+                mainHandler,
+                remoteInputUriController);
+    }
+
+    /** */
+    @Singleton
+    @Provides
+    static NotificationMediaManager provideNotificationMediaManager(
+            Context context,
+            Lazy<StatusBar> statusBarLazy,
+            Lazy<NotificationShadeWindowController> notificationShadeWindowController,
+            NotificationEntryManager notificationEntryManager,
+            MediaArtworkProcessor mediaArtworkProcessor,
+            KeyguardBypassController keyguardBypassController) {
+        return new NotificationMediaManager(
+                context,
+                statusBarLazy,
+                notificationShadeWindowController,
+                notificationEntryManager,
+                mediaArtworkProcessor,
+                keyguardBypassController);
+    }
+
+    /** */
+    @Singleton
+    @Provides
+    static NotificationListener provideNotificationListener(
+            Context context,
+            NotificationManager notificationManager,
+            @Main Handler mainHandler) {
+        return new NotificationListener(
+                context, notificationManager, mainHandler);
+    }
+
+    /** */
+    @Singleton
+    @Provides
+    static SmartReplyController provideSmartReplyController(
+            NotificationEntryManager entryManager, IStatusBarService statusBarService) {
+        return new SmartReplyController(entryManager, statusBarService);
+    }
+
+    /** */
+    @Singleton
+    @Provides
+    static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
+            Context context,
+            @Main Handler mainHandler,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationGroupManager groupManager,
+            VisualStabilityManager visualStabilityManager,
+            StatusBarStateController statusBarStateController,
+            NotificationEntryManager notificationEntryManager,
+            KeyguardBypassController bypassController,
+            BubbleController bubbleController,
+            DynamicPrivacyController privacyController,
+            ForegroundServiceSectionController fgsSectionController) {
+        return new NotificationViewHierarchyManager(
+                context,
+                mainHandler,
+                notificationLockscreenUserManager,
+                groupManager,
+                visualStabilityManager,
+                statusBarStateController,
+                notificationEntryManager,
+                bypassController,
+                bubbleController,
+                privacyController,
+                fgsSectionController);
+    }
+
+    /**
+     * Provides our instance of CommandQueue which is considered optional.
+     */
+    @Provides
+    @Singleton
+    static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
+        return new CommandQueue(context, protoTracer);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.java
new file mode 100644
index 0000000..ad5ef20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.dagger;
+
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
+
+import dagger.Module;
+
+/** */
+@Module(includes = {StatusBarPhoneModule.class, StatusBarDependenciesModule.class,
+        NotificationsModule.class, NotificationRowModule.class})
+public interface StatusBarModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 93f5805..55a20fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -51,10 +51,10 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.Dependency;
-import com.android.systemui.DockedStackExistsListener;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.NotificationChannels;
@@ -80,11 +80,13 @@
     private final CommandQueue mCommandQueue;
     private boolean mDockedStackExists;
     private KeyguardStateController mKeyguardStateController;
+    private final Divider mDivider;
 
     @Inject
     public InstantAppNotifier(Context context, CommandQueue commandQueue,
-            @UiBackground Executor uiBgExecutor) {
+            @UiBackground Executor uiBgExecutor, Divider divider) {
         super(context);
+        mDivider = divider;
         mCommandQueue = commandQueue;
         mUiBgExecutor = uiBgExecutor;
     }
@@ -103,7 +105,7 @@
         mCommandQueue.addCallback(this);
         mKeyguardStateController.addCallback(this);
 
-        DockedStackExistsListener.register(
+        mDivider.registerInSplitScreenListener(
                 exists -> {
                     mDockedStackExists = exists;
                     updateForegroundInstantApps();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index d0e238a..72a7e11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -28,13 +28,10 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /** Handles heads-up and pulsing behavior driven by notification changes. */
-@Singleton
 public class NotificationAlertingManager {
 
     private static final String TAG = "NotifAlertManager";
@@ -47,7 +44,9 @@
 
     private HeadsUpManager mHeadsUpManager;
 
-    @Inject
+    /**
+     * Injected constructor. See {@link NotificationsModule}.
+     */
     public NotificationAlertingManager(
             NotificationEntryManager notificationEntryManager,
             NotificationRemoteInputManager remoteInputManager,
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 b0bf813..4f55e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -64,9 +65,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 
 /**
@@ -94,7 +92,6 @@
  * aware of
  * @see #getActiveNotificationsForCurrentUser() to see every notification that the current user owns
  */
-@Singleton
 public class NotificationEntryManager implements
         CommonNotifCollection,
         Dumpable,
@@ -181,7 +178,9 @@
         }
     }
 
-    @Inject
+    /**
+     * Injected constructor. See {@link NotificationsModule}.
+     */
     public NotificationEntryManager(
             NotificationEntryManagerLogger logger,
             NotificationGroupManager groupManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 99718ab..616c110 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -26,6 +26,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -33,14 +34,10 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * A manager that ensures that notifications are visually stable. It will suppress reorderings
  * and reorder at the right time when they are out of view.
  */
-@Singleton
 public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
 
     private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
@@ -60,7 +57,9 @@
     private ArraySet<View> mAddedChildren = new ArraySet<>();
     private boolean mPulsing;
 
-    @Inject
+    /**
+     * Injected constructor. See {@link NotificationsModule}.
+     */
     public VisualStabilityManager(
             NotificationEntryManager notificationEntryManager, @Main Handler handler) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index ec3285f..91d2de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -44,12 +44,16 @@
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
@@ -191,59 +195,121 @@
     }
 
     /**
-     * Dismiss a notification on behalf of the user.
+     * Dismisses multiple notifications on behalf of the user.
      */
-    public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
+    public void dismissNotifications(
+            List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
         Assert.isMainThread();
-        requireNonNull(stats);
         checkForReentrantCall();
 
-        if (entry != mNotificationSet.get(entry.getKey())) {
-            throw new IllegalStateException("Invalid entry: " + entry.getKey());
-        }
+        final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
+        for (int i = 0; i < entriesToDismiss.size(); i++) {
+            NotificationEntry entry = entriesToDismiss.get(i).first;
+            DismissedByUserStats stats = entriesToDismiss.get(i).second;
 
-        if (entry.getDismissState() == DISMISSED) {
-            return;
-        }
-
-        updateDismissInterceptors(entry);
-        if (isDismissIntercepted(entry)) {
-            mLogger.logNotifDismissedIntercepted(entry.getKey());
-            return;
-        }
-
-        // Optimistically mark the notification as dismissed -- we'll wait for the signal from
-        // system server before removing it from our notification set.
-        entry.setDismissState(DISMISSED);
-        mLogger.logNotifDismissed(entry.getKey());
-
-        List<NotificationEntry> canceledEntries = new ArrayList<>();
-
-        if (isCanceled(entry)) {
-            canceledEntries.add(entry);
-        } else {
-            // Ask system server to remove it for us
-            try {
-                mStatusBarService.onNotificationClear(
-                        entry.getSbn().getPackageName(),
-                        entry.getSbn().getTag(),
-                        entry.getSbn().getId(),
-                        entry.getSbn().getUser().getIdentifier(),
-                        entry.getSbn().getKey(),
-                        stats.dismissalSurface,
-                        stats.dismissalSentiment,
-                        stats.notificationVisibility);
-            } catch (RemoteException e) {
-                // system process is dead if we're here.
+            requireNonNull(stats);
+            if (entry != mNotificationSet.get(entry.getKey())) {
+                throw new IllegalStateException("Invalid entry: " + entry.getKey());
             }
 
-            // Also mark any children as dismissed as system server will auto-dismiss them as well
-            if (entry.getSbn().getNotification().isGroupSummary()) {
-                for (NotificationEntry otherEntry : mNotificationSet.values()) {
-                    if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) {
-                        otherEntry.setDismissState(PARENT_DISMISSED);
-                        if (isCanceled(otherEntry)) {
-                            canceledEntries.add(otherEntry);
+            if (entry.getDismissState() == DISMISSED) {
+                continue;
+            }
+
+            updateDismissInterceptors(entry);
+            if (isDismissIntercepted(entry)) {
+                mLogger.logNotifDismissedIntercepted(entry.getKey());
+                continue;
+            }
+
+            entriesToLocallyDismiss.add(entry);
+            if (!isCanceled(entry)) {
+                // send message to system server if this notification hasn't already been cancelled
+                try {
+                    mStatusBarService.onNotificationClear(
+                            entry.getSbn().getPackageName(),
+                            entry.getSbn().getTag(),
+                            entry.getSbn().getId(),
+                            entry.getSbn().getUser().getIdentifier(),
+                            entry.getSbn().getKey(),
+                            stats.dismissalSurface,
+                            stats.dismissalSentiment,
+                            stats.notificationVisibility);
+                } catch (RemoteException e) {
+                    // system process is dead if we're here.
+                }
+            }
+        }
+
+        locallyDismissNotifications(entriesToLocallyDismiss);
+        rebuildList();
+    }
+
+    /**
+     * Dismisses a single notification on behalf of the user.
+     */
+    public void dismissNotification(
+            NotificationEntry entry,
+            @NonNull DismissedByUserStats stats) {
+        dismissNotifications(List.of(
+                new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+    }
+
+    /**
+     * Dismisses all clearable notifications for a given userid on behalf of the user.
+     */
+    public void dismissAllNotifications(@UserIdInt int userId) {
+        Assert.isMainThread();
+        checkForReentrantCall();
+
+        try {
+            mStatusBarService.onClearAllNotifications(userId);
+        } catch (RemoteException e) {
+            // system process is dead if we're here.
+        }
+
+        final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+        for (int i = entries.size() - 1; i >= 0; i--) {
+            NotificationEntry entry = entries.get(i);
+            if (!shouldDismissOnClearAll(entry, userId)) {
+                // system server won't be removing these notifications, but we still give dismiss
+                // interceptors the chance to filter the notification
+                updateDismissInterceptors(entry);
+                if (isDismissIntercepted(entry)) {
+                    mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+                }
+                entries.remove(i);
+            }
+        }
+
+        locallyDismissNotifications(entries);
+        rebuildList();
+    }
+
+    /**
+     * Optimistically marks the given notifications as dismissed -- we'll wait for the signal
+     * from system server before removing it from our notification set.
+     */
+    private void locallyDismissNotifications(List<NotificationEntry> entries) {
+        final List<NotificationEntry> canceledEntries = new ArrayList<>();
+
+        for (int i = 0; i < entries.size(); i++) {
+            NotificationEntry entry = entries.get(i);
+
+            entry.setDismissState(DISMISSED);
+            mLogger.logNotifDismissed(entry.getKey());
+
+            if (isCanceled(entry)) {
+                canceledEntries.add(entry);
+            } else {
+                // Mark any children as dismissed as system server will auto-dismiss them as well
+                if (entry.getSbn().getNotification().isGroupSummary()) {
+                    for (NotificationEntry otherEntry : mNotificationSet.values()) {
+                        if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+                            otherEntry.setDismissState(PARENT_DISMISSED);
+                            if (isCanceled(otherEntry)) {
+                                canceledEntries.add(otherEntry);
+                            }
                         }
                     }
                 }
@@ -255,7 +321,6 @@
         for (NotificationEntry canceledEntry : canceledEntries) {
             tryRemoveNotification(canceledEntry);
         }
-        rebuildList();
     }
 
     private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -552,7 +617,7 @@
      *
      * See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code.
      */
-    private static boolean shouldAutoDismiss(
+    private static boolean shouldAutoDismissChildren(
             NotificationEntry entry,
             String dismissedGroupKey) {
         return entry.getSbn().getGroupKey().equals(dismissedGroupKey)
@@ -562,10 +627,39 @@
                 && entry.getDismissState() != DISMISSED;
     }
 
+    /**
+     * When the user 'clears all notifications' through SystemUI, NotificationManager will not
+     * dismiss unclearable notifications.
+     * @return true if we think NotificationManager will dismiss the entry when asked to
+     * cancel this notification with {@link NotificationListenerService#REASON_CANCEL_ALL}
+     *
+     * See NotificationManager.cancelAllLocked for corresponding code.
+     */
+    private static boolean shouldDismissOnClearAll(
+            NotificationEntry entry,
+            @UserIdInt int userId) {
+        return userIdMatches(entry, userId)
+                && entry.isClearable()
+                && !hasFlag(entry, Notification.FLAG_BUBBLE)
+                && entry.getDismissState() != DISMISSED;
+    }
+
     private static boolean hasFlag(NotificationEntry entry, int flag) {
         return (entry.getSbn().getNotification().flags & flag) != 0;
     }
 
+    /**
+     * Determine whether the userId applies to the notification in question, either because
+     * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
+     *
+     * See NotificationManager#notificationMatchesUserId
+     */
+    private static boolean userIdMatches(NotificationEntry entry, int userId) {
+        return userId == UserHandle.USER_ALL
+                || entry.getSbn().getUser().getIdentifier() == UserHandle.USER_ALL
+                || entry.getSbn().getUser().getIdentifier() == userId;
+    }
+
     private void dispatchOnEntryInit(NotificationEntry entry) {
         mAmDispatchingToOtherCode = true;
         for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -613,6 +707,7 @@
         }
         mAmDispatchingToOtherCode = false;
     }
+
     @Override
     public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
         final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 83f56cc..1f6413b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -44,6 +44,7 @@
     private final IStatusBarService mStatusBarService;
     private final NotifCollection mNotifCollection;
     private final NotifInflationErrorManager mNotifErrorManager;
+    private final NotifPipeline mNotifPipeline;
 
     private NotificationRowBinderImpl mNotificationRowBinder;
     private InflationCallback mExternalInflationCallback;
@@ -52,10 +53,12 @@
     public NotifInflaterImpl(
             IStatusBarService statusBarService,
             NotifCollection notifCollection,
-            NotifInflationErrorManager errorManager) {
+            NotifInflationErrorManager errorManager,
+            NotifPipeline notifPipeline) {
         mStatusBarService = statusBarService;
         mNotifCollection = notifCollection;
         mNotifErrorManager = errorManager;
+        mNotifPipeline = notifPipeline;
     }
 
     /**
@@ -110,7 +113,7 @@
                                 DISMISS_SENTIMENT_NEUTRAL,
                                 NotificationVisibility.obtain(entry.getKey(),
                                         entry.getRanking().getRank(),
-                                        mNotifCollection.getActiveNotifs().size(),
+                                        mNotifPipeline.getShadeListCount(),
                                         true,
                                         NotificationLogger.getNotificationLocation(entry))
                         ));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index d4d2369..44cec96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -195,4 +195,27 @@
     public List<ListEntry> getShadeList() {
         return mShadeListBuilder.getShadeList();
     }
+
+    /**
+     * Returns the number of notifications currently shown in the shade. This includes all
+     * children and summary notifications. If this method is called during pipeline execution it
+     * will return the number of notifications in its current state, which will likely be only
+     * partially-generated.
+     */
+    public int getShadeListCount() {
+        final List<ListEntry> entries = getShadeList();
+        int numNotifs = 0;
+        for (int i = 0; i < entries.size(); i++) {
+            final ListEntry entry = entries.get(i);
+            if (entry instanceof GroupEntry) {
+                final GroupEntry parentEntry = (GroupEntry) entry;
+                numNotifs++; // include the summary in the count
+                numNotifs += parentEntry.getChildren().size();
+            } else {
+                numNotifs++;
+            }
+        }
+
+        return numNotifs;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 116c70c..8b2a07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.bubbles.BubbleController;
@@ -153,10 +153,10 @@
     private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
         return new DismissedByUserStats(
                 DISMISSAL_OTHER,
-                DISMISS_SENTIMENT_UNKNOWN,
+                DISMISS_SENTIMENT_NEUTRAL,
                 NotificationVisibility.obtain(entry.getKey(),
                         entry.getRanking().getRank(),
-                        mNotifPipeline.getActiveNotifs().size(),
+                        mNotifPipeline.getShadeListCount(),
                         true, // was visible as a bubble
                         NotificationLogger.getNotificationLocation(entry))
         );
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index dc7a50d..8675cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -77,6 +77,14 @@
         })
     }
 
+    fun logNotifClearAllDismissalIntercepted(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+        })
+    }
+
     fun logRankingMissing(key: String, rankingMap: RankingMap) {
         buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
         buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 39f4dfa..8f8f742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -17,15 +17,39 @@
 package com.android.systemui.statusbar.notification.dagger;
 
 import android.content.Context;
+import android.os.Handler;
+import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.util.concurrent.Executor;
 
 import javax.inject.Singleton;
 
@@ -33,9 +57,111 @@
 import dagger.Module;
 import dagger.Provides;
 
-/** Module for classes related to the notifications data pipeline */
+/**
+ * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
+ */
 @Module
-public class NotificationsModule {
+public interface NotificationsModule {
+    /** Provides an instance of {@link NotificationEntryManager} */
+    @Singleton
+    @Provides
+    static NotificationEntryManager provideNotificationEntryManager(
+            NotificationEntryManagerLogger logger,
+            NotificationGroupManager groupManager,
+            NotificationRankingManager rankingManager,
+            NotificationEntryManager.KeyguardEnvironment keyguardEnvironment,
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector,
+            ForegroundServiceDismissalFeatureController fgsFeatureController) {
+        return new NotificationEntryManager(
+                logger,
+                groupManager,
+                rankingManager,
+                keyguardEnvironment,
+                featureFlags,
+                notificationRowBinderLazy,
+                notificationRemoteInputManagerLazy,
+                leakDetector,
+                fgsFeatureController);
+    }
+
+    /** Provides an instance of {@link NotificationGutsManager} */
+    @Singleton
+    @Provides
+    static NotificationGutsManager provideNotificationGutsManager(
+            Context context,
+            VisualStabilityManager visualStabilityManager,
+            Lazy<StatusBar> statusBarLazy,
+            @Main Handler mainHandler,
+            AccessibilityManager accessibilityManager,
+            HighPriorityProvider highPriorityProvider) {
+        return new NotificationGutsManager(
+                context,
+                visualStabilityManager,
+                statusBarLazy,
+                mainHandler,
+                accessibilityManager,
+                highPriorityProvider);
+    }
+
+    /** Provides an instance of {@link VisualStabilityManager} */
+    @Singleton
+    @Provides
+    static VisualStabilityManager provideVisualStabilityManager(
+            NotificationEntryManager notificationEntryManager, Handler handler) {
+        return new VisualStabilityManager(notificationEntryManager, handler);
+    }
+
+    /** Provides an instance of {@link NotificationAlertingManager} */
+    @Singleton
+    @Provides
+    static NotificationAlertingManager provideNotificationAlertingManager(
+            NotificationEntryManager notificationEntryManager,
+            NotificationRemoteInputManager remoteInputManager,
+            VisualStabilityManager visualStabilityManager,
+            StatusBarStateController statusBarStateController,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
+            NotificationListener notificationListener) {
+        return new NotificationAlertingManager(
+                notificationEntryManager,
+                remoteInputManager,
+                visualStabilityManager,
+                statusBarStateController,
+                notificationInterruptionStateProvider,
+                notificationListener);
+    }
+
+    /** Provides an instance of {@link NotificationLogger} */
+    @Singleton
+    @Provides
+    static NotificationLogger provideNotificationLogger(
+            NotificationListener notificationListener,
+            @UiBackground Executor uiBgExecutor,
+            NotificationEntryManager entryManager,
+            StatusBarStateController statusBarStateController,
+            NotificationLogger.ExpansionStateLogger expansionStateLogger) {
+        return new NotificationLogger(
+                notificationListener,
+                uiBgExecutor,
+                entryManager,
+                statusBarStateController,
+                expansionStateLogger);
+    }
+
+    /** Provides an instance of {@link NotificationBlockingHelperManager} */
+    @Singleton
+    @Provides
+    static NotificationBlockingHelperManager provideNotificationBlockingHelperManager(
+            Context context,
+            NotificationGutsManager notificationGutsManager,
+            NotificationEntryManager notificationEntryManager,
+            MetricsLogger metricsLogger) {
+        return new NotificationBlockingHelperManager(
+                context, notificationGutsManager, notificationEntryManager, metricsLogger);
+    }
+
     /** Initializes the notification data pipeline (can be disabled via config). */
     @Singleton
     @Provides
@@ -55,7 +181,7 @@
      */
     @Provides
     @Singleton
-    public CommonNotifCollection provideCommonNotifCollection(
+    static CommonNotifCollection provideCommonNotifCollection(
             FeatureFlags featureFlags,
             Lazy<NotifPipeline> pipeline,
             NotificationEntryManager entryManager) {
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 89e5f55..becb758 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
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -50,13 +51,11 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles notification logging, in particular, logging which notifications are visible and which
  * are not.
  */
-@Singleton
 public class NotificationLogger implements StateListener {
     private static final String TAG = "NotificationLogger";
 
@@ -192,7 +191,9 @@
         }
     }
 
-    @Inject
+    /**
+     * Injected constructor. See {@link NotificationsModule}.
+     */
     public NotificationLogger(NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
             NotificationEntryManager entryManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 4f27c0f..5b4a927 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -26,26 +26,23 @@
 import android.content.DialogInterface
 import android.graphics.Color
 import android.graphics.PixelFormat
-import android.graphics.drawable.Drawable
 import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
 import android.util.Log
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.Window
-import android.view.WindowInsets.Type
 import android.view.WindowInsets.Type.statusBars
 import android.view.WindowManager
 import android.widget.TextView
 import com.android.internal.annotations.VisibleForTesting
-
 import com.android.systemui.R
-
 import javax.inject.Inject
 import javax.inject.Singleton
 
-const val TAG = "ChannelDialogController"
+private const val TAG = "ChannelDialogController"
 
 /**
  * ChannelEditorDialogController is the controller for the dialog half-shelf
@@ -149,9 +146,9 @@
         val channels = groupList
                 .flatMap { group ->
                     group.channels.asSequence().filterNot { channel ->
-                        channel.isImportanceLockedByOEM
-                                || channel.importance == IMPORTANCE_NONE
-                                || channel.isImportanceLockedByCriticalDeviceFunction
+                        channel.isImportanceLockedByOEM ||
+                                channel.importance == IMPORTANCE_NONE ||
+                                channel.isImportanceLockedByCriticalDeviceFunction
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index e2513da..d744fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -74,11 +74,15 @@
 @Singleton
 public final class NotifBindPipeline {
     private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
+    private final NotifBindPipelineLogger mLogger;
     private BindStage mStage;
 
     @Inject
-    NotifBindPipeline(CommonNotifCollection collection) {
+    NotifBindPipeline(
+            CommonNotifCollection collection,
+            NotifBindPipelineLogger logger) {
         collection.addCollectionListener(mCollectionListener);
+        mLogger = logger;
     }
 
     /**
@@ -86,6 +90,8 @@
      */
     public void setStage(
             BindStage stage) {
+        mLogger.logStageSet(stage.getClass().getName());
+
         mStage = stage;
         mStage.setBindRequestListener(this::onBindRequested);
     }
@@ -96,6 +102,8 @@
     public void manageRow(
             @NonNull NotificationEntry entry,
             @NonNull ExpandableNotificationRow row) {
+        mLogger.logManagedRow(entry.getKey());
+
         final BindEntry bindEntry = getBindEntry(entry);
         bindEntry.row = row;
         if (bindEntry.invalidated) {
@@ -130,6 +138,8 @@
      * callbacks when the run finishes. If a run is already in progress, it is restarted.
      */
     private void startPipeline(NotificationEntry entry) {
+        mLogger.logStartPipeline(entry.getKey());
+
         if (mStage == null) {
             throw new IllegalStateException("No stage was ever set on the pipeline");
         }
@@ -147,10 +157,11 @@
 
     private void onPipelineComplete(NotificationEntry entry) {
         final BindEntry bindEntry = getBindEntry(entry);
+        final Set<BindCallback> callbacks = bindEntry.callbacks;
+
+        mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());
 
         bindEntry.invalidated = false;
-
-        final Set<BindCallback> callbacks = bindEntry.callbacks;
         for (BindCallback cb : callbacks) {
             cb.onBindFinished(entry);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
new file mode 100644
index 0000000..2717d7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class NotifBindPipelineLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+    fun logStageSet(stageName: String) {
+        buffer.log(TAG, INFO, {
+            str1 = stageName
+        }, {
+            "Stage set: $str1"
+        })
+    }
+
+    fun logManagedRow(notifKey: String) {
+        buffer.log(TAG, INFO, {
+            str1 = notifKey
+        }, {
+            "Row set for notif: $str1"
+        })
+    }
+
+    fun logStartPipeline(notifKey: String) {
+        buffer.log(TAG, INFO, {
+            str1 = notifKey
+        }, {
+            "Start pipeline for notif: $str1"
+        })
+    }
+
+    fun logFinishedPipeline(notifKey: String, numCallbacks: Int) {
+        buffer.log(TAG, INFO, {
+            str1 = notifKey
+            int1 = numCallbacks
+        }, {
+            "Finished pipeline for notif $str1 with $int1 callbacks"
+        })
+    }
+}
+
+private const val TAG = "NotifBindPipeline"
\ No newline at end of file
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 7b758aa..9212325 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
@@ -16,8 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 
 import android.content.Context;
 import android.metrics.LogMaker;
@@ -27,29 +26,28 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Manager for the notification blocking helper - tracks and helps create the blocking helper
  * affordance.
  */
-@Singleton
 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 final Context mContext;
+    private final NotificationGutsManager mNotificationGutsManager;
+    private final NotificationEntryManager mNotificationEntryManager;
+    private final MetricsLogger mMetricsLogger;
     /** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */
     private ExpandableNotificationRow mBlockingHelperRow;
     private Set<String> mNonBlockablePkgs;
@@ -60,11 +58,18 @@
      */
     private boolean mIsShadeExpanded;
 
-    private MetricsLogger mMetricsLogger = new MetricsLogger();
-
-    @Inject
-    public NotificationBlockingHelperManager(Context context) {
+    /**
+     * Injected constructor. See {@link NotificationsModule}.
+     */
+    public NotificationBlockingHelperManager(
+            Context context,
+            NotificationGutsManager notificationGutsManager,
+            NotificationEntryManager notificationEntryManager,
+            MetricsLogger metricsLogger) {
         mContext = context;
+        mNotificationGutsManager = notificationGutsManager;
+        mNotificationEntryManager = notificationEntryManager;
+        mMetricsLogger = metricsLogger;
         mNonBlockablePkgs = new HashSet<>();
         Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
                 com.android.internal.R.array.config_nonBlockableNotificationPackages));
@@ -99,7 +104,6 @@
             if (DEBUG) {
                 Log.d(TAG, "Manager.perhapsShowBlockingHelper: Showing new blocking helper");
             }
-            NotificationGutsManager manager = Dependency.get(NotificationGutsManager.class);
 
             // Enable blocking helper on the row before moving forward so everything in the guts is
             // correctly prepped.
@@ -113,10 +117,10 @@
 
             // We don't care about the touch origin (x, y) since we're opening guts without any
             // explicit user interaction.
-            manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
+            mNotificationGutsManager.openGuts(
+                    mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
 
-            Dependency.get(MetricsLogger.class)
-                    .count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1);
+            mMetricsLogger.count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1);
             return true;
         }
         return false;
@@ -139,8 +143,7 @@
 
             mBlockingHelperRow.setBlockingHelperShowing(false);
             if (mBlockingHelperRow.isAttachedToWindow()) {
-                Dependency.get(NotificationEntryManager.class).updateNotifications(
-                        "dismissCurrentBlockingHelper");
+                mNotificationEntryManager.updateNotifications("dismissCurrentBlockingHelper");
             }
             mBlockingHelperRow = null;
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 566da65..e3ca283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -33,7 +33,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
-import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
@@ -53,6 +52,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
@@ -66,13 +67,19 @@
     private boolean mInflateSynchronously = false;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotifRemoteViewCache mRemoteViewCache;
+    private final Lazy<SmartReplyConstants> mSmartReplyConstants;
+    private final Lazy<SmartReplyController> mSmartReplyController;
 
     @Inject
     NotificationContentInflater(
             NotifRemoteViewCache remoteViewCache,
-            NotificationRemoteInputManager remoteInputManager) {
+            NotificationRemoteInputManager remoteInputManager,
+            Lazy<SmartReplyConstants> smartReplyConstants,
+            Lazy<SmartReplyController> smartReplyController) {
         mRemoteViewCache = remoteViewCache;
         mRemoteInputManager = remoteInputManager;
+        mSmartReplyConstants = smartReplyConstants;
+        mSmartReplyController = smartReplyController;
     }
 
     @Override
@@ -104,6 +111,8 @@
                 contentToBind,
                 mRemoteViewCache,
                 entry,
+                mSmartReplyConstants.get(),
+                mSmartReplyController.get(),
                 row,
                 bindParams.isLowPriority,
                 bindParams.isChildInGroup,
@@ -136,6 +145,7 @@
                 packageContext);
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext, row.getHeadsUpManager(),
+                mSmartReplyConstants.get(), mSmartReplyController.get(),
                 row.getExistingSmartRepliesAndActions());
 
         apply(
@@ -204,9 +214,8 @@
     private static InflationProgress inflateSmartReplyViews(InflationProgress result,
             @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
             Context packageContext, HeadsUpManager headsUpManager,
+            SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController,
             SmartRepliesAndActions previousSmartRepliesAndActions) {
-        SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
-        SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
         if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
             result.expandedInflatedSmartReplies =
                     InflatedSmartReplies.inflate(
@@ -643,6 +652,8 @@
         private final boolean mUsesIncreasedHeadsUpHeight;
         private final @InflationFlag int mReInflateFlags;
         private final NotifRemoteViewCache mRemoteViewCache;
+        private final SmartReplyConstants mSmartReplyConstants;
+        private final SmartReplyController mSmartReplyController;
         private ExpandableNotificationRow mRow;
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
@@ -653,6 +664,8 @@
                 @InflationFlag int reInflateFlags,
                 NotifRemoteViewCache cache,
                 NotificationEntry entry,
+                SmartReplyConstants smartReplyConstants,
+                SmartReplyController smartReplyController,
                 ExpandableNotificationRow row,
                 boolean isLowPriority,
                 boolean isChildInGroup,
@@ -662,6 +675,8 @@
                 RemoteViews.OnClickHandler remoteViewClickHandler) {
             mEntry = entry;
             mRow = row;
+            mSmartReplyConstants = smartReplyConstants;
+            mSmartReplyController = smartReplyController;
             mInflateSynchronously = inflateSynchronously;
             mReInflateFlags = reInflateFlags;
             mRemoteViewCache = cache;
@@ -701,6 +716,7 @@
                         mUsesIncreasedHeadsUpHeight, packageContext);
                 return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
                         mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
+                        mSmartReplyConstants, mSmartReplyController,
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
                 mError = e;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index a0af4ac..923c348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -33,11 +33,9 @@
 import android.app.NotificationChannelGroup;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.graphics.drawable.Icon;
@@ -61,6 +59,7 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -87,6 +86,7 @@
     private PackageManager mPm;
     private VisualStabilityManager mVisualStabilityManager;
     private ShadeController mShadeController;
+    private ConversationIconFactory mIconFactory;
 
     private String mPackageName;
     private String mAppName;
@@ -186,6 +186,7 @@
             OnSettingsClickListener onSettingsClick,
             OnAppSettingsClickListener onAppSettingsClick,
             OnSnoozeClickListener onSnoozeClickListener,
+            ConversationIconFactory conversationIconFactory,
             boolean isDeviceProvisioned) {
         mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
@@ -203,6 +204,7 @@
         mIsDeviceProvisioned = isDeviceProvisioned;
         mOnSnoozeClickListener = onSnoozeClickListener;
         mShadeController = Dependency.get(ShadeController.class);
+        mIconFactory = conversationIconFactory;
 
         mShortcutManager = shortcutManager;
         mLauncherApps = launcherApps;
@@ -320,8 +322,8 @@
     private void bindIcon() {
         ImageView image = findViewById(R.id.conversation_icon);
         if (mShortcutInfo != null) {
-            image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
-                    mContext.getResources().getDisplayMetrics().densityDpi));
+            image.setImageBitmap(mIconFactory.getConversationBitmap(
+                    mShortcutInfo, mPackageName, mAppUid));
         } else {
             if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
                 // TODO: maybe use a generic group icon, or a composite of recent senders
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 352abcf..c01f6c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -34,6 +34,7 @@
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
+import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
@@ -42,8 +43,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -56,25 +59,21 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 
 /**
  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
  * closing guts, and keeping track of the currently exposed notification guts.
  */
-@Singleton
 public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender {
     private static final String TAG = "NotificationGutsManager";
 
@@ -111,7 +110,9 @@
     private final Handler mMainHandler;
     private Runnable mOpenRunnable;
 
-    @Inject
+    /**
+     * Injected constructor. See {@link NotificationsModule}.
+     */
     public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
             AccessibilityManager accessibilityManager,
@@ -388,6 +389,10 @@
                 notificationInfoView.closeControls(v, false);
             };
         }
+        ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
+                launcherApps, pmUser, IconDrawableFactory.newInstance(mContext),
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_guts_conversation_icon_size));
 
         notificationInfoView.bindNotification(
                 shortcutManager,
@@ -401,8 +406,8 @@
                 onSettingsClick,
                 onAppSettingsClick,
                 onSnoozeClickListener,
+                iconFactoryLoader,
                 mDeviceProvisionedController.isDeviceProvisioned());
-
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index 5170d0b..88ed0bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -157,6 +157,15 @@
         return mViewsNeedReinflation;
     }
 
+    @Override
+    public String toString() {
+        return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x "
+                + "mUseLowPriority=%b mUseChildInGroup=%b mUseIncreasedHeight=%b "
+                + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]",
+                mContentViews, mDirtyContentViews, mUseLowPriority, mUseChildInGroup,
+                mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation);
+    }
+
     /**
      * Content views that should be inflated by default for all notifications.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index f783245..c632f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -38,13 +38,16 @@
 public class RowContentBindStage extends BindStage<RowContentBindParams> {
     private final NotificationRowContentBinder mBinder;
     private final NotifInflationErrorManager mNotifInflationErrorManager;
+    private final RowContentBindStageLogger mLogger;
 
     @Inject
     RowContentBindStage(
             NotificationRowContentBinder binder,
-            NotifInflationErrorManager errorManager) {
+            NotifInflationErrorManager errorManager,
+            RowContentBindStageLogger logger) {
         mBinder = binder;
         mNotifInflationErrorManager = errorManager;
+        mLogger = logger;
     }
 
     @Override
@@ -54,6 +57,8 @@
             @NonNull StageCallback callback) {
         RowContentBindParams params = getStageParams(entry);
 
+        mLogger.logStageParams(entry.getKey(), params.toString());
+
         // Resolve content to bind/unbind.
         @InflationFlag int inflationFlags = params.getContentViews();
         @InflationFlag int invalidatedFlags = params.getDirtyContentViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
new file mode 100644
index 0000000..29cce33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class RowContentBindStageLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+    fun logStageParams(notifKey: String, stageParams: String) {
+        buffer.log(TAG, INFO, {
+            str1 = notifKey
+            str2 = stageParams
+        }, {
+            "Invalidated notif $str1 with params: \n$str2"
+        })
+    }
+}
+
+private const val TAG = "RowContentBindStage"
\ No newline at end of file
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 0cc3371..2eeda1f 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
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
@@ -57,6 +60,7 @@
 import android.util.MathUtils;
 import android.util.Pair;
 import android.view.ContextThemeWrapper;
+import android.view.DisplayCutout;
 import android.view.InputDevice;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -79,6 +83,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
@@ -98,6 +103,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -114,7 +120,11 @@
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -484,8 +494,10 @@
     private NotificationIconAreaController mIconAreaController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final Rect mTmpRect = new Rect();
-    private final NotificationEntryManager mEntryManager =
-            Dependency.get(NotificationEntryManager.class);
+    private final FeatureFlags mFeatureFlags;
+    private final NotifPipeline mNotifPipeline;
+    private final NotifCollection mNotifCollection;
+    private final NotificationEntryManager mEntryManager;
     private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
             ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     @VisibleForTesting
@@ -513,6 +525,8 @@
     private float mLastSentExpandedHeight;
     private boolean mWillExpand;
 
+    private int mWaterfallTopInset;
+
     @Inject
     public NotificationStackScrollLayout(
             @Named(VIEW_CONTEXT) Context context,
@@ -529,7 +543,11 @@
             ZenModeController zenController,
             NotificationSectionsManager notificationSectionsManager,
             ForegroundServiceSectionController fgsSectionController,
-            ForegroundServiceDismissalFeatureController fgsFeatureController
+            ForegroundServiceDismissalFeatureController fgsFeatureController,
+            FeatureFlags featureFlags,
+            NotifPipeline notifPipeline,
+            NotificationEntryManager entryManager,
+            NotifCollection notifCollection
     ) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
@@ -607,16 +625,26 @@
             }
         }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
 
-        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-            @Override
-            public void onPreEntryUpdated(NotificationEntry entry) {
-                if (entry.rowExists() && !entry.getSbn().isClearable()) {
-                    // If the row already exists, the user may have performed a dismiss action on
-                    // the notification. Since it's not clearable we should snap it back.
-                    snapViewIfNeeded(entry);
+        mFeatureFlags = featureFlags;
+        mNotifPipeline = notifPipeline;
+        mEntryManager = entryManager;
+        mNotifCollection = notifCollection;
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+                @Override
+                public void onEntryUpdated(NotificationEntry entry) {
+                    NotificationStackScrollLayout.this.onEntryUpdated(entry);
                 }
-            }
-        });
+            });
+        } else {
+            mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+                @Override
+                public void onPreEntryUpdated(NotificationEntry entry) {
+                    NotificationStackScrollLayout.this.onEntryUpdated(entry);
+                }
+            });
+        }
+
         dynamicPrivacyController.addListener(this);
         mDynamicPrivacyController = dynamicPrivacyController;
         mStatusbarStateController = statusBarStateController;
@@ -708,7 +736,7 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
         boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
-        boolean showFooterView = (showDismissView || mEntryManager.hasActiveNotifications())
+        boolean showFooterView = (showDismissView || hasActiveNotifications())
                 && mStatusBarState != StatusBarState.KEYGUARD
                 && !mRemoteInputManager.getController().isRemoteInputActive();
 
@@ -1714,6 +1742,12 @@
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mBottomInset = insets.getSystemWindowInsetBottom();
 
+        mWaterfallTopInset = 0;
+        final DisplayCutout cutout = insets.getDisplayCutout();
+        if (cutout != null) {
+            mWaterfallTopInset = cutout.getWaterfallInsets().top;
+        }
+
         if (ANCHOR_SCROLLING) {
             // TODO
         } else {
@@ -5310,7 +5344,9 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getMinExpansionHeight() {
-        return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
+        return mShelf.getIntrinsicHeight()
+                - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2
+                + mWaterfallTopInset;
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5537,32 +5573,10 @@
             return;
         }
 
-        performDismissAllAnimations(viewsToHide, closeShade, () -> {
-            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
-                if (canChildBeDismissed(rowToRemove)) {
-                    if (selection == ROWS_ALL) {
-                        // TODO: This is a listener method; we shouldn't be calling it. Can we just
-                        // call performRemoveNotification as below?
-                        mEntryManager.removeNotification(
-                                rowToRemove.getEntry().getKey(),
-                                null /* ranking */,
-                                NotificationListenerService.REASON_CANCEL_ALL);
-                    } else {
-                        mEntryManager.performRemoveNotification(
-                                rowToRemove.getEntry().getSbn(),
-                                NotificationListenerService.REASON_CANCEL_ALL);
-                    }
-                } else {
-                    rowToRemove.resetTranslation();
-                }
-            }
-            if (selection == ROWS_ALL) {
-                try {
-                    mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
-                } catch (Exception ex) {
-                }
-            }
-        });
+        performDismissAllAnimations(
+                viewsToHide,
+                closeShade,
+                () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
     }
 
     private boolean includeChildInDismissAll(
@@ -6407,6 +6421,83 @@
         return false;
     }
 
+    // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+    private void onEntryUpdated(NotificationEntry entry) {
+        // If the row already exists, the user may have performed a dismiss action on the
+        // notification. Since it's not clearable we should snap it back.
+        if (entry.rowExists() && !entry.getSbn().isClearable()) {
+            snapViewIfNeeded(entry);
+        }
+    }
+
+    private boolean hasActiveNotifications() {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return mNotifPipeline.getShadeList().isEmpty();
+        } else {
+            return mEntryManager.hasActiveNotifications();
+        }
+    }
+
+    /**
+     * Called after the animations for a "clear all notifications" action has ended.
+     */
+    private void onDismissAllAnimationsEnd(
+            List<ExpandableNotificationRow> viewsToRemove,
+            @SelectedRows int selectedRows) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            if (selectedRows == ROWS_ALL) {
+                mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
+            } else {
+                final List<Pair<NotificationEntry, DismissedByUserStats>>
+                        entriesWithRowsDismissedFromShade = new ArrayList<>();
+                final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>();
+                final int numVisibleEntries = mNotifPipeline.getShadeListCount();
+                for (int i = 0; i < viewsToRemove.size(); i++) {
+                    final NotificationEntry entry = viewsToRemove.get(i).getEntry();
+                    final DismissedByUserStats stats =
+                            new DismissedByUserStats(
+                                    DISMISSAL_SHADE,
+                                    DISMISS_SENTIMENT_NEUTRAL,
+                                    NotificationVisibility.obtain(
+                                            entry.getKey(),
+                                            entry.getRanking().getRank(),
+                                            numVisibleEntries,
+                                            true,
+                                            NotificationLogger.getNotificationLocation(entry)));
+                    entriesWithRowsDismissedFromShade.add(
+                            new Pair<NotificationEntry, DismissedByUserStats>(entry, stats));
+                }
+                mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
+            }
+        } else {
+            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+                if (canChildBeDismissed(rowToRemove)) {
+                    if (selectedRows == ROWS_ALL) {
+                        // TODO: This is a listener method; we shouldn't be calling it. Can we just
+                        // call performRemoveNotification as below?
+                        mEntryManager.removeNotification(
+                                rowToRemove.getEntry().getKey(),
+                                null /* ranking */,
+                                NotificationListenerService.REASON_CANCEL_ALL);
+                    } else {
+                        mEntryManager.performRemoveNotification(
+                                rowToRemove.getEntry().getSbn(),
+                                NotificationListenerService.REASON_CANCEL_ALL);
+                    }
+                } else {
+                    rowToRemove.resetTranslation();
+                }
+            }
+            if (selectedRows == ROWS_ALL) {
+                try {
+                    mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+                } catch (Exception ex) {
+                }
+            }
+        }
+    }
+
     // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6415,8 +6506,7 @@
         /* Only ever called as a consequence of a lockscreen expansion gesture. */
         @Override
         public boolean onDraggedDown(View startingChild, int dragLengthY) {
-            if (mStatusBarState == StatusBarState.KEYGUARD
-                    && mEntryManager.hasActiveNotifications()) {
+            if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) {
                 mLockscreenGestureLogger.write(
                         MetricsEvent.ACTION_LS_SHADE,
                         (int) (dragLengthY / mDisplayMetrics.density),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 3165597..971f045 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -25,8 +25,7 @@
 
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-
-import javax.inject.Inject;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 
 /** A controller to control all auto-hide things. */
 public class AutoHideController {
@@ -51,7 +50,9 @@
         }
     };
 
-    @Inject
+    /**
+     * Injected constructor. See {@link StatusBarPhoneModule}.
+     */
     public AutoHideController(Context context, @Main Handler handler,
             NotificationRemoteInputManager notificationRemoteInputManager,
             IWindowManager iWindowManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b24942a..b46ca40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -102,6 +102,9 @@
      */
     private int mCutoutSideNudge = 0;
 
+    private DisplayCutout mDisplayCutout;
+    private int mRoundedCornerPadding = 0;
+
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -159,9 +162,15 @@
                 getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin));
         mCarrierLabel.setLayoutParams(lp);
 
-        lp = (MarginLayoutParams) getLayoutParams();
+        updateKeyguardStatusBarHeight();
+    }
+
+    private void updateKeyguardStatusBarHeight() {
+        final int waterfallTop =
+                mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
+        MarginLayoutParams lp =  (MarginLayoutParams) getLayoutParams();
         lp.height =  getResources().getDimensionPixelSize(
-                R.dimen.status_bar_header_height_keyguard);
+                R.dimen.status_bar_header_height_keyguard) + waterfallTop;
         setLayoutParams(lp);
     }
 
@@ -175,6 +184,8 @@
                 R.dimen.display_cutout_margin_consumption);
         mShowPercentAvailable = getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_battery_percentage_setting_available);
+        mRoundedCornerPadding = res.getDimensionPixelSize(
+                R.dimen.rounded_corner_content_padding);
     }
 
     private void updateVisibilities() {
@@ -225,23 +236,26 @@
     }
 
     private boolean updateLayoutConsideringCutout() {
-        DisplayCutout dc = getRootWindowInsets().getDisplayCutout();
+        mDisplayCutout = getRootWindowInsets().getDisplayCutout();
+        updateKeyguardStatusBarHeight();
+
         Pair<Integer, Integer> cornerCutoutMargins =
-                PhoneStatusBarView.cornerCutoutMargins(dc, getDisplay());
-        updateCornerCutoutPadding(cornerCutoutMargins);
-        if (dc == null || cornerCutoutMargins != null) {
+                StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay());
+        updatePadding(cornerCutoutMargins);
+        if (mDisplayCutout == null || cornerCutoutMargins != null) {
             return updateLayoutParamsNoCutout();
         } else {
-            return updateLayoutParamsForCutout(dc);
+            return updateLayoutParamsForCutout();
         }
     }
 
-    private void updateCornerCutoutPadding(Pair<Integer, Integer> cornerCutoutMargins) {
-        if (cornerCutoutMargins != null) {
-            setPadding(cornerCutoutMargins.first, 0, cornerCutoutMargins.second, 0);
-        } else {
-            setPadding(0, 0, 0, 0);
-        }
+    private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) {
+        final int waterfallTop =
+                mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
+        Pair<Integer, Integer> padding =
+                StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
+                        mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
+        setPadding(padding.first, waterfallTop, padding.second, 0);
     }
 
     private boolean updateLayoutParamsNoCutout() {
@@ -268,7 +282,7 @@
         return true;
     }
 
-    private boolean updateLayoutParamsForCutout(DisplayCutout dc) {
+    private boolean updateLayoutParamsForCutout() {
         if (mLayoutState == LAYOUT_CUTOUT) {
             return false;
         }
@@ -279,7 +293,7 @@
         }
 
         Rect bounds = new Rect();
-        boundsFromDirection(dc, Gravity.TOP, bounds);
+        boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds);
 
         mCutoutSpace.setVisibility(View.VISIBLE);
         RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 3f5215e..fd8c71b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -956,6 +956,8 @@
     private boolean onAccessibilityLongClick(View v) {
         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
+                AccessibilityManager.ACCESSIBILITY_BUTTON);
         v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index ba9ba6c..84aecd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -63,7 +63,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
-import com.android.systemui.DockedStackExistsListener;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistHandleViewController;
@@ -75,6 +74,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.policy.DeadZone;
@@ -770,7 +770,14 @@
 
     public void updatePanelSystemUiStateFlags() {
         int displayId = mContext.getDisplayId();
+        if (SysUiState.DEBUG) {
+            Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+        }
         if (mPanelView != null) {
+            if (SysUiState.DEBUG) {
+                Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
+                        + mPanelView.isFullyExpanded() + " inQs=" + mPanelView.isInSettings());
+            }
             mSysUiFlagContainer.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
                     mPanelView.isFullyExpanded() && !mPanelView.isInSettings())
                     .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -862,7 +869,8 @@
 
         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
 
-        DockedStackExistsListener.register(mDockedListener);
+        Divider divider = Dependency.get(Divider.class);
+        divider.registerInSplitScreenListener(mDockedListener);
         updateOrientationViews();
         reloadNavIcons();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 826af66..67e7f23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -73,7 +73,7 @@
 public class NavigationModeController implements Dumpable {
 
     private static final String TAG = NavigationModeController.class.getSimpleName();
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
     public interface ModeChangedListener {
         void onNavigationModeChanged(int mode);
@@ -248,8 +248,7 @@
                     Secure.NAVIGATION_MODE, String.valueOf(mode));
         });
         if (DEBUG) {
-            Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode
-                    + " contextUser=" + mCurrentUserContext.getUserId());
+            Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode);
             dumpAssetPaths(mCurrentUserContext);
         }
 
@@ -293,6 +292,7 @@
                     0 /* flags */, UserHandle.of(userId));
         } catch (PackageManager.NameNotFoundException e) {
             // Never happens for the sysui package
+            Log.e(TAG, "Failed to create package context", e);
             return null;
         }
     }
@@ -404,11 +404,17 @@
             defaultOverlays = "failed_to_fetch";
         }
         pw.println("  defaultOverlays=" + defaultOverlays);
+        pw.println("  restoreGesturalNavMode:");
+        for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) {
+            pw.println("    userId=" + mRestoreGesturalNavBarMode.keyAt(i)
+                    + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i));
+        }
         dumpAssetPaths(mCurrentUserContext);
     }
 
     private void dumpAssetPaths(Context context) {
-        Log.d(TAG, "assetPaths=");
+        Log.d(TAG, "  contextUser=" + mCurrentUserContext.getUserId());
+        Log.d(TAG, "  assetPaths=");
         ApkAssets[] assets = context.getResources().getAssets().getApkAssets();
         for (ApkAssets a : assets) {
             Log.d(TAG, "    " + a.getAssetPath());
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 bdca9a4..d709e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -36,21 +36,18 @@
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import java.util.ArrayList;
 import java.util.Objects;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * A helper class dealing with the alert interactions between {@link NotificationGroupManager} and
  * {@link HeadsUpManager}. In particular, this class deals with keeping
  * the correct notification in a group alerting based off the group suppression.
  */
-@Singleton
 public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
         StateListener {
 
@@ -76,7 +73,9 @@
 
     private boolean mIsDozing;
 
-    @Inject
+    /**
+     * Injected constructor. See {@link StatusBarPhoneModule}.
+     */
     public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
         Dependency.get(StatusBarStateController.class).addCallback(this);
         mRowContentBindStage = bindStage;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
index 7650a3a..bc80a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -88,33 +88,11 @@
             boolean paddingChanged = insets.top != getPaddingTop()
                     || insets.bottom != getPaddingBottom();
 
-            int rightCutout = 0;
-            int leftCutout = 0;
-            DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
-            if (displayCutout != null) {
-                leftCutout = displayCutout.getSafeInsetLeft();
-                rightCutout = displayCutout.getSafeInsetRight();
-            }
-
-            int targetLeft = Math.max(insets.left, leftCutout);
-            int targetRight = Math.max(insets.right, rightCutout);
-
-            // Super-special right inset handling, because scrims and backdrop need to ignore it.
-            if (targetRight != mRightInset || targetLeft != mLeftInset) {
-                mRightInset = targetRight;
-                mLeftInset = targetLeft;
-                applyMargins();
-            }
             // Drop top inset, and pass through bottom inset.
             if (paddingChanged) {
                 setPadding(0, 0, 0, 0);
             }
         } else {
-            if (mRightInset != 0 || mLeftInset != 0) {
-                mRightInset = 0;
-                mLeftInset = 0;
-                applyMargins();
-            }
             boolean changed = getPaddingLeft() != 0
                     || getPaddingRight() != 0
                     || getPaddingTop() != 0
@@ -123,6 +101,17 @@
                 setPadding(0, 0, 0, 0);
             }
         }
+
+        mLeftInset = 0;
+        mRightInset = 0;
+        DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+        if (displayCutout != null) {
+            mLeftInset = displayCutout.getSafeInsetLeft();
+            mRightInset = displayCutout.getSafeInsetRight();
+        }
+        mLeftInset = Math.max(insets.left, mLeftInset);
+        mRightInset = Math.max(insets.right, mRightInset);
+        applyMargins();
         return windowInsets;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 41d8968..260f94c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -43,6 +43,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -76,7 +77,8 @@
                 ZenModeController.Callback,
                 DeviceProvisionedListener,
                 KeyguardStateController.Callback,
-                LocationController.LocationChangeCallback {
+                LocationController.LocationChangeCallback,
+                RecordingController.RecordingStateChangeCallback {
     private static final String TAG = "PhoneStatusBarPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -98,6 +100,7 @@
     private final String mSlotMicrophone;
     private final String mSlotCamera;
     private final String mSlotSensorsOff;
+    private final String mSlotScreenRecord;
 
     private final Context mContext;
     private final Handler mHandler = new Handler();
@@ -116,6 +119,7 @@
     private final LocationController mLocationController;
     private final Executor mUiBgExecutor;
     private final SensorPrivacyController mSensorPrivacyController;
+    private final RecordingController mRecordingController;
 
     // Assume it's all good unless we hear otherwise.  We don't always seem
     // to get broadcasts that it *is* there.
@@ -149,6 +153,7 @@
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mLocationController = Dependency.get(LocationController.class);
         mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
+        mRecordingController = Dependency.get(RecordingController.class);
         mUiBgExecutor = uiBgExecutor;
 
         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
@@ -167,6 +172,8 @@
         mSlotMicrophone = context.getString(com.android.internal.R.string.status_bar_microphone);
         mSlotCamera = context.getString(com.android.internal.R.string.status_bar_camera);
         mSlotSensorsOff = context.getString(com.android.internal.R.string.status_bar_sensors_off);
+        mSlotScreenRecord = context.getString(
+                com.android.internal.R.string.status_bar_screen_record);
 
         // listen for broadcasts
         IntentFilter filter = new IntentFilter();
@@ -235,6 +242,10 @@
         mIconController.setIconVisibility(mSlotSensorsOff,
                 mSensorPrivacyController.isSensorPrivacyEnabled());
 
+        // screen record
+        mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
+        mIconController.setIconVisibility(mSlotScreenRecord, false);
+
         mRotationLockController.addCallback(this);
         mBluetooth.addCallback(this);
         mProvisionedController.addCallback(this);
@@ -246,6 +257,7 @@
         mKeyguardStateController.addCallback(this);
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
         mLocationController.addCallback(this);
+        mRecordingController.addCallback(this);
 
         commandQueue.addCallback(this);
     }
@@ -438,7 +450,7 @@
         }
         if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting);
         mHandler.removeCallbacks(mRemoveCastIconRunnable);
-        if (isCasting) {
+        if (isCasting && !mRecordingController.isRecording()) { // screen record has its own icon
             mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast,
                     mContext.getString(R.string.accessibility_casting));
             mIconController.setIconVisibility(mSlotCast, true);
@@ -643,4 +655,40 @@
             mIconController.setIconVisibility(mSlotCast, false);
         }
     };
+
+    // Screen Recording
+    @Override
+    public void onCountdown(long millisUntilFinished) {
+        if (DEBUG) Log.d(TAG, "screenrecord: countdown " + millisUntilFinished);
+        int countdown = (int) Math.floorDiv(millisUntilFinished + 500, 1000);
+        int resourceId = R.drawable.stat_sys_screen_record;
+        switch (countdown) {
+            case 1:
+                resourceId = R.drawable.stat_sys_screen_record_1;
+                break;
+            case 2:
+                resourceId = R.drawable.stat_sys_screen_record_2;
+                break;
+            case 3:
+                resourceId = R.drawable.stat_sys_screen_record_3;
+                break;
+        }
+        mIconController.setIcon(mSlotScreenRecord, resourceId, null);
+        mIconController.setIconVisibility(mSlotScreenRecord, true);
+    }
+
+    @Override
+    public void onRecordingStart() {
+        if (DEBUG) Log.d(TAG, "screenrecord: showing icon");
+        mIconController.setIcon(mSlotScreenRecord,
+                R.drawable.stat_sys_screen_record, null);
+        mIconController.setIconVisibility(mSlotScreenRecord, true);
+    }
+
+    @Override
+    public void onRecordingEnd() {
+        // Ensure this is on the main thread, since it could be called during countdown
+        if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
+        mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f3b0a79..156a7e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -23,12 +23,10 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.EventLog;
 import android.util.Pair;
-import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -36,7 +34,6 @@
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 import com.android.systemui.Dependency;
@@ -84,6 +81,8 @@
     private int mCutoutSideNudge = 0;
     private boolean mHeadsUpVisible;
 
+    private int mRoundedCornerPadding = 0;
+
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -294,15 +293,25 @@
     public void updateResources() {
         mCutoutSideNudge = getResources().getDimensionPixelSize(
                 R.dimen.display_cutout_margin_consumption);
+        mRoundedCornerPadding = getResources().getDimensionPixelSize(
+                R.dimen.rounded_corner_content_padding);
 
+        updateStatusBarHeight();
+    }
+
+    private void updateStatusBarHeight() {
+        final int waterfallTopInset =
+                mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
-        layoutParams.height = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+        layoutParams.height =
+                getResources().getDimensionPixelSize(R.dimen.status_bar_height) - waterfallTopInset;
         setLayoutParams(layoutParams);
     }
 
     private void updateLayoutForCutout() {
-        Pair<Integer, Integer> cornerCutoutMargins = cornerCutoutMargins(mDisplayCutout,
-                getDisplay());
+        updateStatusBarHeight();
+        Pair<Integer, Integer> cornerCutoutMargins =
+                StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay());
         updateCutoutLocation(cornerCutoutMargins);
         updateSafeInsets(cornerCutoutMargins);
     }
@@ -337,47 +346,11 @@
         // Depending on our rotation, we may have to work around a cutout in the middle of the view,
         // or letterboxing from the right or left sides.
 
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        if (mDisplayCutout == null || mDisplayCutout.isEmpty()
-                || mLastOrientation != ORIENTATION_PORTRAIT || cornerCutoutMargins == null) {
-            lp.leftMargin = 0;
-            lp.rightMargin = 0;
-            return;
-        }
+        Pair<Integer, Integer> padding =
+                StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
+                        mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
 
-        lp.leftMargin = Math.max(lp.leftMargin, cornerCutoutMargins.first);
-        lp.rightMargin = Math.max(lp.rightMargin, cornerCutoutMargins.second);
-
-        // If we're already inset enough (e.g. on the status bar side), we can have 0 margin
-        WindowInsets insets = getRootWindowInsets();
-        int leftInset = insets.getSystemWindowInsetLeft();
-        int rightInset = insets.getSystemWindowInsetRight();
-        if (lp.leftMargin <= leftInset) {
-            lp.leftMargin = 0;
-        }
-        if (lp.rightMargin <= rightInset) {
-            lp.rightMargin = 0;
-        }
-    }
-
-    public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
-            Display display) {
-        if (cutout == null) {
-            return null;
-        }
-        Point size = new Point();
-        display.getRealSize(size);
-
-        Rect bounds = new Rect();
-        boundsFromDirection(cutout, Gravity.TOP, bounds);
-
-        if (bounds.left <= 0) {
-            return new Pair<>(bounds.right, 0);
-        }
-        if (bounds.right >= size.x) {
-            return new Pair<>(0, size.x - bounds.left);
-        }
-        return null;
+        setPadding(padding.first, getPaddingTop(), padding.second, getPaddingBottom());
     }
 
     public void setHeadsUpVisible(boolean headsUpVisible) {
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 823adff..6298afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -163,7 +163,6 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -199,6 +198,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -597,7 +597,7 @@
      * Public constructor for StatusBar.
      *
      * StatusBar is considered optional, and therefore can not be marked as @Inject directly.
-     * Instead, an @Provide method is included.
+     * Instead, an @Provide method is included. See {@link StatusBarPhoneModule}.
      */
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     public StatusBar(
@@ -1409,8 +1409,11 @@
         if (!mRecentsOptional.isPresent()) {
             return false;
         }
-        int dockSide = WindowManagerProxy.getInstance().getDockSide();
-        if (dockSide == WindowManager.DOCKED_INVALID) {
+        Divider divider = null;
+        if (mDividerOptional.isPresent()) {
+            divider = mDividerOptional.get();
+        }
+        if (divider == null || !divider.inSplitMode()) {
             final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
             if (navbarPos == NAV_BAR_POS_INVALID) {
                 return false;
@@ -1420,16 +1423,13 @@
                     : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
             return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction);
         } else {
-            if (mDividerOptional.isPresent()) {
-                Divider divider = mDividerOptional.get();
-                if (divider.isMinimized() && !divider.isHomeStackResizable()) {
-                    // Undocking from the minimized state is not supported
-                    return false;
-                } else {
-                    divider.onUndockingTask();
-                    if (metricsUndockAction != -1) {
-                        mMetricsLogger.action(metricsUndockAction);
-                    }
+            if (divider.isMinimized() && !divider.isHomeStackResizable()) {
+                // Undocking from the minimized state is not supported
+                return false;
+            } else {
+                divider.onUndockingTask();
+                if (metricsUndockAction != -1) {
+                    mMetricsLogger.action(metricsUndockAction);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 0f3b5db..e1a20b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
 
@@ -35,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -56,6 +58,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -66,7 +69,11 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -97,6 +104,9 @@
     private final KeyguardStateController mKeyguardStateController;
     private final ActivityStarter mActivityStarter;
     private final NotificationEntryManager mEntryManager;
+    private final NotifPipeline mNotifPipeline;
+    private final NotifCollection mNotifCollection;
+    private final FeatureFlags mFeatureFlags;
     private final StatusBarStateController mStatusBarStateController;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final MetricsLogger mMetricsLogger;
@@ -135,7 +145,9 @@
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
             Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
-            ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
+            ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
+            FeatureFlags featureFlags, NotifPipeline notifPipeline,
+            NotifCollection notifCollection) {
         mContext = context;
         mNotificationPanel = panel;
         mPresenter = presenter;
@@ -162,12 +174,25 @@
         mLockPatternUtils = lockPatternUtils;
         mBackgroundHandler = backgroundHandler;
         mUiBgExecutor = uiBgExecutor;
-        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-            @Override
-            public void onPendingEntryAdded(NotificationEntry entry) {
-                handleFullScreenIntent(entry);
-            }
-        });
+        mFeatureFlags = featureFlags;
+        mNotifPipeline = notifPipeline;
+        mNotifCollection = notifCollection;
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+                @Override
+                public void onPendingEntryAdded(NotificationEntry entry) {
+                    handleFullScreenIntent(entry);
+                }
+            });
+        } else {
+            mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+                @Override
+                public void onEntryAdded(NotificationEntry entry) {
+                    handleFullScreenIntent(entry);
+                }
+            });
+        }
+
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mMainThreadHandler = mainThreadHandler;
         mActivityIntentHelper = activityIntentHelper;
@@ -246,15 +271,14 @@
             mHeadsUpManager.removeNotification(sbn.getKey(),
                     true /* releaseImmediately */);
         }
-        StatusBarNotification parentToCancel = null;
+        NotificationEntry parentToCancel = null;
         if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-            StatusBarNotification summarySbn =
-                    mGroupManager.getLogicalGroupSummary(sbn).getSbn();
-            if (shouldAutoCancel(summarySbn)) {
+            NotificationEntry summarySbn = mGroupManager.getLogicalGroupSummary(sbn);
+            if (shouldAutoCancel(summarySbn.getSbn())) {
                 parentToCancel = summarySbn;
             }
         }
-        final StatusBarNotification parentToCancelFinal = parentToCancel;
+        final NotificationEntry parentToCancelFinal = parentToCancel;
         final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
                 sbn, row, controller, intent,
                 isActivityIntent, wasOccluded, parentToCancelFinal);
@@ -279,7 +303,7 @@
             PendingIntent intent,
             boolean isActivityIntent,
             boolean wasOccluded,
-            StatusBarNotification parentToCancelFinal) {
+            NotificationEntry parentToCancelFinal) {
         String notificationKey = sbn.getKey();
         try {
             // The intent we are sending is for the application, which
@@ -330,7 +354,7 @@
             collapseOnMainThread();
         }
 
-        final int count = mEntryManager.getActiveNotificationsCount();
+        final int count = getVisibleNotificationsCount();
         final int rank = entry.getRanking().getRank();
         NotificationVisibility.NotificationLocation location =
                 NotificationLogger.getNotificationLocation(entry);
@@ -341,15 +365,19 @@
         } catch (RemoteException ex) {
             // system process is dead if we're here.
         }
+
         if (!isBubble) {
             if (parentToCancelFinal != null) {
+                // TODO: (b/145659174) remove - this cancels the parent if the notification clicked
+                // on will auto-cancel and is the only child in the group. This won't be
+                // necessary in the new pipeline due to group pruning in ShadeListBuilder.
                 removeNotification(parentToCancelFinal);
             }
             if (shouldAutoCancel(sbn)
                     || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
                     notificationKey)) {
                 // Automatically remove all notifications that we may have kept around longer
-                removeNotification(sbn);
+                removeNotification(row.getEntry());
             }
         }
         mIsCollapsingToShowActivityOverLockscreen = false;
@@ -482,11 +510,10 @@
         return entry.shouldSuppressFullScreenIntent();
     }
 
-    private void removeNotification(StatusBarNotification notification) {
+    private void removeNotification(NotificationEntry entry) {
         // We have to post it to the UI thread for synchronization
         mMainThreadHandler.post(() -> {
-            Runnable removeRunnable =
-                    () -> mEntryManager.performRemoveNotification(notification, REASON_CLICK);
+            Runnable removeRunnable = createRemoveRunnable(entry);
             if (mPresenter.isCollapsing()) {
                 // To avoid lags we're only performing the remove
                 // after the shade was collapsed
@@ -497,6 +524,53 @@
         });
     }
 
+    // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+    private int getVisibleNotificationsCount() {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return mNotifPipeline.getShadeListCount();
+        } else {
+            return mEntryManager.getActiveNotificationsCount();
+        }
+    }
+
+    private Runnable createRemoveRunnable(NotificationEntry entry) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    // see NotificationLogger#logNotificationClear
+                    int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+                    if (mHeadsUpManager.isAlerting(entry.getKey())) {
+                        dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+                    } else if (mNotificationPanel.hasPulsingNotifications()) {
+                        dismissalSurface = NotificationStats.DISMISSAL_AOD;
+                    }
+
+                    mNotifCollection.dismissNotification(
+                            entry,
+                            new DismissedByUserStats(
+                                    dismissalSurface,
+                                    DISMISS_SENTIMENT_NEUTRAL,
+                                    NotificationVisibility.obtain(
+                                            entry.getKey(),
+                                            entry.getRanking().getRank(),
+                                            mNotifPipeline.getShadeListCount(),
+                                            true,
+                                            NotificationLogger.getNotificationLocation(entry))
+                            ));
+                }
+            };
+        } else {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    mEntryManager.performRemoveNotification(entry.getSbn(), REASON_CLICK);
+                }
+            };
+        }
+    }
+
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
@@ -506,6 +580,9 @@
         private final CommandQueue mCommandQueue;
         private final Lazy<AssistManager> mAssistManagerLazy;
         private final NotificationEntryManager mEntryManager;
+        private final FeatureFlags mFeatureFlags;
+        private final NotifPipeline mNotifPipeline;
+        private final NotifCollection mNotifCollection;
         private final HeadsUpManagerPhone mHeadsUpManager;
         private final ActivityStarter mActivityStarter;
         private final IStatusBarService mStatusBarService;
@@ -557,7 +634,10 @@
                 @UiBackground Executor uiBgExecutor,
                 ActivityIntentHelper activityIntentHelper,
                 BubbleController bubbleController,
-                ShadeController shadeController) {
+                ShadeController shadeController,
+                FeatureFlags featureFlags,
+                NotifPipeline notifPipeline,
+                NotifCollection notifCollection) {
             mContext = context;
             mCommandQueue = commandQueue;
             mAssistManagerLazy = assistManagerLazy;
@@ -583,6 +663,9 @@
             mActivityIntentHelper = activityIntentHelper;
             mBubbleController = bubbleController;
             mShadeController = shadeController;
+            mFeatureFlags = featureFlags;
+            mNotifPipeline = notifPipeline;
+            mNotifCollection = notifCollection;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -608,8 +691,6 @@
             return this;
         }
 
-
-
         public StatusBarNotificationActivityStarter build() {
             return new StatusBarNotificationActivityStarter(mContext,
                     mCommandQueue, mAssistManagerLazy,
@@ -638,7 +719,10 @@
                     mBackgroundHandler,
                     mUiBgExecutor,
                     mActivityIntentHelper,
-                    mBubbleController);
+                    mBubbleController,
+                    mFeatureFlags,
+                    mNotifPipeline,
+                    mNotifCollection);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index da5df6a..22bf513 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -16,8 +16,21 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.view.WindowInsets.Type.systemBars;
+
+import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+
 import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
 /**
@@ -28,7 +41,118 @@
     public static final String TAG = "PhoneStatusBarWindowView";
     public static final boolean DEBUG = StatusBar.DEBUG;
 
+    private int mLeftInset = 0;
+    private int mRightInset = 0;
+    private int mTopInset = 0;
+
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
+        final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+        mLeftInset = 0;
+        mRightInset = 0;
+        mTopInset = 0;
+        DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+        if (displayCutout != null) {
+            mTopInset = displayCutout.getWaterfallInsets().top;
+            mLeftInset = displayCutout.getSafeInsetLeft();
+            mRightInset = displayCutout.getSafeInsetRight();
+        }
+        mLeftInset = Math.max(insets.left, mLeftInset);
+        mRightInset = Math.max(insets.right, mRightInset);
+        applyMargins();
+        return windowInsets;
+    }
+
+    private void applyMargins() {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getLayoutParams() instanceof LayoutParams) {
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset
+                        || lp.topMargin != mTopInset) {
+                    lp.rightMargin = mRightInset;
+                    lp.leftMargin = mLeftInset;
+                    lp.topMargin = mTopInset;
+                    child.requestLayout();
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute the padding needed for status bar related views, e.g., PhoneStatusBar,
+     * QuickStatusBarHeader and KeyguardStatusBarView).
+     *
+     * @param cutout
+     * @param cornerCutoutPadding
+     * @param roundedCornerContentPadding
+     * @return
+     */
+    public static Pair<Integer, Integer> paddingNeededForCutoutAndRoundedCorner(
+            DisplayCutout cutout, Pair<Integer, Integer> cornerCutoutPadding,
+            int roundedCornerContentPadding) {
+        if (cutout == null) {
+            return new Pair<>(roundedCornerContentPadding, roundedCornerContentPadding);
+        }
+
+        // compute the padding needed for corner cutout.
+        final int leftMargin = cutout.getSafeInsetLeft();
+        final int rightMargin = cutout.getSafeInsetRight();
+        int leftCornerCutoutPadding = 0;
+        int rightCornerCutoutPadding = 0;
+        if (cornerCutoutPadding != null) {
+            if (cornerCutoutPadding.first > leftMargin) {
+                leftCornerCutoutPadding = cornerCutoutPadding.first - leftMargin;
+            }
+            if (cornerCutoutPadding.second > rightMargin) {
+                rightCornerCutoutPadding = cornerCutoutPadding.second - rightMargin;
+            }
+        }
+
+        // compute the padding needed for rounded corner
+        int leftRoundedCornerPadding = 0;
+        int rightRoundedCornerPadding = 0;
+        if (roundedCornerContentPadding > leftMargin) {
+            leftRoundedCornerPadding = roundedCornerContentPadding - leftMargin;
+        }
+        if (roundedCornerContentPadding > rightMargin) {
+            rightRoundedCornerPadding = roundedCornerContentPadding - rightMargin;
+        }
+
+        return new Pair<>(
+                Math.max(leftCornerCutoutPadding, leftRoundedCornerPadding),
+                Math.max(rightCornerCutoutPadding, rightRoundedCornerPadding));
+    }
+
+    /**
+     * Compute the corner cutout margins
+     *
+     * @param cutout
+     * @param display
+     * @return
+     */
+    public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
+            Display display) {
+        if (cutout == null) {
+            return null;
+        }
+        Point size = new Point();
+        display.getRealSize(size);
+
+        Rect bounds = new Rect();
+        boundsFromDirection(cutout, Gravity.TOP, bounds);
+
+        if (bounds.left <= 0) {
+            return new Pair<>(bounds.right, 0);
+        }
+        if (bounds.right >= size.x) {
+            return new Pair<>(0, size.x - bounds.left);
+        }
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
new file mode 100644
index 0000000..fcf698c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.dagger;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.IWindowManager;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * This module provides instances needed to construct {@link StatusBar}. These are moved to this
+ * separate from {@link StatusBarPhoneModule} module so that components that wish to build their own
+ * version of StatusBar can include just dependencies, without injecting StatusBar itself.
+ */
+@Module
+public interface StatusBarPhoneDependenciesModule {
+    /** */
+    @Singleton
+    @Provides
+    static AutoHideController newAutoHideController(Context context,
+            @Main Handler handler,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            IWindowManager iWindowManager) {
+        return new AutoHideController(context, handler, notificationRemoteInputManager,
+                iWindowManager);
+    }
+
+    /** */
+    @Singleton
+    @Provides
+    static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper(
+            RowContentBindStage bindStage) {
+        return new NotificationGroupAlertTransferHelper(bindStage);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 15a0e08..26459a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.phone.dagger;
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
@@ -52,7 +52,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.StatusBarDependenciesModule;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -65,7 +64,27 @@
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.phone.KeyguardLiftController;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
+import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -91,8 +110,8 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(includes = {StatusBarDependenciesModule.class})
-public class StatusBarModule {
+@Module(includes = {StatusBarPhoneDependenciesModule.class})
+public interface StatusBarPhoneModule {
     /**
      * Provides our instance of StatusBar which is considered optional.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index dea8c5d..edab4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -38,6 +38,7 @@
 import android.widget.Toast;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -67,12 +68,21 @@
 
     @Inject
     public ToastUI(Context context, CommandQueue commandQueue) {
+        this(context, commandQueue,
+                (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),
+                INotificationManager.Stub.asInterface(
+                        ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+                AccessibilityManager.getInstance(context));
+    }
+
+    @VisibleForTesting
+    ToastUI(Context context, CommandQueue commandQueue, WindowManager windowManager,
+            INotificationManager notificationManager, AccessibilityManager accessibilityManager) {
         super(context);
         mCommandQueue = commandQueue;
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-        mAccessibilityManager = AccessibilityManager.getInstance(context);
+        mWindowManager = windowManager;
+        mNotificationManager = notificationManager;
+        mAccessibilityManager = accessibilityManager;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
index 70bcc21..0487ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -98,6 +98,14 @@
     private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap()
 
     /**
+     * Whether we are currently resolving conflicts by asking content to move. If we are, we'll
+     * temporarily ignore calls to [onContentMoved] - those calls are from the content that is
+     * moving to new, conflict-free bounds, so we don't need to perform conflict detection
+     * calculations in response.
+     */
+    private var currentlyResolvingConflicts = false
+
+    /**
      * Makes the coordinator aware of a new piece of floating content, and moves any existing
      * content out of the way, if necessary.
      *
@@ -126,6 +134,13 @@
      */
     @JvmOverloads
     fun onContentMoved(content: FloatingContent) {
+
+        // Ignore calls when we are currently resolving conflicts, since those calls are from
+        // content that is moving to new, conflict-free bounds.
+        if (currentlyResolvingConflicts) {
+            return
+        }
+
         if (!allContentBounds.containsKey(content)) {
             Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " +
                     "This should never happen.")
@@ -162,6 +177,8 @@
      * them to move out of the way.
      */
     private fun maybeMoveConflictingContent(fromContent: FloatingContent) {
+        currentlyResolvingConflicts = true
+
         val conflictingNewBounds = allContentBounds[fromContent]!!
         allContentBounds
                 // Filter to content that intersects with the new bounds. That's content that needs
@@ -182,6 +199,8 @@
                                             .minus(conflictingNewBounds)))
                     allContentBounds[content] = content.getFloatingBoundsOnScreen()
                 }
+
+        currentlyResolvingConflicts = false
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 7dad05d..9227cf0 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -48,8 +48,8 @@
 public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
     private static final String TAG = "DisplayImeController";
 
-    static final int ANIMATION_DURATION_SHOW_MS = 275;
-    static final int ANIMATION_DURATION_HIDE_MS = 340;
+    public static final int ANIMATION_DURATION_SHOW_MS = 275;
+    public static final int ANIMATION_DURATION_HIDE_MS = 340;
     static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
     private static final int DIRECTION_NONE = 0;
     private static final int DIRECTION_SHOW = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
index 64b0b66..4652abf 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -35,6 +35,7 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.util.DisplayMetrics;
 import android.util.RotationUtils;
 import android.util.Size;
 import android.view.Display;
@@ -191,6 +192,11 @@
         return mDensityDpi;
     }
 
+    /** Get the density scale for the display. */
+    public float density() {
+        return mDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+    }
+
     /** Get whether this layout is landscape. */
     public boolean isLandscape() {
         return mWidth > mHeight;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 5706bee..79188ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -17,25 +17,18 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 
 import static com.android.systemui.ScreenDecorations.rectsToRegion;
-import static com.android.systemui.tuner.TunablePadding.FLAG_END;
-import static com.android.systemui.tuner.TunablePadding.FLAG_START;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Fragment;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -43,21 +36,12 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.Display;
-import android.view.View;
 import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R.dimen;
-import com.android.systemui.ScreenDecorations.TunablePaddingTagListener;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.tuner.TunablePadding;
-import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 
 import org.junit.Before;
@@ -68,8 +52,6 @@
 
 import java.util.Collections;
 
-import dagger.Lazy;
-
 @RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
@@ -77,19 +59,12 @@
 
     private TestableLooper mTestableLooper;
     private ScreenDecorations mScreenDecorations;
-    @Mock private StatusBar mStatusBar;
     private WindowManager mWindowManager;
-    private FragmentService mFragmentService;
-    private FragmentHostManager mFragmentHostManager;
-    private NotificationShadeWindowView mView;
-    private StatusBarWindowView mStatusBarWindowView;
-    private TunablePaddingService mTunablePaddingService;
     private Handler mMainHandler;
     @Mock
     private TunerService mTunerService;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock private Lazy<StatusBar> mStatusBarLazy;
 
     @Before
     public void setup() {
@@ -97,25 +72,14 @@
 
         mTestableLooper = TestableLooper.get(this);
         mMainHandler = new Handler(mTestableLooper.getLooper());
-        mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class);
-        mFragmentService = mDependency.injectMockDependency(FragmentService.class);
 
         mWindowManager = mock(WindowManager.class);
-        mView = spy(new NotificationShadeWindowView(mContext, null));
-        mStatusBarWindowView = spy(new StatusBarWindowView(mContext, null));
-        when(mStatusBarLazy.get()).thenReturn(mStatusBar);
-        when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mView);
-        when(mStatusBar.getStatusBarWindow()).thenReturn(mStatusBarWindowView);
 
         Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
         when(mWindowManager.getDefaultDisplay()).thenReturn(display);
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
 
-        mFragmentHostManager = mock(FragmentHostManager.class);
-        when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
-
-
-        mScreenDecorations = new ScreenDecorations(mContext, mStatusBarLazy, mMainHandler,
+        mScreenDecorations = new ScreenDecorations(mContext, mMainHandler,
                 mBroadcastDispatcher, mTunerService) {
             @Override
             public void start() {
@@ -159,8 +123,6 @@
         mScreenDecorations.start();
         // No views added.
         verify(mWindowManager, never()).addView(any(), any());
-        // No Fragments watched.
-        verify(mFragmentHostManager, never()).addTagListener(any(), any());
         // No Tuners tuned.
         verify(mTunerService, never()).addTunable(any(), any());
     }
@@ -178,12 +140,8 @@
         // Add 2 windows for rounded corners (top and bottom).
         verify(mWindowManager, times(2)).addView(any(), any());
 
-        // Add 2 tag listeners for each of the fragments that are needed.
-        verify(mFragmentHostManager, times(2)).addTagListener(any(), any());
         // One tunable.
         verify(mTunerService, times(1)).addTunable(any(), any());
-        // One TunablePadding.
-        verify(mTunablePaddingService, times(1)).add(any(), anyString(), anyInt(), anyInt());
     }
 
     @Test
@@ -227,29 +185,6 @@
     }
 
     @Test
-    public void testPaddingTagListener() {
-        TunablePaddingTagListener tagListener = new TunablePaddingTagListener(14, 5);
-        View v = mock(View.class);
-        View child = mock(View.class);
-        Fragment f = mock(Fragment.class);
-        TunablePadding padding = mock(TunablePadding.class);
-
-        when(mTunablePaddingService.add(any(), anyString(), anyInt(), anyInt()))
-                .thenReturn(padding);
-        when(f.getView()).thenReturn(v);
-        when(v.findViewById(5)).thenReturn(child);
-
-        // Trigger callback and verify we get a TunablePadding created.
-        tagListener.onFragmentViewCreated(null, f);
-        verify(mTunablePaddingService).add(eq(child), eq(ScreenDecorations.PADDING), eq(14),
-                eq(FLAG_START | FLAG_END));
-
-        // Call again and verify destroy is called.
-        tagListener.onFragmentViewCreated(null, f);
-        verify(padding).destroy();
-    }
-
-    @Test
     public void testUpdateRoundedCorners() {
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 7d47f6bd..fc79fcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -44,7 +44,6 @@
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
-import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.face.FaceManager;
 import android.service.notification.ZenModeConfig;
@@ -859,42 +858,6 @@
         verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
     }
 
-    static class TestableBubbleController extends BubbleController {
-        // Let's assume surfaces can be synchronized immediately.
-        TestableBubbleController(Context context,
-                NotificationShadeWindowController notificationShadeWindowController,
-                StatusBarStateController statusBarStateController,
-                ShadeController shadeController,
-                BubbleData data,
-                ConfigurationController configurationController,
-                NotificationInterruptionStateProvider interruptionStateProvider,
-                ZenModeController zenModeController,
-                NotificationLockscreenUserManager lockscreenUserManager,
-                NotificationGroupManager groupManager,
-                NotificationEntryManager entryManager,
-                NotifPipeline notifPipeline,
-                FeatureFlags featureFlags,
-                DumpController dumpController) {
-            super(context,
-                    notificationShadeWindowController, statusBarStateController, shadeController,
-                    data, Runnable::run, configurationController, interruptionStateProvider,
-                    zenModeController, lockscreenUserManager, groupManager, entryManager,
-                     notifPipeline, featureFlags, dumpController);
-            setInflateSynchronously(true);
-        }
-    }
-
-    static class TestableNotificationInterruptionStateProvider extends
-            NotificationInterruptionStateProvider {
-
-        TestableNotificationInterruptionStateProvider(Context context,
-                NotificationFilter filter, StatusBarStateController controller,
-                BatteryController batteryController) {
-            super(context, filter, controller, batteryController);
-            mUseHeadsUp = true;
-        }
-    }
-
     /**
      * Sets the bubble metadata flags for this entry. These ]flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 5a1bef9..24f8a7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -40,7 +40,6 @@
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
-import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.face.FaceManager;
 import android.service.notification.ZenModeConfig;
@@ -788,42 +787,6 @@
                 groupSummary.getEntry()));
     }
 
-    static class TestableBubbleController extends BubbleController {
-        // Let's assume surfaces can be synchronized immediately.
-        TestableBubbleController(Context context,
-                NotificationShadeWindowController notificationShadeWindowController,
-                StatusBarStateController statusBarStateController,
-                ShadeController shadeController,
-                BubbleData data,
-                ConfigurationController configurationController,
-                NotificationInterruptionStateProvider interruptionStateProvider,
-                ZenModeController zenModeController,
-                NotificationLockscreenUserManager lockscreenUserManager,
-                NotificationGroupManager groupManager,
-                NotificationEntryManager entryManager,
-                NotifPipeline notifPipeline,
-                FeatureFlags featureFlags,
-                DumpController dumpController) {
-            super(context,
-                    notificationShadeWindowController, statusBarStateController, shadeController,
-                    data, Runnable::run, configurationController, interruptionStateProvider,
-                    zenModeController, lockscreenUserManager, groupManager, entryManager,
-                    notifPipeline, featureFlags, dumpController);
-            setInflateSynchronously(true);
-        }
-    }
-
-    static class TestableNotificationInterruptionStateProvider extends
-            NotificationInterruptionStateProvider {
-
-        TestableNotificationInterruptionStateProvider(Context context,
-                NotificationFilter filter, StatusBarStateController controller,
-                BatteryController batteryController) {
-            super(context, filter, controller, batteryController);
-            mUseHeadsUp = true;
-        }
-    }
-
     /**
      * Sets the bubble metadata flags for this entry. These flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
new file mode 100644
index 0000000..338abf5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+/**
+ * Testable BubbleController subclass that immediately synchronizes surfaces.
+ */
+public class TestableBubbleController extends BubbleController {
+
+    // Let's assume surfaces can be synchronized immediately.
+    TestableBubbleController(Context context,
+            NotificationShadeWindowController notificationShadeWindowController,
+            StatusBarStateController statusBarStateController,
+            ShadeController shadeController,
+            BubbleData data,
+            ConfigurationController configurationController,
+            NotificationInterruptionStateProvider interruptionStateProvider,
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager lockscreenUserManager,
+            NotificationGroupManager groupManager,
+            NotificationEntryManager entryManager,
+            NotifPipeline notifPipeline,
+            FeatureFlags featureFlags,
+            DumpController dumpController) {
+        super(context,
+                notificationShadeWindowController, statusBarStateController, shadeController,
+                data, Runnable::run, configurationController, interruptionStateProvider,
+                zenModeController, lockscreenUserManager, groupManager, entryManager,
+                notifPipeline, featureFlags, dumpController);
+        setInflateSynchronously(true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java
new file mode 100644
index 0000000..5d192b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+public class TestableNotificationInterruptionStateProvider
+        extends NotificationInterruptionStateProvider {
+
+    TestableNotificationInterruptionStateProvider(Context context,
+            NotificationFilter filter, StatusBarStateController controller,
+            BatteryController batteryController) {
+        super(context, filter, controller, batteryController);
+        mUseHeadsUp = true;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 5a0a495..f1fba79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -282,6 +282,9 @@
                 .inflationCallback(any()))
                 .thenReturn(mExpandableNotificationRowComponentBuilder);
         when(mExpandableNotificationRowComponentBuilder
+                .rowContentBindStage(any()))
+                .thenReturn(mExpandableNotificationRowComponentBuilder);
+        when(mExpandableNotificationRowComponentBuilder
                 .onExpandClickListener(any()))
                 .thenReturn(mExpandableNotificationRowComponentBuilder);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 96db16a..12e9d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
@@ -33,8 +34,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -50,12 +51,12 @@
 import android.os.RemoteException;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
@@ -104,7 +105,6 @@
     @Spy private RecordingCollectionListener mCollectionListener;
     @Mock private CollectionReadyForBuildListener mBuildListener;
     @Mock private FeatureFlags mFeatureFlags;
-    @Mock private DismissedByUserStats mDismissedByUserStats;
 
     @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
     @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -424,13 +424,16 @@
     public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() {
         // GIVEN A notif group with one summary and two children
         mCollection.addNotificationLifetimeExtender(mExtender1);
-        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")
-                .setGroup(mContext, GROUP_1)
-                .setGroupSummary(mContext, true));
-        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag")
-                .setGroup(mContext, GROUP_1));
+        CollectionEvent notif1 = postNotif(
+                buildNotif(TEST_PACKAGE, 1, "myTag")
+                        .setGroup(mContext, GROUP_1)
+                        .setGroupSummary(mContext, true));
+        CollectionEvent notif2 = postNotif(
+                buildNotif(TEST_PACKAGE, 2, "myTag")
+                        .setGroup(mContext, GROUP_1));
+        CollectionEvent notif3 = postNotif(
+                buildNotif(TEST_PACKAGE, 3, "myTag")
+                        .setGroup(mContext, GROUP_1));
 
         NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
         NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
@@ -456,7 +459,7 @@
     }
 
     @Test
-    public void testDismissInterceptorsAreCalled() throws RemoteException {
+    public void testDismissNotificationCallsDismissInterceptors() throws RemoteException {
         // GIVEN a collection with notifications with multiple dismiss interceptors
         mInterceptor1.shouldInterceptDismissal = true;
         mInterceptor2.shouldInterceptDismissal = true;
@@ -469,10 +472,7 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // WHEN a notification is manually dismissed
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        DismissedByUserStats stats = defaultStats(entry);
         mCollection.dismissNotification(entry, stats);
 
         // THEN all interceptors get checked
@@ -506,10 +506,7 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // WHEN a notification is manually dismissed and intercepted
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        DismissedByUserStats stats = defaultStats(entry);
         mCollection.dismissNotification(entry, stats);
         assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
         clearInvocations(mInterceptor1, mInterceptor2);
@@ -531,7 +528,7 @@
                 eq(notif.sbn.getKey()),
                 anyInt(),
                 anyInt(),
-                anyObject());
+                eq(stats.notificationVisibility));
     }
 
     @Test
@@ -544,19 +541,16 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // GIVEN a notification is manually dismissed
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        DismissedByUserStats stats = defaultStats(entry);
         mCollection.dismissNotification(entry, stats);
 
         // WHEN all interceptors end their interception dismissal
         mInterceptor1.shouldInterceptDismissal = false;
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
-                mDismissedByUserStats);
+                stats);
 
         // THEN we send the dismissal to system server
-        verify(mStatusBarService, times(1)).onNotificationClear(
+        verify(mStatusBarService).onNotificationClear(
                 eq(notif.sbn.getPackageName()),
                 eq(notif.sbn.getTag()),
                 eq(47),
@@ -564,7 +558,7 @@
                 eq(notif.sbn.getKey()),
                 anyInt(),
                 anyInt(),
-                anyObject());
+                eq(stats.notificationVisibility));
     }
 
     @Test
@@ -581,16 +575,12 @@
         NotificationEntry entry = mCollectionListener.getEntry(notif.key);
 
         // GIVEN a notification is manually dismissed
-        DismissedByUserStats stats = new DismissedByUserStats(
-                NotificationStats.DISMISSAL_SHADE,
-                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
-        mCollection.dismissNotification(entry, stats);
+        mCollection.dismissNotification(entry, defaultStats(entry));
 
        // WHEN an interceptor ends its interception
         mInterceptor1.shouldInterceptDismissal = false;
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
-                mDismissedByUserStats);
+                defaultStats(entry));
 
         // THEN all interceptors get checked
         verify(mInterceptor1).shouldInterceptDismissal(entry);
@@ -613,7 +603,7 @@
 
         // WHEN we try to end the dismissal of an interceptor that didn't intercept the notif
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
-                mDismissedByUserStats);
+                defaultStats(entry));
 
         // THEN an exception is thrown
     }
@@ -636,11 +626,11 @@
     @Test
     public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
         // GIVEN a collection with two grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -657,11 +647,11 @@
     @Test
     public void testUpdatingDismissedSummaryBringsChildrenBack() {
         // GIVEN a collection with two grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -680,14 +670,14 @@
     @Test
     public void testDismissedChildrenAreNotResetByParentUpdate() {
         // GIVEN a collection with three grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
-        NotifEvent notif2 = mNoMan.postNotif(
+        CollectionEvent notif2 = postNotif(
                 buildNotif(TEST_PACKAGE, 2)
                         .setGroup(mContext, GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -709,11 +699,11 @@
     @Test
     public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() {
         // GIVEN a collection with two grouped notifs in it
-        NotifEvent notif0 = mNoMan.postNotif(
+        CollectionEvent notif0 = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setOverrideGroupKey(GROUP_1)
                         .setGroupSummary(mContext, true));
-        NotifEvent notif1 = mNoMan.postNotif(
+        CollectionEvent notif1 = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setOverrideGroupKey(GROUP_1));
         NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -1055,6 +1045,213 @@
         assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason);
     }
 
+    @Test
+    public void testDismissNotificationsRebuildsOnce() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+        clearInvocations(mBuildListener);
+
+        // WHEN both notifications are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN build list is only called one time
+        verify(mBuildListener).onBuildList(any(Collection.class));
+    }
+
+    @Test
+    public void testDismissNotificationsSentToSystemServer() throws RemoteException {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN both notifications are manually dismissed together
+        DismissedByUserStats stats1 = defaultStats(entry1);
+        DismissedByUserStats stats2 = defaultStats(entry2);
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN we send the dismissals to system server
+        verify(mStatusBarService).onNotificationClear(
+                notif1.sbn.getPackageName(),
+                notif1.sbn.getTag(),
+                47,
+                notif1.sbn.getUser().getIdentifier(),
+                notif1.sbn.getKey(),
+                stats1.dismissalSurface,
+                stats1.dismissalSentiment,
+                stats1.notificationVisibility);
+
+        verify(mStatusBarService).onNotificationClear(
+                notif2.sbn.getPackageName(),
+                notif2.sbn.getTag(),
+                88,
+                notif2.sbn.getUser().getIdentifier(),
+                notif2.sbn.getKey(),
+                stats2.dismissalSurface,
+                stats2.dismissalSentiment,
+                stats2.notificationVisibility);
+    }
+
+    @Test
+    public void testDismissNotificationsMarkedAsDismissed() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN both notifications are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN the entries are marked as dismissed
+        assertEquals(DISMISSED, entry1.getDismissState());
+        assertEquals(DISMISSED, entry2.getDismissState());
+    }
+
+    @Test
+    public void testDismissNotificationssCallsDismissInterceptors() {
+        // GIVEN a collection with notifications with multiple dismiss interceptors
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+        mInterceptor3.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+        mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN both notifications are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(new Pair(entry1, defaultStats(entry1)),
+                        new Pair(entry2, defaultStats(entry2))));
+
+        // THEN all interceptors get checked
+        verify(mInterceptor1).shouldInterceptDismissal(entry1);
+        verify(mInterceptor2).shouldInterceptDismissal(entry1);
+        verify(mInterceptor3).shouldInterceptDismissal(entry1);
+        verify(mInterceptor1).shouldInterceptDismissal(entry2);
+        verify(mInterceptor2).shouldInterceptDismissal(entry2);
+        verify(mInterceptor3).shouldInterceptDismissal(entry2);
+
+        assertEquals(List.of(mInterceptor1, mInterceptor2), entry1.mDismissInterceptors);
+        assertEquals(List.of(mInterceptor1, mInterceptor2), entry2.mDismissInterceptors);
+    }
+
+    @Test
+    public void testDismissAllNotificationsCallsRebuildOnce() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+        clearInvocations(mBuildListener);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+        // THEN build list is only called one time
+        verify(mBuildListener).onBuildList(any(Collection.class));
+    }
+
+    @Test
+    public void testDismissAllNotificationsSentToSystemServer() throws RemoteException {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+        // THEN we send the dismissal to system server
+        verify(mStatusBarService).onClearAllNotifications(
+                entry1.getSbn().getUser().getIdentifier());
+    }
+
+    @Test
+    public void testDismissAllNotificationsMarkedAsDismissed() {
+        // GIVEN a collection with a couple notifications
+        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+        // THEN the entries are marked as dismissed
+        assertEquals(DISMISSED, entry1.getDismissState());
+        assertEquals(DISMISSED, entry2.getDismissState());
+    }
+
+    @Test
+    public void testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs() {
+        // GIVEN a collection with one unclearable notification and one clearable notification
+        NotificationEntryBuilder notifEntryBuilder = buildNotif(TEST_PACKAGE, 47, "myTag");
+        notifEntryBuilder.modifyNotification(mContext)
+                .setFlag(FLAG_NO_CLEAR, true);
+        NotifEvent unclearabeNotif = mNoMan.postNotif(notifEntryBuilder);
+        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry unclearableEntry = mCollectionListener.getEntry(unclearabeNotif.key);
+        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+        // WHEN all notifications are dismissed for the user who posted both notifs
+        mCollection.dismissAllNotifications(unclearableEntry.getSbn().getUser().getIdentifier());
+
+        // THEN only the clearable entry is marked as dismissed
+        assertEquals(NOT_DISMISSED, unclearableEntry.getDismissState());
+        assertEquals(DISMISSED, entry2.getDismissState());
+    }
+
+    @Test
+    public void testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs() {
+        // GIVEN a collection with multiple dismiss interceptors
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+        mInterceptor3.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+        mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+        // GIVEN a collection with one unclearable and one clearable notification
+        NotifEvent unclearableNotif = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 47, "myTag")
+                        .setFlag(mContext, FLAG_NO_CLEAR, true));
+        NotificationEntry unclearable = mCollectionListener.getEntry(unclearableNotif.key);
+        NotifEvent clearableNotif = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 88, "myTag")
+                        .setFlag(mContext, FLAG_NO_CLEAR, false));
+        NotificationEntry clearable = mCollectionListener.getEntry(clearableNotif.key);
+
+        // WHEN all notifications are dismissed for the user who posted the notif
+        mCollection.dismissAllNotifications(clearable.getSbn().getUser().getIdentifier());
+
+        // THEN all interceptors get checked for the unclearable notification
+        verify(mInterceptor1).shouldInterceptDismissal(unclearable);
+        verify(mInterceptor2).shouldInterceptDismissal(unclearable);
+        verify(mInterceptor3).shouldInterceptDismissal(unclearable);
+        assertEquals(List.of(mInterceptor1, mInterceptor2), unclearable.mDismissInterceptors);
+
+        // THEN no interceptors get checked for the clearable notification
+        verify(mInterceptor1, never()).shouldInterceptDismissal(clearable);
+        verify(mInterceptor2, never()).shouldInterceptDismissal(clearable);
+        verify(mInterceptor3, never()).shouldInterceptDismissal(clearable);
+    }
+
     private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
         return new NotificationEntryBuilder()
                 .setPkg(pkg)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
index 408bba4..6408f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -59,7 +59,7 @@
         MockitoAnnotations.initMocks(this);
         CommonNotifCollection collection = mock(CommonNotifCollection.class);
 
-        mBindPipeline = new NotifBindPipeline(collection);
+        mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
         mBindPipeline.setStage(mStage);
 
         ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 1dfe7bc..a8c438a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -43,6 +44,7 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -83,13 +85,12 @@
                 any(NotificationMenuRowPlugin.MenuItem.class)))
                 .thenReturn(true);
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
-        mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
-        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mDependency.injectMockDependency(BubbleController.class);
 
         mHelper = new NotificationTestHelper(mContext, mDependency);
 
-        mBlockingHelperManager = new NotificationBlockingHelperManager(mContext);
+        mBlockingHelperManager = new NotificationBlockingHelperManager(
+                mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class));
         // By default, have the shade visible/expanded.
         mBlockingHelperManager.setNotificationShadeExpanded(1f);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 8a42e5f..149a95a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -50,10 +50,12 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.tests.R;
 
 import org.junit.Assert;
@@ -92,9 +94,14 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
+
+        final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class);
+        final SmartReplyController smartReplyController = mock(SmartReplyController.class);
         mNotificationInflater = new NotificationContentInflater(
                 mCache,
-                mock(NotificationRemoteInputManager.class));
+                mock(NotificationRemoteInputManager.class),
+                () -> smartReplyConstants,
+                () -> smartReplyController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index e8de10f7..27b263f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -51,7 +51,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
-import android.graphics.drawable.Drawable;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -60,12 +61,12 @@
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -112,7 +113,8 @@
     private NotificationEntry mBubbleEntry;
     @Mock
     private ShortcutInfo mShortcutInfo;
-    private Drawable mImage;
+    @Mock
+    private Bitmap mImage;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -134,6 +136,8 @@
     private NotificationGuts mNotificationGuts;
     @Mock
     private ShadeController mShadeController;
+    @Mock
+    private ConversationIconFactory mIconFactory;
 
     @Before
     public void setUp() throws Exception {
@@ -176,9 +180,8 @@
         when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
         List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
-        mImage = mContext.getDrawable(R.drawable.ic_remove);
-        when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
-                anyInt())).thenReturn(mImage);
+        when(mIconFactory.getConversationBitmap(any(ShortcutInfo.class), anyString(), anyInt()))
+                .thenReturn(mImage);
 
         mNotificationChannel = new NotificationChannel(
                 TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
@@ -224,9 +227,10 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
-        assertEquals(mImage, view.getDrawable());
+        assertEquals(mImage, ((BitmapDrawable) view.getDrawable()).getBitmap());
     }
 
     @Test
@@ -244,6 +248,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
         assertTrue(textView.getText().toString().contains("App Name"));
@@ -290,6 +295,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
         assertTrue(textView.getText().toString().contains(group.getName()));
@@ -312,6 +318,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -333,6 +340,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(GONE, nameView.getVisibility());
@@ -361,6 +369,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(VISIBLE, nameView.getVisibility());
@@ -385,6 +394,7 @@
                 },
                 null,
                 null,
+                mIconFactory,
                 true);
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -407,6 +417,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
@@ -430,6 +441,7 @@
                 },
                 null,
                 null,
+                mIconFactory,
                 false);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
@@ -449,6 +461,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
@@ -469,6 +482,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
@@ -491,9 +505,9 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
-
         // Promote it
         mNotificationInfo.findViewById(R.id.home).performClick();
         mTestableLooper.processAllMessages();
@@ -522,9 +536,9 @@
                 (View v, int hours) -> {
                     latch.countDown();
                 },
+                mIconFactory,
                 true);
 
-
         // Promote it
         mNotificationInfo.findViewById(R.id.snooze).performClick();
         mTestableLooper.processAllMessages();
@@ -551,6 +565,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         assertFalse(mBubbleEntry.isBubble());
@@ -583,6 +598,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         assertTrue(mBubbleEntry.isBubble());
@@ -613,6 +629,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         assertFalse(mBubbleEntry.isBubble());
@@ -641,9 +658,9 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
-
         ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
         assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
                 fave.getContentDescription().toString());
@@ -675,6 +692,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
@@ -708,6 +726,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
@@ -743,9 +762,9 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
-
         ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
         assertEquals(mContext.getString(R.string.notification_conversation_mute),
                 mute.getContentDescription().toString());
@@ -774,7 +793,9 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
+
         verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
     }
@@ -794,7 +815,9 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
+
         verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
     }
@@ -815,6 +838,7 @@
                 null,
                 null,
                 null,
+                mIconFactory,
                 true);
 
         mNotificationInfo.findViewById(R.id.mute).performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fd5512d..9a52ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.tests.R;
 
 import org.mockito.ArgumentCaptor;
@@ -98,7 +99,6 @@
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(BubbleController.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
-        dependency.injectMockDependency(SmartReplyController.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupManager = new NotificationGroupManager(mStatusBarStateController);
         mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
@@ -106,16 +106,19 @@
         mHeadsUpManager.setUp(null, mGroupManager, null, null);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
-
         NotificationContentInflater contentBinder = new NotificationContentInflater(
                 mock(NotifRemoteViewCache.class),
-                mock(NotificationRemoteInputManager.class));
+                mock(NotificationRemoteInputManager.class),
+                () -> mock(SmartReplyConstants.class),
+                () -> mock(SmartReplyController.class));
         contentBinder.setInflateSynchronously(true);
-        mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class));
+        mBindStage = new RowContentBindStage(contentBinder,
+                mock(NotifInflationErrorManager.class),
+                mock(RowContentBindStageLogger.class));
 
         CommonNotifCollection collection = mock(CommonNotifCollection.class);
 
-        mBindPipeline = new NotifBindPipeline(collection);
+        mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
         mBindPipeline.setStage(mBindStage);
 
         ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index d9fe655..0f2482c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -60,8 +60,10 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mRowContentBindStage = new RowContentBindStage(mBinder,
-                mock(NotifInflationErrorManager.class));
+        mRowContentBindStage = new RowContentBindStage(
+                mBinder,
+                mock(NotifInflationErrorManager.class),
+                mock(RowContentBindStageLogger.class));
         mRowContentBindStage.createStageParams(mEntry);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b16e52c..9ccee75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -70,6 +70,8 @@
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -133,6 +135,7 @@
     @Mock private NotificationSectionsManager mNotificationSectionsManager;
     @Mock private NotificationSection mNotificationSection;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private FeatureFlags mFeatureFlags;
     private UserChangedListener mUserChangedListener;
     private TestableNotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
@@ -182,9 +185,8 @@
                 mock(LeakDetector.class),
                 mock(ForegroundServiceDismissalFeatureController.class)
         );
-        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
-
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
         NotificationShelf notificationShelf = mock(NotificationShelf.class);
         when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
@@ -208,7 +210,11 @@
                 mZenModeController,
                 mNotificationSectionsManager,
                 mock(ForegroundServiceSectionController.class),
-                mock(ForegroundServiceDismissalFeatureController.class)
+                mock(ForegroundServiceDismissalFeatureController.class),
+                mFeatureFlags,
+                mock(NotifPipeline.class),
+                mEntryManager,
+                mock(NotifCollection.class)
         );
         verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
         mUserChangedListener = userChangedCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 5027610..1e4df27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -67,6 +68,8 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -114,6 +117,12 @@
     private BubbleController mBubbleController;
     @Mock
     private ShadeControllerImpl mShadeController;
+    @Mock
+    private FeatureFlags mFeatureFlags;
+    @Mock
+    private NotifPipeline mNotifPipeline;
+    @Mock
+    private NotifCollection mNotifCollection;
 
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -162,6 +171,7 @@
         mActiveNotifications.add(mBubbleNotificationRow.getEntry());
         when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
         mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
                 getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -175,11 +185,12 @@
                 mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
-                mActivityIntentHelper, mBubbleController, mShadeController))
+                mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
+                mNotifPipeline, mNotifCollection)
                 .setStatusBar(mStatusBar)
                 .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
                 .setNotificationPresenter(mock(NotificationPresenter.class))
-                .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+                .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
         .build();
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
new file mode 100644
index 0000000..d58f2c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.toast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
+import android.os.Binder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ToastUITest extends SysuiTestCase {
+    private static final String PACKAGE_NAME_1 = "com.example1.test";
+    private static final Binder TOKEN_1 = new Binder();
+    private static final Binder WINDOW_TOKEN_1 = new Binder();
+    private static final String PACKAGE_NAME_2 = "com.example2.test";
+    private static final Binder TOKEN_2 = new Binder();
+    private static final Binder WINDOW_TOKEN_2 = new Binder();
+    private static final String TEXT = "Hello World";
+    private static final int MESSAGE_RES_ID = R.id.message;
+
+    @Mock private CommandQueue mCommandQueue;
+    @Mock private WindowManager mWindowManager;
+    @Mock private INotificationManager mNotificationManager;
+    @Mock private AccessibilityManager mAccessibilityManager;
+    @Mock private ITransientNotificationCallback mCallback;
+    @Captor private ArgumentCaptor<View> mViewCaptor;
+    @Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor;
+    private ToastUI mToastUI;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mToastUI = new ToastUI(mContext, mCommandQueue, mWindowManager, mNotificationManager,
+                mAccessibilityManager);
+    }
+
+    @Test
+    public void testStart_addToastUIAsCallbackToCommandQueue() throws Exception {
+        mToastUI.start();
+
+        verify(mCommandQueue).addCallback(mToastUI);
+    }
+
+    @Test
+    public void testShowToast_addsCorrectViewToWindowManager() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+        verify(mWindowManager).addView(mViewCaptor.capture(), any());
+        View view = mViewCaptor.getValue();
+        assertThat(((TextView) view.findViewById(MESSAGE_RES_ID)).getText()).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+        verify(mWindowManager).addView(any(), mParamsCaptor.capture());
+        ViewGroup.LayoutParams params = mParamsCaptor.getValue();
+        assertThat(params).isInstanceOf(WindowManager.LayoutParams.class);
+        WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
+        assertThat(windowParams.packageName).isEqualTo(mContext.getPackageName());
+        assertThat(windowParams.getTitle()).isEqualTo("Toast");
+        assertThat(windowParams.token).isEqualTo(WINDOW_TOKEN_1);
+    }
+
+    @Test
+    public void testShowToast_callsCallback() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        verify(mCallback).onToastShown();
+    }
+
+    @Test
+    public void testShowToast_sendsAccessibilityEvent() throws Exception {
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+        ArgumentCaptor<AccessibilityEvent> eventCaptor = ArgumentCaptor.forClass(
+                AccessibilityEvent.class);
+        verify(mAccessibilityManager).sendAccessibilityEvent(eventCaptor.capture());
+        AccessibilityEvent event = eventCaptor.getValue();
+        assertThat(event.getEventType()).isEqualTo(
+                AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+        assertThat(event.getClassName()).isEqualTo(Toast.class.getName());
+        assertThat(event.getPackageName()).isEqualTo(PACKAGE_NAME_1);
+    }
+
+    @Test
+    public void testHideToast_removesView() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+        View view = verifyWmAddViewAndAttachToParent();
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+        verify(mWindowManager).removeViewImmediate(view);
+    }
+
+    @Test
+    public void testHideToast_finishesToken() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+    }
+
+    @Test
+    public void testHideToast_callsCallback() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+        verify(mCallback).onToastHidden();
+    }
+
+    @Test
+    public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_2);
+
+        verify(mCallback, never()).onToastHidden();
+    }
+
+    @Test
+    public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+
+        mToastUI.hideToast(PACKAGE_NAME_2, TOKEN_1);
+
+        verify(mCallback, never()).onToastHidden();
+    }
+
+    @Test
+    public void testShowToast_afterShowToast_hidesCurrentToast() throws Exception {
+        mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback);
+        View view = verifyWmAddViewAndAttachToParent();
+
+        mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
+
+        verify(mWindowManager).removeViewImmediate(view);
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+        verify(mCallback).onToastHidden();
+    }
+
+    private View verifyWmAddViewAndAttachToParent() {
+        ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
+        verify(mWindowManager).addView(viewCaptor.capture(), any());
+        View view = viewCaptor.getValue();
+        // Simulate attaching to view hierarchy
+        ViewGroup parent = new FrameLayout(mContext);
+        parent.addView(view);
+        return view;
+    }
+}
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index ab21a76..0e7e101 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -38,6 +38,6 @@
         "-Wunreachable-code",
     ],
     sanitize: {
-        cfi: true,
+       cfi: true,
     },
 }
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
index 89c4665..4dd00f1 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
@@ -19,8 +19,6 @@
 import android.util.Log;
 import android.webkit.PacProcessor;
 
-import com.android.internal.annotations.GuardedBy;
-
 /**
  * @hide
  */
@@ -28,10 +26,6 @@
     private static final String TAG = "PacWebView";
 
     private static final PacWebView sInstance = new PacWebView();
-
-    private Object mLock = new Object();
-
-    @GuardedBy("mLock")
     private PacProcessor mProcessor = PacProcessor.getInstance();
 
     public static PacWebView getInstance() {
@@ -39,20 +33,16 @@
     }
 
     @Override
-    public boolean setCurrentProxyScript(String script) {
-        synchronized (mLock) {
-            if (!mProcessor.setProxyScript(script)) {
-                Log.e(TAG, "Unable to parse proxy script.");
-                return false;
-            }
-            return true;
+    public synchronized boolean setCurrentProxyScript(String script) {
+        if (!mProcessor.setProxyScript(script)) {
+            Log.e(TAG, "Unable to parse proxy script.");
+            return false;
         }
+        return true;
     }
 
     @Override
-    public String makeProxyRequest(String url, String host) {
-        synchronized (mLock) {
-            return mProcessor.findProxyForUrl(url);
-        }
+    public synchronized String makeProxyRequest(String url, String host) {
+        return mProcessor.findProxyForUrl(url);
     }
 }
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 63dd99e..eda3fb9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -248,6 +248,10 @@
     // Package: android
     NOTE_LOCATION_CHANGED = 59;
 
+    // Notify user that a SIM is required to connect to Wifi network
+    // Package: android
+    NOTE_ID_WIFI_SIM_REQUIRED = 60;
+
     // 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/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 75ec4b0..6c8aaf4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,8 +16,9 @@
 
 package com.android.server.accessibility;
 
-import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP;
 import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
@@ -39,6 +40,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.graphics.GraphicBuffer;
+import android.graphics.ParcelableColorSpace;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -1021,13 +1023,15 @@
                 final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
                 final HardwareBuffer hardwareBuffer =
                         HardwareBuffer.createFromGraphicBuffer(graphicBuffer);
-                final int colorSpaceId = screenshotBuffer.getColorSpace().getId();
+                final ParcelableColorSpace colorSpace =
+                        new ParcelableColorSpace(screenshotBuffer.getColorSpace());
 
                 // Send back the result.
                 final Bundle payload = new Bundle();
                 payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
                         hardwareBuffer);
-                payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID, colorSpaceId);
+                payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
+                payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, SystemClock.uptimeMillis());
                 callback.sendResult(payload);
             }, null).recycleOnUse());
         } finally {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index b74be7e..a3b5a3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility.gestures;
 
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
@@ -24,6 +25,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
@@ -31,6 +33,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
@@ -132,6 +135,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this));
         // Three-finger taps.
         mMultiFingerGestures.add(
@@ -139,6 +145,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
         // Four-finger taps.
         mMultiFingerGestures.add(
@@ -146,6 +155,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
         // Two-finger swipes.
         mMultiFingerGestures.add(
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index 20def61..e5340f1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -42,10 +42,10 @@
     // The acceptable distance the pointer can move and still count as a tap.
     private int mTouchSlop;
     // A tap counts when target number of fingers are down and up once.
-    private int mCompletedTapCount;
+    protected int mCompletedTapCount;
     // A flag set to true when target number of fingers have touched down at once before.
     // Used to indicate what next finger action should be. Down when false and lift when true.
-    private boolean mIsTargetFingerCountReached = false;
+    protected boolean mIsTargetFingerCountReached = false;
     // Store initial down points for slop checking and update when next down if is inside slop.
     private PointF[] mBases;
     // The points in bases that already have slop checked when onDown or onPointerDown.
@@ -56,7 +56,11 @@
      * @throws IllegalArgumentException if <code>fingers<code/> is less than 2
      *                                  or <code>taps<code/> is not positive.
      */
-    MultiFingerMultiTap(Context context, int fingers, int taps, int gestureId,
+    MultiFingerMultiTap(
+            Context context,
+            int fingers,
+            int taps,
+            int gestureId,
             GestureMatcher.StateChangeListener listener) {
         super(gestureId, new Handler(context.getMainLooper()), listener);
         Preconditions.checkArgument(fingers >= 2);
@@ -117,8 +121,7 @@
         cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
 
         final PointF nearest = findNearestPoint(rawEvent, mTouchSlop, false);
-        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR)
-                && null != nearest) {
+        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && null != nearest) {
             // Increase current tap count when the user have all fingers lifted
             // within the tap timeout since the target number of fingers are down.
             if (mIsTargetFingerCountReached) {
@@ -169,8 +172,7 @@
         } else {
             nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true);
         }
-        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR)
-                && nearest != null) {
+        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && nearest != null) {
             // The user have all fingers down within the tap timeout since first finger down,
             // setting the timeout for fingers to be lifted.
             if (currentFingerCount == mTargetFingerCount) {
@@ -227,11 +229,11 @@
     }
 
     /**
-     * Find the nearest location to the given event in the bases.
-     * If no one found, it could be not inside {@code slop}, filtered or empty bases.
-     * When {@code filterMatched} is true, if the location of given event matches one of the points
-     * in {@link #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location
-     * will be added to {@link #mExcludedPointsForDownSlopChecked}.
+     * Find the nearest location to the given event in the bases. If no one found, it could be not
+     * inside {@code slop}, filtered or empty bases. When {@code filterMatched} is true, if the
+     * location of given event matches one of the points in {@link
+     * #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location will be
+     * added to {@link #mExcludedPointsForDownSlopChecked}.
      *
      * @param event to find nearest point in bases.
      * @param slop to check to the given location of the event.
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
new file mode 100644
index 0000000..7824fd9
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers
+ * and taps for each instance is specified in the constructor.
+ */
+class MultiFingerMultiTapAndHold extends MultiFingerMultiTap {
+
+    MultiFingerMultiTapAndHold(
+            Context context,
+            int fingers,
+            int taps,
+            int gestureId,
+            GestureMatcher.StateChangeListener listener) {
+        super(context, fingers, taps, gestureId, listener);
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        super.onPointerDown(event, rawEvent, policyFlags);
+        if (mIsTargetFingerCountReached && mCompletedTapCount + 1 == mTargetTapCount) {
+            completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mCompletedTapCount + 1 == mTargetFingerCount) {
+            // Calling super.onUp  would complete the multi-tap version of this.
+            cancelGesture(event, rawEvent, policyFlags);
+        } else {
+            super.onUp(event, rawEvent, policyFlags);
+            cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    public String getGestureName() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(mTargetFingerCount).append("-Finger ");
+        if (mTargetTapCount == 1) {
+            builder.append("Single");
+        } else if (mTargetTapCount == 2) {
+            builder.append("Double");
+        } else if (mTargetTapCount == 3) {
+            builder.append("Triple");
+        } else if (mTargetTapCount > 3) {
+            builder.append(mTargetTapCount);
+        }
+        return builder.append(" Tap and hold").toString();
+    }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 4f49fb7..0b3899d 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.appprediction;
 
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
@@ -30,12 +32,17 @@
 import android.content.pm.ServiceInfo;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.provider.DeviceConfig;
 import android.service.appprediction.AppPredictionService;
+import android.service.appprediction.IPredictionService;
 import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.LocalServices;
 import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.people.PeopleServiceInternal;
 
 import java.util.function.Consumer;
 
@@ -47,6 +54,8 @@
              implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
 
     private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
+    private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
+            "predict_using_people_service_";
 
     @Nullable
     @GuardedBy("mLock")
@@ -104,14 +113,16 @@
     @GuardedBy("mLock")
     public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
             @NonNull AppPredictionSessionId sessionId) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.onCreatePredictionSession(context, sessionId);
-
-            if (!mSessionInfos.containsKey(sessionId)) {
-                mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
-                        this::removeAppPredictionSessionInfo));
-            }
+        if (!mSessionInfos.containsKey(sessionId)) {
+            mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
+                    DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+                            PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false),
+                    this::removeAppPredictionSessionInfo));
+        }
+        final boolean serviceExists = resolveService(sessionId, s ->
+                s.onCreatePredictionSession(context, sessionId));
+        if (!serviceExists) {
+            mSessionInfos.remove(sessionId);
         }
     }
 
@@ -121,10 +132,7 @@
     @GuardedBy("mLock")
     public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull AppTargetEvent event) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.notifyAppTargetEvent(sessionId, event);
-        }
+        resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event));
     }
 
     /**
@@ -133,10 +141,8 @@
     @GuardedBy("mLock")
     public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.notifyLaunchLocationShown(sessionId, launchLocation, targetIds);
-        }
+        resolveService(sessionId, s ->
+                s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
     }
 
     /**
@@ -145,10 +151,7 @@
     @GuardedBy("mLock")
     public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.sortAppTargets(sessionId, targets, callback);
-        }
+        resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback));
     }
 
     /**
@@ -157,14 +160,11 @@
     @GuardedBy("mLock")
     public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.registerPredictionUpdates(sessionId, callback);
-
-            AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-            if (sessionInfo != null) {
-                sessionInfo.addCallbackLocked(callback);
-            }
+        final boolean serviceExists = resolveService(sessionId, s ->
+                s.registerPredictionUpdates(sessionId, callback));
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (serviceExists && sessionInfo != null) {
+            sessionInfo.addCallbackLocked(callback);
         }
     }
 
@@ -174,14 +174,11 @@
     @GuardedBy("mLock")
     public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.unregisterPredictionUpdates(sessionId, callback);
-
-            AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-            if (sessionInfo != null) {
-                sessionInfo.removeCallbackLocked(callback);
-            }
+        final boolean serviceExists = resolveService(sessionId, s ->
+                s.unregisterPredictionUpdates(sessionId, callback));
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (serviceExists && sessionInfo != null) {
+            sessionInfo.removeCallbackLocked(callback);
         }
     }
 
@@ -190,10 +187,7 @@
      */
     @GuardedBy("mLock")
     public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.requestPredictionUpdate(sessionId);
-        }
+        resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId));
     }
 
     /**
@@ -201,14 +195,11 @@
      */
     @GuardedBy("mLock")
     public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
-        final RemoteAppPredictionService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.onDestroyPredictionSession(sessionId);
-
-            AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-            if (sessionInfo != null) {
-                sessionInfo.destroy();
-            }
+        final boolean serviceExists = resolveService(sessionId, s ->
+                s.onDestroyPredictionSession(sessionId));
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (serviceExists && sessionInfo != null) {
+            sessionInfo.destroy();
         }
     }
 
@@ -312,6 +303,33 @@
 
     @GuardedBy("mLock")
     @Nullable
+    protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId,
+            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) {
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return false;
+        if (sessionInfo.mUsesPeopleService) {
+            final IPredictionService service =
+                    LocalServices.getService(PeopleServiceInternal.class);
+            if (service != null) {
+                try {
+                    cb.run(service);
+                } catch (RemoteException e) {
+                    // Shouldn't happen.
+                    Slog.w(TAG, "Failed to invoke service:" + service, e);
+                }
+            }
+            return service != null;
+        } else {
+            final RemoteAppPredictionService service = getRemoteServiceLocked();
+            if (service != null) {
+                service.scheduleOnResolvedService(cb);
+            }
+            return service != null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
     private RemoteAppPredictionService getRemoteServiceLocked() {
         if (mRemoteService == null) {
             final String serviceName = getComponentNameLocked();
@@ -334,8 +352,12 @@
     private static final class AppPredictionSessionInfo {
         private static final boolean DEBUG = false;  // Do not submit with true
 
+        @NonNull
         private final AppPredictionSessionId mSessionId;
+        @NonNull
         private final AppPredictionContext mPredictionContext;
+        private final boolean mUsesPeopleService;
+        @NonNull
         private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction;
 
         private final RemoteCallbackList<IPredictionCallback> mCallbacks =
@@ -352,13 +374,17 @@
                     }
                 };
 
-        AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext predictionContext,
-                Consumer<AppPredictionSessionId> removeSessionInfoAction) {
+        AppPredictionSessionInfo(
+                @NonNull final AppPredictionSessionId id,
+                @NonNull final AppPredictionContext predictionContext,
+                final boolean usesPeopleService,
+                @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) {
             if (DEBUG) {
                 Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id);
             }
             mSessionId = id;
             mPredictionContext = predictionContext;
+            mUsesPeopleService = usesPeopleService;
             mRemoveSessionInfoAction = removeSessionInfoAction;
         }
 
diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
index 04e0e7f..ceb1caf 100644
--- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
+++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
@@ -16,13 +16,8 @@
 package com.android.server.appprediction;
 
 import android.annotation.NonNull;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionSessionId;
-import android.app.prediction.AppTargetEvent;
-import android.app.prediction.IPredictionCallback;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.os.IBinder;
 import android.service.appprediction.IPredictionService;
 import android.text.format.DateUtils;
@@ -71,70 +66,6 @@
     }
 
     /**
-     * Notifies the service of a new prediction session.
-     */
-    public void onCreatePredictionSession(@NonNull AppPredictionContext context,
-            @NonNull AppPredictionSessionId sessionId) {
-        scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId));
-    }
-
-    /**
-     * Records an app target event to the service.
-     */
-    public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
-            @NonNull AppTargetEvent event) {
-        scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event));
-    }
-
-    /**
-     * Records when a launch location is shown.
-     */
-    public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId,
-            @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
-        scheduleAsyncRequest((s)
-                -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
-    }
-
-    /**
-     * Requests the service to sort a list of apps or shortcuts.
-     */
-    public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
-            @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
-        scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback));
-    }
-
-
-    /**
-     * Registers a callback for continuous updates of predicted apps or shortcuts.
-     */
-    public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
-            @NonNull IPredictionCallback callback) {
-        scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback));
-    }
-
-    /**
-     * Unregisters a callback for continuous updates of predicted apps or shortcuts.
-     */
-    public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
-            @NonNull IPredictionCallback callback) {
-        scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback));
-    }
-
-    /**
-     * Requests a new set of predicted apps or shortcuts.
-     */
-    public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
-        scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId));
-    }
-
-    /**
-     * Notifies the service of the end of an existing prediction session.
-     */
-    public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
-        scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId));
-    }
-
-    /**
      * Schedules a request to bind to the remote service.
      */
     public void reconnect() {
@@ -142,6 +73,13 @@
     }
 
     /**
+     * Schedule async request on remote service.
+     */
+    public void scheduleOnResolvedService(@NonNull AsyncRequest<IPredictionService> request) {
+        scheduleAsyncRequest(request);
+    }
+
+    /**
      * Failure callback
      */
     public interface RemoteAppPredictionServiceCallbacks
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4474f60..872f0eb 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -119,6 +119,9 @@
     private final LocalLog mUiLatencyHistory;
     private final LocalLog mWtfHistory;
     private final FieldClassificationStrategy mFieldClassificationStrategy;
+
+    @GuardedBy("mLock")
+    @Nullable
     private RemoteInlineSuggestionRenderService mRemoteInlineSuggestionRenderService;
 
     /**
@@ -236,17 +239,8 @@
             sendStateToClients(/* resetClient= */ false);
         }
         updateRemoteAugmentedAutofillService();
+        updateRemoteInlineSuggestionRenderServiceLocked();
 
-        final ComponentName componentName = RemoteInlineSuggestionRenderService
-                .getServiceComponentName(getContext(), mUserId);
-        if (componentName != null) {
-            mRemoteInlineSuggestionRenderService = new RemoteInlineSuggestionRenderService(
-                    getContext(), componentName, InlineSuggestionRenderService.SERVICE_INTERFACE,
-                    mUserId, new InlineSuggestionRenderCallbacksImpl(),
-                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
-        } else {
-            mRemoteInlineSuggestionRenderService = null;
-        }
         return enabledChanged;
     }
 
@@ -1644,7 +1638,29 @@
         return mFieldClassificationStrategy.getDefaultAlgorithm();
     }
 
-    RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderService() {
+    private void updateRemoteInlineSuggestionRenderServiceLocked() {
+        if (mRemoteInlineSuggestionRenderService != null) {
+            if (sVerbose) {
+                Slog.v(TAG, "updateRemoteInlineSuggestionRenderService(): "
+                        + "destroying old remote service");
+            }
+            mRemoteInlineSuggestionRenderService = null;
+        }
+
+        mRemoteInlineSuggestionRenderService = getRemoteInlineSuggestionRenderServiceLocked();
+    }
+
+    RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderServiceLocked() {
+        final ComponentName componentName = RemoteInlineSuggestionRenderService
+                .getServiceComponentName(getContext(), mUserId);
+
+        if (mRemoteInlineSuggestionRenderService == null) {
+            mRemoteInlineSuggestionRenderService = new RemoteInlineSuggestionRenderService(
+                    getContext(), componentName, InlineSuggestionRenderService.SERVICE_INTERFACE,
+                    mUserId, new InlineSuggestionRenderCallbacksImpl(),
+                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+        }
+
         return mRemoteInlineSuggestionRenderService;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index d93ac68..1eb7692 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -146,7 +146,8 @@
             @Nullable AutofillValue focusedValue,
             @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
-            @NonNull Runnable onErrorCallback) {
+            @NonNull Runnable onErrorCallback,
+            @NonNull RemoteInlineSuggestionRenderService remoteRenderService) {
         long requestTime = SystemClock.elapsedRealtime();
         AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
 
@@ -168,7 +169,7 @@
                                     maybeRequestShowInlineSuggestions(sessionId,
                                             inlineSuggestionsRequest, inlineSuggestionsData,
                                             focusedId, inlineSuggestionsCallback, client,
-                                            onErrorCallback);
+                                            onErrorCallback, remoteRenderService);
                                     requestAutofill.complete(null);
                                 }
 
@@ -234,7 +235,8 @@
             @Nullable InlineSuggestionsRequest request, @Nullable Dataset[] inlineSuggestionsData,
             @NonNull AutofillId focusedId,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
-            @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) {
+            @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
+            @NonNull RemoteInlineSuggestionRenderService remoteRenderService) {
         if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null
                 || request == null) {
             return;
@@ -254,7 +256,7 @@
                                 } catch (RemoteException e) {
                                     Slog.w(TAG, "Encounter exception autofilling the values");
                                 }
-                            }, onErrorCallback));
+                            }, onErrorCallback, remoteRenderService));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
         }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
index 31dc23f..5d5af53 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
@@ -36,7 +36,10 @@
 
 import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
 
-final class RemoteInlineSuggestionRenderService extends
+/**
+ * Remote service to help connect to InlineSuggestionRenderService in ExtServices.
+ */
+public final class RemoteInlineSuggestionRenderService extends
         AbstractMultiplePendingRequestsRemoteService<RemoteInlineSuggestionRenderService,
                 IInlineSuggestionRenderService> {
 
@@ -81,9 +84,11 @@
      * Called by {@link Session} to generate a call to the
      * {@link RemoteInlineSuggestionRenderService} to request rendering a slice .
      */
-    void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
-            @NonNull InlinePresentation presentation, int width, int height) {
-        scheduleAsyncRequest((s) -> s.renderSuggestion(callback, presentation, width, height));
+    public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
+            @NonNull InlinePresentation presentation, int width, int height,
+            @Nullable IBinder hostInputToken) {
+        scheduleAsyncRequest(
+                (s) -> s.renderSuggestion(callback, presentation, width, height, hostInputToken));
     }
 
     @Nullable
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 72029d1..697e5d7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1018,7 +1018,8 @@
 
     // FillServiceCallbacks
     @Override
-    public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
+    public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras,
+            boolean authenticateInline) {
         if (sDebug) {
             Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
                     + "; intentSender=" + intent);
@@ -1042,7 +1043,7 @@
         final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
         mHandler.sendMessage(obtainMessage(
                 Session::startAuthentication,
-                this, authenticationId, intent, fillInIntent));
+                this, authenticationId, intent, fillInIntent, authenticateInline));
     }
 
     // VultureCallback
@@ -2623,8 +2624,6 @@
                 mService.getServicePackageName(), mComponentName,
                 serviceLabel, serviceIcon, this, id, mCompatMode);
 
-        mService.logDatasetShown(id, mClientState);
-
         synchronized (mLock) {
             if (mUiShownTime == 0) {
                 // Log first time UI is shown.
@@ -2656,10 +2655,6 @@
     private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
             @Nullable String filterText) {
         final List<Dataset> datasets = response.getDatasets();
-        if (datasets == null) {
-            Log.w(TAG, "response returned null datasets");
-            return false;
-        }
 
         final InlineSuggestionSession.ImeResponse imeResponse =
                 mInlineSuggestionSession.waitAndGetImeResponse();
@@ -2671,13 +2666,12 @@
         final InlineSuggestionsRequest request = imeResponse.getRequest();
         InlineSuggestionsResponse inlineSuggestionsResponse =
                 InlineSuggestionFactory.createInlineSuggestionsResponse(request,
-                        response.getRequestId(),
-                        datasets.toArray(new Dataset[]{}), filterText, response.getInlineActions(),
-                        mCurrentViewId, mContext, this, () -> {
+                        response, filterText, response.getInlineActions(), mCurrentViewId, mContext,
+                        this, () -> {
                             synchronized (mLock) {
                                 requestHideFillUi(mCurrentViewId);
                             }
-                        });
+                        }, mService.getRemoteInlineSuggestionRenderServiceLocked());
         try {
             imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse);
         } catch (RemoteException e) {
@@ -2981,7 +2975,7 @@
                     synchronized (mLock) {
                         cancelAugmentedAutofillLocked();
                     }
-                });
+                }, mService.getRemoteInlineSuggestionRenderServiceLocked());
 
         if (mAugmentedAutofillDestroyer == null) {
             mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
@@ -3160,7 +3154,8 @@
             }
             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
                     datasetIndex);
-            startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
+            startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent,
+                    /* authenticateInline= */false);
 
         }
     }
@@ -3184,10 +3179,11 @@
     }
 
     private void startAuthentication(int authenticationId, IntentSender intent,
-            Intent fillInIntent) {
+            Intent fillInIntent, boolean authenticateInline) {
         try {
             synchronized (mLock) {
-                mClient.authenticate(id, authenticationId, intent, fillInIntent);
+                mClient.authenticate(id, authenticationId, intent, fillInIntent,
+                        authenticateInline);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Error launching auth intent", e);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 26bb7c3..71c3c16 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -79,7 +79,7 @@
 
     public interface AutoFillUiCallback {
         void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent,
-                @Nullable Bundle extras);
+                @Nullable Bundle extras, boolean authenticateInline);
         void fill(int requestId, int datasetIndex, @NonNull Dataset dataset);
         void save();
         void cancelSave();
@@ -217,7 +217,8 @@
                     if (mCallback != null) {
                         mCallback.authenticate(response.getRequestId(),
                                 AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
-                                response.getAuthentication(), response.getClientState());
+                                response.getAuthentication(), response.getClientState(),
+                                /* authenticateInline= */ false);
                     }
                 }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 5f6e47b..a8886fc 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -22,14 +22,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.IInlineSuggestionUiCallback;
 import android.service.autofill.InlinePresentation;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.view.SurfaceControl;
-import android.view.View;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 import android.view.inline.InlinePresentationSpec;
 import android.view.inputmethod.InlineSuggestion;
@@ -40,11 +43,14 @@
 
 import com.android.internal.view.inline.IInlineContentCallback;
 import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.server.LocalServices;
 import com.android.server.UiThread;
+import com.android.server.autofill.RemoteInlineSuggestionRenderService;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.BiFunction;
+import java.util.function.BiConsumer;
 import java.util.regex.Pattern;
 
 public final class InlineSuggestionFactory {
@@ -61,8 +67,42 @@
     }
 
     /**
-     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by
-     * augmented autofill service.
+     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
+     * autofill service, potentially filtering the datasets.
+     */
+    public static InlineSuggestionsResponse createInlineSuggestionsResponse(
+            @NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
+            @Nullable String filterText,
+            @Nullable List<InlinePresentation> inlineActions,
+            @NonNull AutofillId autofillId,
+            @NonNull Context context,
+            @NonNull AutoFillUI.AutoFillUiCallback client,
+            @NonNull Runnable onErrorCallback,
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
+        final BiConsumer<Dataset, Integer> onClickFactory;
+        if (response.getAuthentication() != null) {
+            onClickFactory = (dataset, datasetIndex) -> client.authenticate(response.getRequestId(),
+                    datasetIndex, response.getAuthentication(), response.getClientState(),
+                    /* authenticateInline= */ true);
+        } else {
+            onClickFactory = (dataset, datasetIndex) ->
+                    client.fill(response.getRequestId(), datasetIndex, dataset);
+        }
+
+        final List<Dataset> datasetList = response.getDatasets();
+        final Dataset[] datasets = datasetList == null
+                ? null
+                : datasetList.toArray(new Dataset[]{});
+
+        return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, response,
+                datasets, filterText, inlineActions, autofillId, context, onErrorCallback,
+                onClickFactory, remoteRenderService);
+    }
+
+    /**
+     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by augmented
+     * autofill service.
      */
     public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse(
             @NonNull InlineSuggestionsRequest request,
@@ -70,44 +110,42 @@
             @NonNull AutofillId autofillId,
             @NonNull Context context,
             @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
-            @NonNull Runnable onErrorCallback) {
+            @NonNull Runnable onErrorCallback,
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
         if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
         return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request,
-                datasets, /* filterText= */ null, /* inlineActions= */ null, autofillId, context,
-                onErrorCallback,
-                (dataset, filedIndex) -> (v -> inlineSuggestionUiCallback.autofill(dataset)));
-    }
-
-    /**
-     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
-     * autofill service, potentially filtering the datasets.
-     */
-    public static InlineSuggestionsResponse createInlineSuggestionsResponse(
-            @NonNull InlineSuggestionsRequest request, int requestId,
-            @NonNull Dataset[] datasets,
-            @Nullable String filterText,
-            @Nullable List<InlinePresentation> inlineActions,
-            @NonNull AutofillId autofillId,
-            @NonNull Context context,
-            @NonNull AutoFillUI.AutoFillUiCallback client,
-            @NonNull Runnable onErrorCallback) {
-        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
-        return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, datasets,
-                filterText, inlineActions, autofillId, context, onErrorCallback,
-                (dataset, filedIndex) -> (v -> client.fill(requestId, filedIndex, dataset)));
+                /* fillResponse= */ null, datasets, /* filterText= */ null,
+                /* inlineActions= */ null, autofillId, context, onErrorCallback,
+                (dataset, fieldIndex) ->
+                        inlineSuggestionUiCallback.autofill(dataset), remoteRenderService);
     }
 
     private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
             boolean isAugmented, @NonNull InlineSuggestionsRequest request,
-            @NonNull Dataset[] datasets, @Nullable String filterText,
+            @Nullable FillResponse response, @Nullable Dataset[] datasets,
+            @Nullable String filterText,
             @Nullable List<InlinePresentation> inlineActions, @NonNull AutofillId autofillId,
             @NonNull Context context, @NonNull Runnable onErrorCallback,
-            @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
+            @NonNull BiConsumer<Dataset, Integer> onClickFactory,
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+
         final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
-                onErrorCallback);
-        for (int i = 0; i < datasets.length; i++) {
-            final Dataset dataset = datasets[i];
+        if (response.getAuthentication() != null) {
+            InlineSuggestion inlineAuthSuggestion = createInlineAuthSuggestion(response,
+                    remoteRenderService, onClickFactory, onErrorCallback,
+                    request.getHostInputToken());
+            inlineSuggestions.add(inlineAuthSuggestion);
+
+            return new InlineSuggestionsResponse(inlineSuggestions);
+        }
+
+        if (datasets == null) {
+            Slog.w(TAG, "Datasets should not be null here");
+            return null;
+        }
+
+        for (int datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
+            final Dataset dataset = datasets[datasetIndex];
             final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
             if (fieldIndex < 0) {
                 Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
@@ -123,15 +161,17 @@
                 continue;
             }
             InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
-                    fieldIndex, mergedInlinePresentation(request, i, inlinePresentation),
-                    inlineSuggestionUi, onClickListenerFactory);
+                    fieldIndex, mergedInlinePresentation(request, datasetIndex, inlinePresentation),
+                    onClickFactory, remoteRenderService, onErrorCallback,
+                    request.getHostInputToken());
+
             inlineSuggestions.add(inlineSuggestion);
         }
         if (inlineActions != null) {
             for (InlinePresentation inlinePresentation : inlineActions) {
                 final InlineSuggestion inlineAction = createInlineAction(isAugmented, context,
                         mergedInlinePresentation(request, 0, inlinePresentation),
-                        inlineSuggestionUi);
+                        remoteRenderService, onErrorCallback, request.getHostInputToken());
                 inlineSuggestions.add(inlineAction);
             }
         }
@@ -173,43 +213,59 @@
     private static InlineSuggestion createInlineAction(boolean isAugmented,
             @NonNull Context context,
             @NonNull InlinePresentation inlinePresentation,
-            @NonNull InlineSuggestionUi inlineSuggestionUi) {
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            @NonNull Runnable onErrorCallback, @Nullable IBinder hostInputToken) {
         // TODO(b/146453195): fill in the autofill hint properly.
         final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
                 inlinePresentation.getInlinePresentationSpec(),
                 isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
                         : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
                 InlineSuggestionInfo.TYPE_ACTION);
-        final View.OnClickListener onClickListener = v -> {
-            // TODO(b/148567875): Launch the intent provided through the slice. This
-            //  should be part of the UI renderer therefore will be moved to the support
-            //  library.
+        final Runnable onClickAction = () -> {
             Toast.makeText(context, "icon clicked", Toast.LENGTH_SHORT).show();
         };
         return new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
-                        onClickListener));
+                createInlineContentProvider(inlinePresentation, onClickAction, onErrorCallback,
+                        remoteRenderService, hostInputToken));
     }
 
     private static InlineSuggestion createInlineSuggestion(boolean isAugmented,
-            @NonNull Dataset dataset,
-            int fieldIndex, @NonNull InlinePresentation inlinePresentation,
-            @NonNull InlineSuggestionUi inlineSuggestionUi,
-            @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
+            @NonNull Dataset dataset, int datasetIndex,
+            @NonNull InlinePresentation inlinePresentation,
+            @NonNull BiConsumer<Dataset, Integer> onClickFactory,
+            @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+            @NonNull Runnable onErrorCallback, @Nullable IBinder hostInputToken) {
         // TODO(b/146453195): fill in the autofill hint properly.
         final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
                 inlinePresentation.getInlinePresentationSpec(),
                 isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
                         : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
                 InlineSuggestionInfo.TYPE_SUGGESTION);
-        final View.OnClickListener onClickListener = onClickListenerFactory.apply(dataset,
-                fieldIndex);
+
         final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
-                        onClickListener));
+                createInlineContentProvider(inlinePresentation,
+                        () -> onClickFactory.accept(dataset, datasetIndex), onErrorCallback,
+                        remoteRenderService, hostInputToken));
+
         return inlineSuggestion;
     }
 
+    private static InlineSuggestion createInlineAuthSuggestion(@NonNull FillResponse response,
+            @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+            @NonNull BiConsumer<Dataset, Integer> onClickFactory, @NonNull Runnable onErrorCallback,
+            @Nullable IBinder hostInputToken) {
+        final InlinePresentation inlinePresentation = response.getInlinePresentation();
+        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+                inlinePresentation.getInlinePresentationSpec(),
+                InlineSuggestionInfo.SOURCE_AUTOFILL, null, InlineSuggestionInfo.TYPE_SUGGESTION);
+
+        return new InlineSuggestion(inlineSuggestionInfo,
+                createInlineContentProvider(inlinePresentation,
+                        () -> onClickFactory.accept(null,
+                                AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
+                        onErrorCallback, remoteRenderService, hostInputToken));
+    }
+
     /**
      * Returns an {@link InlinePresentation} with the style spec from the request/host, and
      * everything else from the provided {@code inlinePresentation}.
@@ -231,27 +287,64 @@
     }
 
     private static IInlineContentProvider.Stub createInlineContentProvider(
-            @NonNull InlinePresentation inlinePresentation,
-            @NonNull InlineSuggestionUi inlineSuggestionUi,
-            @Nullable View.OnClickListener onClickListener) {
+            @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
+            @NonNull Runnable onErrorCallback,
+            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+            @Nullable IBinder hostInputToken) {
         return new IInlineContentProvider.Stub() {
             @Override
-            public void provideContent(int width, int height,
-                    IInlineContentCallback callback) {
+            public void provideContent(int width, int height, IInlineContentCallback callback) {
                 UiThread.getHandler().post(() -> {
-                    SurfaceControl sc = inlineSuggestionUi.inflate(inlinePresentation, width,
-                            height,
-                            onClickListener);
-                    try {
-                        callback.onContent(sc);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Encounter exception calling back with inline content.");
+                    final IInlineSuggestionUiCallback uiCallback = createInlineSuggestionUiCallback(
+                            callback, onClickAction, onErrorCallback);
+
+                    if (remoteRenderService == null) {
+                        Slog.e(TAG, "RemoteInlineSuggestionRenderService is null");
+                        return;
                     }
+
+                    remoteRenderService.renderSuggestion(uiCallback, inlinePresentation,
+                            width, height, hostInputToken);
                 });
             }
         };
     }
 
+    private static IInlineSuggestionUiCallback.Stub createInlineSuggestionUiCallback(
+            @NonNull IInlineContentCallback callback, @NonNull Runnable onAutofillCallback,
+            @NonNull Runnable onErrorCallback) {
+        return new IInlineSuggestionUiCallback.Stub() {
+            @Override
+            public void onAutofill() throws RemoteException {
+                onAutofillCallback.run();
+            }
+
+            @Override
+            public void onContent(SurfaceControl surface)
+                    throws RemoteException {
+                callback.onContent(surface);
+            }
+
+            @Override
+            public void onError() throws RemoteException {
+                onErrorCallback.run();
+            }
+
+            @Override
+            public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId)
+                    throws RemoteException {
+                //TODO(b/149574510): Move logic to IMMS
+                final WindowManagerInternal windowManagerInternal = LocalServices.getService(
+                        WindowManagerInternal.class);
+                if (!windowManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
+                        displayId)) {
+                    Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
+                    onErrorCallback.run();
+                }
+            }
+        };
+    }
+
     private InlineSuggestionFactory() {
     }
-}
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
deleted file mode 100644
index bf148a6..0000000
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill.ui;
-
-import static android.app.slice.SliceItem.FORMAT_IMAGE;
-import static android.app.slice.SliceItem.FORMAT_TEXT;
-
-import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Icon;
-import android.graphics.fonts.SystemFonts;
-import android.os.IBinder;
-import android.service.autofill.InlinePresentation;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices
- * implementation.
- *
- * TODO(b/146453086): remove this class once autofill ext service is implemented.
- *
- * @hide
- */
-public class InlineSuggestionUi {
-
-    private static final String TAG = "InlineSuggestionUi";
-
-    // The pattern to match the value can be obtained by calling {@code Resources#getResourceName
-    // (int)}. This name is a single string of the form "package:type/entry".
-    private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)");
-
-    private final @NonNull Context mContext;
-    private final @NonNull Runnable mOnErrorCallback;
-
-    InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) {
-        this.mContext = context;
-        mOnErrorCallback = onErrorCallback;
-    }
-
-    /**
-     * Returns a {@link SurfaceControl} with the inflated content embedded in it.
-     */
-    @MainThread
-    @Nullable
-    public SurfaceControl inflate(@NonNull InlinePresentation inlinePresentation, int width,
-            int height, @Nullable View.OnClickListener onClickListener) {
-        Log.d(TAG, "Inflating the inline suggestion UI");
-
-        //TODO(b/137800469): Pass in inputToken from IME.
-        final SurfaceControlViewHost wvr = new SurfaceControlViewHost(mContext,
-                mContext.getDisplay(), (IBinder) null);
-        final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl();
-
-        Context contextThemeWrapper = getContextThemeWrapper(mContext,
-                inlinePresentation.getInlinePresentationSpec().getStyle());
-        if (contextThemeWrapper == null) {
-            contextThemeWrapper = getDefaultContextThemeWrapper(mContext);
-        }
-        final View suggestionView = renderSlice(inlinePresentation.getSlice(),
-                contextThemeWrapper);
-
-        final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(
-                mContext, mOnErrorCallback);
-        suggestionRoot.addView(suggestionView);
-        suggestionRoot.setOnClickListener(onClickListener);
-
-        WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(width, height,
-                        WindowManager.LayoutParams.TYPE_APPLICATION, 0,
-                        PixelFormat.TRANSPARENT);
-        wvr.addView(suggestionRoot, lp);
-        return sc;
-    }
-
-    private static View renderSlice(Slice slice, Context context) {
-        final LayoutInflater inflater = LayoutInflater.from(context);
-        final ViewGroup suggestionView =
-                (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);
-
-        final ImageView startIconView =
-                suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
-        final TextView titleView =
-                suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
-        final TextView subtitleView =
-                suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
-        final ImageView endIconView =
-                suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);
-
-        boolean hasStartIcon = false;
-        boolean hasEndIcon = false;
-        boolean hasSubtitle = false;
-        final List<SliceItem> sliceItems = slice.getItems();
-        for (int i = 0; i < sliceItems.size(); i++) {
-            final SliceItem sliceItem = sliceItems.get(i);
-            if (sliceItem.getFormat().equals(FORMAT_IMAGE)) {
-                final Icon sliceIcon = sliceItem.getIcon();
-                if (i == 0) { // start icon
-                    startIconView.setImageIcon(sliceIcon);
-                    hasStartIcon = true;
-                } else { // end icon
-                    endIconView.setImageIcon(sliceIcon);
-                    hasEndIcon = true;
-                }
-            } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) {
-                final List<String> sliceHints = sliceItem.getHints();
-                final String sliceText = sliceItem.getText().toString();
-                if (sliceHints.contains("inline_title")) { // title
-                    titleView.setText(sliceText);
-                } else { // subtitle
-                    subtitleView.setText(sliceText);
-                    hasSubtitle = true;
-                }
-            }
-        }
-        if (!hasStartIcon) {
-            startIconView.setVisibility(View.GONE);
-        }
-        if (!hasEndIcon) {
-            endIconView.setVisibility(View.GONE);
-        }
-        if (!hasSubtitle) {
-            subtitleView.setVisibility(View.GONE);
-        }
-
-        return suggestionView;
-    }
-
-    private Context getDefaultContextThemeWrapper(@NonNull Context context) {
-        Resources.Theme theme = context.getResources().newTheme();
-        theme.applyStyle(android.R.style.Theme_AutofillInlineSuggestion, true);
-        return new ContextThemeWrapper(context, theme);
-    }
-
-    /**
-     * Returns a context wrapping the theme in the provided {@code style}, or null if {@code
-     * style} doesn't pass validation.
-     */
-    @Nullable
-    private static Context getContextThemeWrapper(@NonNull Context context,
-            @Nullable String style) {
-        if (style == null) {
-            return null;
-        }
-        Matcher matcher = RESOURCE_NAME_PATTERN.matcher(style);
-        if (!matcher.matches()) {
-            Log.d(TAG, "Can not parse the style=" + style);
-            return null;
-        }
-        String packageName = matcher.group(1);
-        String type = matcher.group(2);
-        String entry = matcher.group(3);
-        if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(type) || TextUtils.isEmpty(entry)) {
-            Log.d(TAG, "Can not proceed with empty field values in the style=" + style);
-            return null;
-        }
-        Resources resources = null;
-        try {
-            resources = context.getPackageManager().getResourcesForApplication(
-                    packageName);
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-        int resId = resources.getIdentifier(entry, type, packageName);
-        if (resId == Resources.ID_NULL) {
-            return null;
-        }
-        Resources.Theme theme = resources.newTheme();
-        theme.applyStyle(resId, true);
-        if (!validateBaseTheme(theme, resId)) {
-            Log.d(TAG, "Provided theme is not a child of Theme.InlineSuggestion, ignoring it.");
-            return null;
-        }
-        if (!validateFontFamilyForTextViewStyles(theme)) {
-            Log.d(TAG,
-                    "Provided theme specifies a font family that is not system font, ignoring it.");
-            return null;
-        }
-        return new ContextThemeWrapper(context, theme);
-    }
-
-    private static boolean validateFontFamilyForTextViewStyles(Resources.Theme theme) {
-        return validateFontFamily(theme, android.R.attr.autofillInlineSuggestionTitle)
-                && validateFontFamily(theme, android.R.attr.autofillInlineSuggestionSubtitle);
-    }
-
-    private static boolean validateFontFamily(Resources.Theme theme, int styleAttr) {
-        TypedArray ta = null;
-        try {
-            ta = theme.obtainStyledAttributes(null, new int[]{android.R.attr.fontFamily},
-                    styleAttr,
-                    0);
-            if (ta.getIndexCount() == 0) {
-                return true;
-            }
-            String fontFamily = ta.getString(ta.getIndex(0));
-            return SystemFonts.getRawSystemFallbackMap().containsKey(fontFamily);
-        } finally {
-            if (ta != null) {
-                ta.recycle();
-            }
-        }
-    }
-
-    private static boolean validateBaseTheme(Resources.Theme theme, int styleAttr) {
-        TypedArray ta = null;
-        try {
-            ta = theme.obtainStyledAttributes(null,
-                    new int[]{android.R.attr.isAutofillInlineSuggestionTheme}, styleAttr, 0);
-            if (ta.getIndexCount() == 0) {
-                return false;
-            }
-            return ta.getBoolean(ta.getIndex(0), false);
-        } finally {
-            if (ta != null) {
-                ta.recycle();
-            }
-        }
-    }
-}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 31ea5fa..9a33fc9 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -998,6 +998,11 @@
 
                     sendErrorSignal(mClientAdapterReference, serviceAdapterReference,
                             ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+                } finally {
+                    synchronized (parentService.mLock) {
+                        parentService.mPackagesWithShareRequests
+                                .remove(mDataShareRequest.getPackageName());
+                    }
                 }
             });
 
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 442c9e5..f688759 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -216,17 +216,11 @@
     /**
      * Returns the events for the user in the given time period.
      *
-     * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
-     *     result.
-     * @param hideShortcutInvocationEvents whether the {@link UsageEvents.Event#SHORTCUT_INVOCATION}
-     *     events need to be excluded from the result.
-     * @param hideLocusIdEvents whether the {@link UsageEvents.Event#LOCUS_ID_SET}
-     *     events need to be excluded from the result.
-     *
+     * @param flags defines the visibility of certain usage events - see flags defined in
+     * {@link UsageEvents}.
      */
     public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
-            long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents,
-            boolean hideLocusIdEvents);
+            long endTime, int flags);
 
     /**
      * Used to persist the last time a job was run for this app, in order to make decisions later
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 311a494..a4a42bc 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -69,6 +69,7 @@
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -196,6 +197,12 @@
                     + " due to " + getEnableDisableReasonString(mReason) + " by " + mPackageName;
         }
 
+        void dump(ProtoOutputStream proto) {
+            proto.write(BluetoothManagerServiceDumpProto.ActiveLog.TIMESTAMP_MS, mTimestamp);
+            proto.write(BluetoothManagerServiceDumpProto.ActiveLog.ENABLE, mEnable);
+            proto.write(BluetoothManagerServiceDumpProto.ActiveLog.PACKAGE_NAME, mPackageName);
+            proto.write(BluetoothManagerServiceDumpProto.ActiveLog.REASON, mReason);
+        }
     }
 
     private final LinkedList<ActiveLog> mActiveLogs = new LinkedList<>();
@@ -2408,56 +2415,56 @@
         if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) {
             return;
         }
+        if ((args.length > 0) && args[0].startsWith("--proto")) {
+            dumpProto(fd);
+            return;
+        }
         String errorMsg = null;
 
-        boolean protoOut = (args.length > 0) && args[0].startsWith("--proto");
+        writer.println("Bluetooth Status");
+        writer.println("  enabled: " + isEnabled());
+        writer.println("  state: " + BluetoothAdapter.nameForState(mState));
+        writer.println("  address: " + mAddress);
+        writer.println("  name: " + mName);
+        if (mEnable) {
+            long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime;
+            String onDurationString = String.format(Locale.US, "%02d:%02d:%02d.%03d",
+                    (int) (onDuration / (1000 * 60 * 60)),
+                    (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60),
+                    (int) (onDuration % 1000));
+            writer.println("  time since enabled: " + onDurationString);
+        }
 
-        if (!protoOut) {
-            writer.println("Bluetooth Status");
-            writer.println("  enabled: " + isEnabled());
-            writer.println("  state: " + BluetoothAdapter.nameForState(mState));
-            writer.println("  address: " + mAddress);
-            writer.println("  name: " + mName);
-            if (mEnable) {
-                long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime;
-                String onDurationString = String.format(Locale.US, "%02d:%02d:%02d.%03d",
-                        (int) (onDuration / (1000 * 60 * 60)),
-                        (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60),
-                        (int) (onDuration % 1000));
-                writer.println("  time since enabled: " + onDurationString);
+        if (mActiveLogs.size() == 0) {
+            writer.println("\nBluetooth never enabled!");
+        } else {
+            writer.println("\nEnable log:");
+            for (ActiveLog log : mActiveLogs) {
+                writer.println("  " + log);
             }
+        }
 
-            if (mActiveLogs.size() == 0) {
-                writer.println("\nBluetooth never enabled!");
-            } else {
-                writer.println("\nEnable log:");
-                for (ActiveLog log : mActiveLogs) {
-                    writer.println("  " + log);
-                }
-            }
+        writer.println(
+                "\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+        if (mCrashes == CRASH_LOG_MAX_SIZE) {
+            writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
+        }
+        for (Long time : mCrashTimestamps) {
+            writer.println("  " + timeToLog(time));
+        }
 
-            writer.println(
-                    "\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
-            if (mCrashes == CRASH_LOG_MAX_SIZE) {
-                writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
-            }
-            for (Long time : mCrashTimestamps) {
-                writer.println("  " + timeToLog(time));
-            }
+        writer.println("\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s")
+                + " registered");
+        for (ClientDeathRecipient app : mBleApps.values()) {
+            writer.println("  " + app.getPackageName());
+        }
 
-            writer.println("\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s")
-                    + "registered");
-            for (ClientDeathRecipient app : mBleApps.values()) {
-                writer.println("  " + app.getPackageName());
-            }
-
-            writer.println("");
-            writer.flush();
-            if (args.length == 0) {
-                // Add arg to produce output
-                args = new String[1];
-                args[0] = "--print";
-            }
+        writer.println("");
+        writer.flush();
+        if (args.length == 0) {
+            // Add arg to produce output
+            args = new String[1];
+            args[0] = "--print";
         }
 
         if (mBluetoothBinder == null) {
@@ -2470,14 +2477,42 @@
             }
         }
         if (errorMsg != null) {
-            // Silently return if we are extracting metrics in Protobuf format
-            if (protoOut) {
-                return;
-            }
             writer.println(errorMsg);
         }
     }
 
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled());
+        proto.write(BluetoothManagerServiceDumpProto.STATE, mState);
+        proto.write(BluetoothManagerServiceDumpProto.STATE_NAME,
+                BluetoothAdapter.nameForState(mState));
+        proto.write(BluetoothManagerServiceDumpProto.ADDRESS, mAddress);
+        proto.write(BluetoothManagerServiceDumpProto.NAME, mName);
+        if (mEnable) {
+            proto.write(BluetoothManagerServiceDumpProto.LAST_ENABLED_TIME_MS, mLastEnabledTime);
+        }
+        proto.write(BluetoothManagerServiceDumpProto.CURR_TIMESTAMP_MS,
+                SystemClock.elapsedRealtime());
+        for (ActiveLog log : mActiveLogs) {
+            long token = proto.start(BluetoothManagerServiceDumpProto.ACTIVE_LOGS);
+            log.dump(proto);
+            proto.end(token);
+        }
+        proto.write(BluetoothManagerServiceDumpProto.NUM_CRASHES, mCrashes);
+        proto.write(BluetoothManagerServiceDumpProto.CRASH_LOG_MAXED,
+                mCrashes == CRASH_LOG_MAX_SIZE);
+        for (Long time : mCrashTimestamps) {
+            proto.write(BluetoothManagerServiceDumpProto.CRASH_TIMESTAMPS_MS, time);
+        }
+        proto.write(BluetoothManagerServiceDumpProto.NUM_BLE_APPS, mBleApps.size());
+        for (ClientDeathRecipient app : mBleApps.values()) {
+            proto.write(BluetoothManagerServiceDumpProto.BLE_APP_PACKAGE_NAMES,
+                    app.getPackageName());
+        }
+        proto.flush();
+    }
+
     private static String getEnableDisableReasonString(int reason) {
         switch (reason) {
             case BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST:
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c47cde3..b334b26 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3749,6 +3749,7 @@
             if (nm == null) return;
 
             if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+                checkNetworkStackPermission();
                 nm.forceReevaluation(Binder.getCallingUid());
             }
         }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index c987620..9540f43 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1556,16 +1556,16 @@
         }
 
         Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
-        switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
-            case AppOpsManager.MODE_DEFAULT:
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
-                break;
-            case AppOpsManager.MODE_ALLOWED:
-                return;
-            default:
-                throw new SecurityException("Request to ignore AppOps for non-legacy API");
+
+        // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+        // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+        // permission or is the System Server.
+        if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+                TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+            return;
         }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
     }
 
     private void createOrUpdateTransform(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 75e310d..033f63b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -28,6 +28,10 @@
 import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
 import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
@@ -136,6 +140,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.AppFuseMount;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.FuseUnavailableMountException;
@@ -149,6 +154,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.Installer;
 import com.android.server.storage.AppFuseBridge;
 import com.android.server.storage.StorageSessionController;
@@ -184,6 +190,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -270,6 +277,11 @@
         public void onStopUser(int userHandle) {
             mStorageManagerService.onStopUser(userHandle);
         }
+
+        @Override
+        public void onStartUser(TargetUser user) {
+            mStorageManagerService.snapshotAndMonitorLegacyStorageAppOp(user.getUserHandle());
+        }
     }
 
     private static final boolean DEBUG_EVENTS = false;
@@ -578,6 +590,12 @@
 
     private final boolean mIsFuseEnabled;
 
+    @GuardedBy("mLock")
+    private final Set<Integer> mUidsWithLegacyExternalStorage = new ArraySet<>();
+    // Not guarded by lock, always used on the ActivityManager thread
+    private final Map<Integer, PackageMonitor> mPackageMonitorsForUser = new ArrayMap<>();
+
+
     class ObbState implements IBinder.DeathRecipient {
         public ObbState(String rawPath, String canonicalPath, int callingUid,
                 IObbActionListener token, int nonce, String volId) {
@@ -1145,6 +1163,10 @@
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
+        PackageMonitor monitor = mPackageMonitorsForUser.remove(userId);
+        if (monitor != null) {
+            monitor.unregister();
+        }
     }
 
     private boolean supportsBlockCheckpoint() throws RemoteException {
@@ -1300,13 +1322,6 @@
                     vol.state = newState;
                     onVolumeStateChangedLocked(vol, oldState, newState);
                 }
-                try {
-                    if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
-                        mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
-                    }
-                } catch (Installer.InstallerException e) {
-                    Slog.i(TAG, "Failed when private volume mounted " + vol, e);
-                }
             }
         }
 
@@ -1843,6 +1858,49 @@
         }
     }
 
+    private void updateLegacyStorageApps(String packageName, int uid, boolean hasLegacy) {
+        synchronized (mLock) {
+            if (hasLegacy) {
+                Slog.v(TAG, "Package " + packageName + " has legacy storage");
+                mUidsWithLegacyExternalStorage.add(uid);
+            } else {
+                // TODO(b/149391976): Handle shared user id. Check if there's any other
+                // installed app with legacy external storage before removing
+                Slog.v(TAG, "Package " + packageName + " does not have legacy storage");
+                mUidsWithLegacyExternalStorage.remove(uid);
+            }
+        }
+    }
+
+    private void snapshotAndMonitorLegacyStorageAppOp(UserHandle user) {
+        int userId = user.getIdentifier();
+
+        // TODO(b/149391976): Use mIAppOpsService.getPackagesForOps instead of iterating below
+        // It should improve performance but the AppOps method doesn't return any app here :(
+        // This operation currently takes about ~20ms on a freshly flashed device
+        for (ApplicationInfo ai : mPmInternal.getInstalledApplications(MATCH_DIRECT_BOOT_AWARE
+                        | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER,
+                        userId, Process.myUid())) {
+            try {
+                boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE, ai.uid,
+                        ai.packageName) == MODE_ALLOWED;
+                updateLegacyStorageApps(ai.packageName, ai.uid, hasLegacy);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to check legacy op for package " + ai.packageName, e);
+            }
+        }
+
+        PackageMonitor monitor = new PackageMonitor() {
+                @Override
+                public void onPackageRemoved(String packageName, int uid) {
+                    updateLegacyStorageApps(packageName, uid, false);
+                }
+            };
+        // TODO(b/149391976): Use different handler?
+        monitor.register(mContext, user, true, mHandler);
+        mPackageMonitorsForUser.put(userId, monitor);
+    }
+
     private static long getLastAccessTime(AppOpsManager manager,
             int uid, String packageName, int[] ops) {
         long maxTime = 0;
@@ -3110,6 +3168,15 @@
 
         try {
             mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            // After preparing user storage, we should check if we should mount data mirror again,
+            // and we do it for user 0 only as we only need to do once for all users.
+            if (volumeUuid != null) {
+                final StorageManager storage = mContext.getSystemService(StorageManager.class);
+                VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+                if (info != null && userId == 0 && info.type == VolumeInfo.TYPE_PRIVATE) {
+                    mInstaller.tryMountDataMirror(volumeUuid);
+                }
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3973,7 +4040,7 @@
             if (mIsFuseEnabled && hasMtp) {
                 ApplicationInfo ai = mIPackageManager.getApplicationInfo(packageName,
                         0, UserHandle.getUserId(uid));
-                if (ai.isSignedWithPlatformKey()) {
+                if (ai != null && ai.isSignedWithPlatformKey()) {
                     // Platform processes hosting the MTP server should be able to write in Android/
                     return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
                 }
@@ -4335,6 +4402,13 @@
             mHandler.obtainMessage(H_RESET).sendToTarget();
         }
 
+        @Override
+        public boolean hasLegacyExternalStorage(int uid) {
+            synchronized (mLock) {
+                return mUidsWithLegacyExternalStorage.contains(uid);
+            }
+        }
+
         public boolean hasExternalStorage(int uid, String packageName) {
             // No need to check for system uid. This avoids a deadlock between
             // PackageManagerService and AppOpsService.
@@ -4380,8 +4454,11 @@
                             // volumes, USB OTGs that are rarely mounted. The app will get the
                             // external_storage gid on next organic restart.
                             killAppForOpChange(code, uid, packageName);
-                            return;
                         }
+                        return;
+                    case OP_LEGACY_STORAGE:
+                        updateLegacyStorageApps(packageName, uid, mode == MODE_ALLOWED);
+                        return;
                 }
             }
 
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index e3c7325..aabe98b 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -168,6 +168,11 @@
         public int getUserIdentifier() {
             return mUserInfo.id;
         }
+
+        @Override
+        public String toString() {
+            return Integer.toString(getUserIdentifier());
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0e5a6bb..f85fc28 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -61,6 +61,7 @@
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.DataFailCause;
 import android.telephony.DisconnectCause;
+import android.telephony.DisplayInfo;
 import android.telephony.LocationAccessPolicy;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
@@ -205,6 +206,8 @@
 
     private boolean[] mUserMobileDataState;
 
+    private DisplayInfo[] mDisplayInfos;
+
     private SignalStrength[] mSignalStrength;
 
     private boolean[] mMessageWaiting;
@@ -284,7 +287,8 @@
     static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
                         | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
-                        | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+                        | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+                        | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED;
 
     static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_PRECISE_CALL_STATE
@@ -443,6 +447,7 @@
         mCallAttributes = copyOf(mCallAttributes, mNumPhones);
         mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
         mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
+        mDisplayInfos = copyOf(mDisplayInfos, mNumPhones);
 
         // ds -> ss switch.
         if (mNumPhones < oldNumPhones) {
@@ -482,6 +487,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
             mBarringInfo.add(i, new BarringInfo());
+            mDisplayInfos[i] = null;
         }
     }
 
@@ -540,6 +546,7 @@
         mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
         mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
         mBarringInfo = new ArrayList<>();
+        mDisplayInfos = new DisplayInfo[numPhones];
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
             mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -568,6 +575,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
             mBarringInfo.add(i, new BarringInfo());
+            mDisplayInfos[i] = null;
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -978,6 +986,15 @@
                             remove(r.binder);
                         }
                     }
+                    if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+                        try {
+                            if (mDisplayInfos[phoneId] != null) {
+                                r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
                     if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
                         try {
                             r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
@@ -1501,6 +1518,45 @@
         }
     }
 
+    /**
+     * Notify display network info changed.
+     *
+     * @param phoneId Phone id
+     * @param subId Subscription id
+     * @param displayInfo Display network info
+     *
+     * @see PhoneStateListener#onDisplayInfoChanged(DisplayInfo)
+     */
+    public void notifyDisplayInfoChanged(int phoneId, int subId,
+                                         @NonNull DisplayInfo displayInfo) {
+        if (!checkNotifyPermission("notifyDisplayInfoChanged()")) {
+            return;
+        }
+        if (VDBG) {
+            log("notifyDisplayInfoChanged: PhoneId=" + phoneId
+                    + " subId=" + subId + " displayInfo=" + displayInfo);
+        }
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                if (mDisplayInfos[phoneId] != null) {
+                    mDisplayInfos[phoneId] = displayInfo;
+                    for (Record r : mRecords) {
+                        if (r.matchPhoneStateListenerEvent(
+                                PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
+                                && idMatch(r.subId, subId, phoneId)) {
+                            try {
+                                r.callback.onDisplayInfoChanged(displayInfo);
+                            } catch (RemoteException ex) {
+                                mRemoveList.add(r.binder);
+                            }
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     public void notifyCallForwardingChanged(boolean cfi) {
         notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
     }
@@ -2730,6 +2786,20 @@
             }
         }
 
+        if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+            try {
+                if (VDBG) {
+                    log("checkPossibleMissNotify: onDisplayInfoChanged phoneId="
+                            + phoneId + " dpi=" + mDisplayInfos[phoneId]);
+                }
+                if (mDisplayInfos[phoneId] != null) {
+                    r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+                }
+            } catch (RemoteException ex) {
+                mRemoveList.add(r.binder);
+            }
+        }
+
         if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
             try {
                 if (VDBG) {
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+    private static final String TAG = "UserspaceRebootLogger";
+
+    private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+            "persist.sys.userspace_reboot.log.should_log";
+    private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+            "sys.userspace_reboot.log.last_started";
+    private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+            "sys.userspace_reboot.log.last_finished";
+    private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+    private UserspaceRebootLogger() {}
+
+    /**
+     * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+     * logged on the next successful boot.
+     */
+    public static void noteUserspaceRebootWasRequested() {
+        SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+        SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+                String.valueOf(SystemClock.elapsedRealtime()));
+    }
+
+    /**
+     * Updates internal state on boot after successful userspace reboot.
+     *
+     * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+     */
+    public static void noteUserspaceRebootSuccess() {
+        SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+                String.valueOf(SystemClock.elapsedRealtime()));
+    }
+
+    /**
+     * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+     */
+    public static boolean shouldLogUserspaceRebootEvent() {
+        return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+    }
+
+    /**
+     * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+     *
+     * <p>Should be called in the end of {@link
+     * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+     * tried to proactivelly unlock storage of the primary user.
+     */
+    public static void logEventAsync(boolean userUnlocked, Executor executor) {
+        final int outcome = computeOutcome();
+        final long durationMillis;
+        if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+            durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+                    - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+        } else {
+            durationMillis = 0;
+        }
+        final int encryptionState =
+                userUnlocked
+                    ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+                    : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+        executor.execute(
+                () -> {
+                    Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+                            + " durationMillis: " + durationMillis + " encryptionState: "
+                            + encryptionState + " }");
+                    FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+                            durationMillis, encryptionState);
+                    SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+                });
+    }
+
+    private static int computeOutcome() {
+        if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+            return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+        }
+        String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+        if (reason.startsWith("reboot,")) {
+            reason = reason.substring("reboot".length());
+        }
+        switch (reason) {
+            case "userspace_failed,watchdog_fork":
+                // Since fork happens before shutdown sequence, attribute it to
+                // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+            case "userspace_failed,shutdown_aborted":
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+            case "userspace_failed,init_user0_failed":
+                // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+                // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+            case "mount_userdata_failed":
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+            case "userspace_failed,watchdog_triggered":
+                return
+                    USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+            default:
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0852458..1da07ef 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -278,6 +278,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
 import android.sysprop.VoldProperties;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -347,6 +348,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.appop.AppOpsService;
@@ -2282,6 +2284,20 @@
         }
     }
 
+    private void maybeLogUserspaceRebootEvent() {
+        if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+            return;
+        }
+        final int userId = mUserController.getCurrentUserId();
+        if (userId != UserHandle.USER_SYSTEM) {
+            // Only log for user0.
+            return;
+        }
+        // TODO(b/148767783): should we check all profiles under user0?
+        UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+                BackgroundThread.getExecutor());
+    }
+
     /**
      * Encapsulates global settings related to hidden API enforcement behaviour, including tracking
      * the latest value via a content observer.
@@ -5361,6 +5377,12 @@
             // Start looking for apps that are abusing wake locks.
             Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
             mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+            // Check if we are performing userspace reboot before setting sys.boot_completed to
+            // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+            // .boot_completed is 1.
+            if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+                UserspaceRebootLogger.noteUserspaceRebootSuccess();
+            }
             // Tell anyone interested that we are done booting!
             SystemProperties.set("sys.boot_completed", "1");
 
@@ -5381,6 +5403,7 @@
                             }
                         }
                     });
+            maybeLogUserspaceRebootEvent();
             mUserController.scheduleStartProfiles();
         }
         // UART is on if init's console service is running, send a warning notification.
@@ -7133,7 +7156,7 @@
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
-                } else if (dyingProc == cpr.proc) {
+                } else if (dyingProc == cpr.proc && dyingProc != null) {
                     // The old stable connection's client should be killed during proc cleaning up,
                     // so do not re-use the old ContentProviderRecord, otherwise the new clients
                     // could get killed unexpectedly.
@@ -8364,6 +8387,9 @@
 
     @Override
     public void setActivityController(IActivityController controller, boolean imAMonkey) {
+        if (controller != null) {
+            Binder.allowBlocking(controller.asBinder());
+        }
         mActivityTaskManager.setActivityController(controller, imAMonkey);
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index abe0dd5..ffa7d92 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,8 @@
 import android.app.ApplicationExitInfo.SubReason;
 import android.app.IApplicationThread;
 import android.app.IUidObserver;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -119,6 +121,7 @@
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 import com.android.server.wm.WindowManagerService;
 
+import dalvik.annotation.compat.VersionCodes;
 import dalvik.system.VMRuntime;
 
 import java.io.File;
@@ -327,6 +330,15 @@
      */
     private static final int PROC_KILL_TIMEOUT = 2000; // 2 seconds;
 
+    /**
+     * Native heap allocations will now have a non-zero tag in the most significant byte.
+     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+     * Pointers</a>
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = VersionCodes.Q)
+    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
     ActivityManagerService mService = null;
 
     // To kill process groups asynchronously
@@ -1768,6 +1780,13 @@
                 runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
             }
 
+            // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level,
+            // or the compat feature.
+            if (app.info.allowsNativeHeapPointerTagging()
+                    && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
+                runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            }
+
             String invokeWith = null;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 88eb885..fb48db4 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -30,6 +30,7 @@
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_LOCKED_BOOT_COMPLETED;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -92,8 +93,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -398,13 +399,14 @@
             // Do not report secondary users, runtime restarts or first boot/upgrade
             if (userId == UserHandle.USER_SYSTEM
                     && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
-                MetricsLogger.histogram(mInjector.getContext(),
-                        "framework_locked_boot_completed", uptimeSeconds);
-                final int MAX_UPTIME_SECONDS = 120;
-                if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+                final long elapsedTimeMs = SystemClock.elapsedRealtime();
+                FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                        BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_LOCKED_BOOT_COMPLETED,
+                        elapsedTimeMs);
+                final long maxElapsedTimeMs = 120_000;
+                if (elapsedTimeMs > maxElapsedTimeMs) {
                     Slog.wtf("SystemServerTiming",
-                            "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
+                            "finishUserBoot took too long. elapsedTimeMs=" + elapsedTimeMs);
                 }
             }
 
@@ -618,9 +620,10 @@
         // Do not report secondary users, runtime restarts or first boot/upgrade
         if (userId == UserHandle.USER_SYSTEM
                 && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-            int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
-            MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
-                    uptimeSeconds);
+            final long elapsedTimeMs = SystemClock.elapsedRealtime();
+            FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_BOOT_COMPLETED,
+                    elapsedTimeMs);
         }
         final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
         bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06561f5..5e1582c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
@@ -52,11 +53,14 @@
 import static android.app.AppOpsManager.makeKey;
 import static android.app.AppOpsManager.modeToName;
 import static android.app.AppOpsManager.opToName;
+import static android.app.AppOpsManager.opToPublicName;
 import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
-import static android.os.Process.STATSD_UID;
+import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
 
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
@@ -79,6 +83,8 @@
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.AsyncNotedAppOp;
+import android.app.RuntimeAppOpAccessMessage;
+import android.app.SyncNotedAppOp;
 import android.compat.Compatibility;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -87,6 +93,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
@@ -137,6 +144,7 @@
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.MessageSamplingConfig;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -147,6 +155,7 @@
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.PackageList;
 
 import libcore.util.EmptyArray;
 
@@ -163,6 +172,8 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -172,6 +183,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
 
 public class AppOpsService extends IAppOpsService.Stub {
     static final String TAG = "AppOps";
@@ -308,6 +321,34 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
+    /** Package sampled for message collection in the current session */
+    @GuardedBy("this")
+    private String mSampledPackage = null;
+
+    /** Appop sampled for message collection in the current session */
+    @GuardedBy("this")
+    private int mSampledAppOpCode = OP_NONE;
+
+    /** Maximum distance for appop to be considered for message collection in the current session */
+    @GuardedBy("this")
+    private int mAcceptableLeftDistance = 0;
+
+    /** Number of messages collected for sampled package and appop in the current session */
+    @GuardedBy("this")
+    private float mMessagesCollectedCount;
+
+    /** List of rarely used packages priorities for message collection */
+    @GuardedBy("this")
+    private ArraySet<String> mRarelyUsedPackages = new ArraySet<>();
+
+    /** Sampling strategy used for current session */
+    @GuardedBy("this")
+    @AppOpsManager.SamplingStrategy
+    private int mSamplingStrategy;
+
+    /** Last runtime permission access message collected and ready for reporting */
+    @GuardedBy("this")
+    private RuntimeAppOpAccessMessage mCollectedRuntimePermissionMessage;
     /**
      * An unsynchronized pool of {@link OpEventProxyInfo} objects.
      */
@@ -411,9 +452,9 @@
                     Slog.e(TAG, "Bad app ops settings", e);
                 }
                 TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_TOP_STATE_SETTLE_TIME, 30 * 1000L);
+                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
                 FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 10 * 1000L);
+                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
                 BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
                         KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
             }
@@ -1236,9 +1277,16 @@
             UserHandle.formatUid(sb, mWatchingUid);
             sb.append(" flags=0x");
             sb.append(Integer.toHexString(mFlags));
-            if (mWatchedOpCode != OP_NONE) {
-                sb.append(" op=");
-                sb.append(opToName(mWatchedOpCode));
+            switch (mWatchedOpCode) {
+                case OP_NONE:
+                    break;
+                case ALL_OPS:
+                    sb.append(" op=(all)");
+                    break;
+                default:
+                    sb.append(" op=");
+                    sb.append(opToName(mWatchedOpCode));
+                    break;
             }
             sb.append(" from uid=");
             UserHandle.formatUid(sb, mCallingUid);
@@ -1535,6 +1583,38 @@
             }
         }, packageSuspendFilter);
 
+        final IntentFilter packageAddedFilter = new IntentFilter();
+        packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageAddedFilter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final Uri data = intent.getData();
+
+                final String packageName = data.getSchemeSpecificPart();
+                PackageInfo pi = LocalServices.getService(
+                        PackageManagerInternal.class).getPackageInfo(packageName,
+                        PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
+                if (isSamplingTarget(pi)) {
+                    synchronized (this) {
+                        mRarelyUsedPackages.add(packageName);
+                    }
+                }
+            }
+        }, packageAddedFilter);
+
+        List<String> packageNames = getPackageNamesForSampling();
+        synchronized (this) {
+            resamplePackageAndAppOpLocked(packageNames);
+        }
+
+        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+            @Override
+            public void run() {
+                initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
+            }
+        });
+
         PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
         packageManagerInternal.setExternalSourcesPolicy(
@@ -1890,9 +1970,9 @@
 
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
         boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
-        boolean isCallerStatsCollector = Binder.getCallingUid() == STATSD_UID;
+        boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
 
-        if (!isCallerStatsCollector && !isCallerInstrumented) {
+        if (!isCallerSystem && !isCallerInstrumented) {
             mHandler.post(() -> callback.sendResult(new Bundle()));
             return;
         }
@@ -3030,6 +3110,9 @@
                         featureId, message, System.currentTimeMillis());
                 final boolean[] wasNoteForwarded = {false};
 
+                reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode, featureId,
+                        message);
+
                 if (callbacks != null) {
                     callbacks.broadcast((cb) -> {
                         try {
@@ -3366,7 +3449,8 @@
             return false;
         }
 
-        return permInfo.getProtection() == PROTECTION_DANGEROUS;
+        return permInfo.getProtection() == PROTECTION_DANGEROUS
+                || (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
     }
 
     private void verifyIncomingUid(int uid) {
@@ -5493,6 +5577,227 @@
         mHistoricalRegistry.clearHistory();
     }
 
+    /**
+     * Report runtime access to AppOp together with message (including stack trace)
+     *
+     * @param packageName The package which reported the op
+     * @param notedAppOp contains code of op and featureId provided by developer
+     * @param message Message describing AppOp access (can be stack trace)
+     *
+     * @return Config for future sampling to reduce amount of reporting
+     */
+    @Override
+    public MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(
+            String packageName, SyncNotedAppOp notedAppOp, String message) {
+        int uid = Binder.getCallingUid();
+        Objects.requireNonNull(packageName);
+        synchronized (this) {
+            switchPackageIfRarelyUsedLocked(packageName);
+            if (!packageName.equals(mSampledPackage)) {
+                return new MessageSamplingConfig(OP_NONE, 0,
+                        Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
+            }
+
+            Objects.requireNonNull(notedAppOp);
+            Objects.requireNonNull(message);
+
+            reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName,
+                    AppOpsManager.strOpToOp(notedAppOp.getOp()),
+                    notedAppOp.getFeatureId(), message);
+
+            return new MessageSamplingConfig(mSampledAppOpCode, mAcceptableLeftDistance,
+                    Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
+        }
+    }
+
+    /**
+     * Report runtime access to AppOp together with message (entry point for reporting
+     * asynchronous access)
+     * @param uid Uid of the package which reported the op
+     * @param packageName The package which reported the op
+     * @param opCode Code of AppOp
+     * @param featureId FeautreId of AppOp reported
+     * @param message Message describing AppOp access (can be stack trace)
+     */
+    private void reportRuntimeAppOpAccessMessageAsyncLocked(int uid,
+            @NonNull String packageName, int opCode, @Nullable String featureId,
+            @NonNull String message) {
+        switchPackageIfRarelyUsedLocked(packageName);
+        if (!Objects.equals(mSampledPackage, packageName)) {
+            return;
+        }
+        reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName, opCode, featureId, message);
+    }
+
+    /**
+     * Decides whether reported message is within the range of watched AppOps and picks it for
+     * reporting uniformly at random across all received messages.
+     */
+    private void reportRuntimeAppOpAccessMessageInternalLocked(int uid,
+            @NonNull String packageName, int opCode, @Nullable String featureId,
+            @NonNull String message) {
+        int newLeftDistance = AppOpsManager.leftCircularDistance(opCode,
+                mSampledAppOpCode, _NUM_OP);
+
+        if (mAcceptableLeftDistance < newLeftDistance) {
+            return;
+        }
+
+        if (mAcceptableLeftDistance > newLeftDistance) {
+            mAcceptableLeftDistance = newLeftDistance;
+            mMessagesCollectedCount = 0.0f;
+        }
+
+        mMessagesCollectedCount += 1.0f;
+        if (ThreadLocalRandom.current().nextFloat() <= 1.0f / mMessagesCollectedCount) {
+            mCollectedRuntimePermissionMessage = new RuntimeAppOpAccessMessage(uid, opCode,
+                    packageName, featureId, message, mSamplingStrategy);
+        }
+        return;
+    }
+
+    /** Pulls current AppOps access report and resamples package and app op to watch */
+    @Override
+    public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        RuntimeAppOpAccessMessage result;
+        List<String> packageNames = getPackageNamesForSampling();
+        synchronized (this) {
+            result = mCollectedRuntimePermissionMessage;
+            resamplePackageAndAppOpLocked(packageNames);
+        }
+        return result;
+    }
+
+    /**
+     * Checks if package is in the list of rarely used package and starts watching the new package
+     * to collect incoming message.
+     * @param packageName
+     */
+    private void switchPackageIfRarelyUsedLocked(@NonNull String packageName) {
+        if (mRarelyUsedPackages.contains(packageName)) {
+            mRarelyUsedPackages.remove(packageName);
+            if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
+                mSamplingStrategy = RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
+                resampleAppOpForPackageLocked(packageName);
+            }
+        }
+    }
+
+    /** Resamples package and appop to watch from the list provided. */
+    private void resamplePackageAndAppOpLocked(@NonNull List<String> packageNames) {
+        if (!packageNames.isEmpty()) {
+            mSamplingStrategy = RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
+            resampleAppOpForPackageLocked(packageNames.get(
+                    ThreadLocalRandom.current().nextInt(packageNames.size())));
+        }
+    }
+
+    /** Resamples appop for the chosen package and initializes sampling state */
+    private void resampleAppOpForPackageLocked(@NonNull String packageName) {
+        mMessagesCollectedCount = 0.0f;
+        mSampledAppOpCode = ThreadLocalRandom.current().nextInt(_NUM_OP);
+        mAcceptableLeftDistance = _NUM_OP;
+        mSampledPackage = packageName;
+        mCollectedRuntimePermissionMessage = null;
+    }
+
+    /**
+     * Creates list of rarely used packages - packages which were not used over last week or
+     * which declared but did not use permissions over last week.
+     *  */
+    private void initializeRarelyUsedPackagesList(@NonNull ArraySet<String> candidates) {
+        AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+        List<String> runtimeAppOpsList = getRuntimeAppOpsList();
+        AppOpsManager.HistoricalOpsRequest histOpsRequest =
+                new AppOpsManager.HistoricalOpsRequest.Builder(
+                        Instant.now().minus(7, ChronoUnit.DAYS).toEpochMilli(),
+                        Long.MAX_VALUE).setOpNames(runtimeAppOpsList).setFlags(
+                        OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED).build();
+        appOps.getHistoricalOps(histOpsRequest, AsyncTask.THREAD_POOL_EXECUTOR,
+                new Consumer<HistoricalOps>() {
+                    @Override
+                    public void accept(HistoricalOps histOps) {
+                        int uidCount = histOps.getUidCount();
+                        for (int uidIdx = 0; uidIdx < uidCount; uidIdx++) {
+                            final AppOpsManager.HistoricalUidOps uidOps = histOps.getUidOpsAt(
+                                    uidIdx);
+                            int pkgCount = uidOps.getPackageCount();
+                            for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++) {
+                                String packageName = uidOps.getPackageOpsAt(
+                                        pkgIdx).getPackageName();
+                                if (!candidates.contains(packageName)) {
+                                    continue;
+                                }
+                                AppOpsManager.HistoricalPackageOps packageOps =
+                                        uidOps.getPackageOpsAt(pkgIdx);
+                                if (packageOps.getOpCount() != 0) {
+                                    candidates.remove(packageName);
+                                }
+                            }
+                        }
+                        synchronized (this) {
+                            mRarelyUsedPackages = candidates;
+                        }
+                    }
+                });
+    }
+
+    /** List of app ops related to runtime permissions */
+    private List<String> getRuntimeAppOpsList() {
+        ArrayList<String> result = new ArrayList();
+        for (int i = 0; i < _NUM_OP; i++) {
+            if (shouldCollectNotes(i)) {
+                result.add(opToPublicName(i));
+            }
+        }
+        return result;
+    }
+
+    /** Returns list of packages to be used for package sampling */
+    private @NonNull List<String> getPackageNamesForSampling() {
+        List<String> packageNames = new ArrayList<>();
+        PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
+        PackageList packages = packageManagerInternal.getPackageList();
+        for (String packageName : packages.getPackageNames()) {
+            PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName,
+                    PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
+            if (isSamplingTarget(pkg)) {
+                packageNames.add(pkg.packageName);
+            }
+        }
+        return packageNames;
+    }
+
+    /** Checks whether package should be included in sampling pool */
+    private boolean isSamplingTarget(@Nullable PackageInfo pkg) {
+        if (pkg == null) {
+            return false;
+        }
+        if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.Q) {
+            return false;
+        }
+
+        String[] requestedPermissions = pkg.requestedPermissions;
+        if (requestedPermissions == null) {
+            return false;
+        }
+        for (String permission : requestedPermissions) {
+            PermissionInfo permissionInfo;
+            try {
+                permissionInfo = mContext.getPackageManager().getPermissionInfo(permission, 0);
+            } catch (PackageManager.NameNotFoundException ignored) {
+                continue;
+            }
+            if (permissionInfo.getProtection() == PROTECTION_DANGEROUS) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void removeUidsForUserLocked(int userHandle) {
         for (int i = mUidStates.size() - 1; i >= 0; --i) {
             final int uid = mUidStates.keyAt(i);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 97a8b87..4be74b5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -30,6 +30,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
@@ -128,6 +129,7 @@
 import android.util.MathUtils;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -157,6 +159,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
@@ -247,6 +250,7 @@
     // AudioHandler messages
     private static final int MSG_SET_DEVICE_VOLUME = 0;
     private static final int MSG_PERSIST_VOLUME = 1;
+    private static final int MSG_PERSIST_VOLUME_GROUP = 2;
     private static final int MSG_PERSIST_RINGER_MODE = 3;
     private static final int MSG_AUDIO_SERVER_DIED = 4;
     private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -780,6 +784,10 @@
         mSettingsObserver = new SettingsObserver();
         createStreamStates();
 
+        // must be called after createStreamStates() as it uses MUSIC volume as default if no
+        // persistent data
+        initVolumeGroupStates();
+
         // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -995,11 +1003,14 @@
         mDeviceBroker.onAudioServerDied();
 
         // Restore call state
-        if (AudioSystem.setPhoneState(mMode) ==  AudioSystem.AUDIO_STATUS_OK) {
-            mModeLogger.log(new AudioEventLogger.StringEvent(
-                "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")"));
+        synchronized (mDeviceBroker.mSetModeLock) {
+            if (AudioSystem.setPhoneState(mMode, getModeOwnerUid())
+                    ==  AudioSystem.AUDIO_STATUS_OK) {
+                mModeLogger.log(new AudioEventLogger.StringEvent(
+                        "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode)
+                        + ", uid=" + getModeOwnerUid() + ")"));
+            }
         }
-
         final int forSys;
         synchronized (mSettingsLock) {
             forSys = mCameraSoundForced ?
@@ -1018,6 +1029,9 @@
             streamState.applyAllVolumes();
         }
 
+        // Restore audio volume groups
+        restoreVolumeGroups();
+
         // Restore mono mode
         updateMasterMono(mContentResolver);
 
@@ -1063,6 +1077,15 @@
                             + AudioSystem.audioSystemErrorToString(status)
                             + " when connecting mixes for policy " + policy.toLogFriendlyString());
                     policy.release();
+                } else {
+                    final int deviceAffinitiesStatus = policy.setupDeviceAffinities();
+                    if (deviceAffinitiesStatus != AudioSystem.SUCCESS) {
+                        Log.e(TAG, "onAudioServerDied: error "
+                                + AudioSystem.audioSystemErrorToString(deviceAffinitiesStatus)
+                                + " when connecting device affinities for policy "
+                                + policy.toLogFriendlyString());
+                        policy.release();
+                    }
                 }
             }
         }
@@ -2288,20 +2311,20 @@
                                             String callingPackage) {
         enforceModifyAudioRoutingPermission();
         Objects.requireNonNull(attr, "attr must not be null");
-        // @todo not hold the caller context, post message
-        int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
-        final int device = getDeviceForStream(stream);
-
-        int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);
-
-        AudioSystem.setVolumeIndexForAttributes(attr, index, device);
-
         final int volumeGroup = getVolumeGroupIdForAttributes(attr);
-        final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
-        if (avg == null) {
+        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+            Log.e(TAG, ": no volume group found for attributes " + attr.toString());
             return;
         }
-        for (final int groupedStream : avg.getLegacyStreamTypes()) {
+        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+                index/*val1*/, flags/*val2*/, callingPackage));
+
+        vgs.setVolumeIndex(index, flags);
+
+        // For legacy reason, propagate to all streams associated to this volume group
+        for (final int groupedStream : vgs.getLegacyStreamTypes()) {
             setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
                             Binder.getCallingUid());
         }
@@ -2323,10 +2346,12 @@
     public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
         enforceModifyAudioRoutingPermission();
         Objects.requireNonNull(attr, "attr must not be null");
-        int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
-        final int device = getDeviceForStream(stream);
-
-        return AudioSystem.getVolumeIndexForAttributes(attr, device);
+        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+            throw new IllegalArgumentException("No volume group for attributes " + attr);
+        }
+        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+        return vgs.getVolumeIndex();
     }
 
     /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3414,14 +3439,30 @@
         return modeOwnerPid;
     }
 
+    /**
+     * Return the uid of the current audio mode owner
+     * @return 0 if nobody owns the mode
+     */
+    /*package*/ int getModeOwnerUid() {
+        int modeOwnerUid = 0;
+        try {
+            modeOwnerUid = mSetModeDeathHandlers.get(0).getUid();
+        } catch (Exception e) {
+            // nothing to do, modeOwnerUid is not modified
+        }
+        return modeOwnerUid;
+    }
+
     private class SetModeDeathHandler implements IBinder.DeathRecipient {
-        private IBinder mCb; // To be notified of client's death
-        private int mPid;
+        private final IBinder mCb; // To be notified of client's death
+        private final int mPid;
+        private final int mUid;
         private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
 
-        SetModeDeathHandler(IBinder cb, int pid) {
+        SetModeDeathHandler(IBinder cb, int pid, int uid) {
             mCb = cb;
             mPid = pid;
+            mUid = uid;
         }
 
         public void binderDied() {
@@ -3434,7 +3475,7 @@
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
                 } else {
-                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
+                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, mUid, TAG);
                 }
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
@@ -3459,6 +3500,10 @@
         public IBinder getBinder() {
             return mCb;
         }
+
+        public int getUid() {
+            return mUid;
+        }
     }
 
     /** @see AudioManager#setMode(int) */
@@ -3507,7 +3552,8 @@
                         + " without permission or being mode owner");
                 return;
             }
-            newModeOwnerPid = setModeInt(mode, cb, callingPid, callingPackage);
+            newModeOwnerPid = setModeInt(
+                mode, cb, callingPid, Binder.getCallingUid(), callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
@@ -3519,9 +3565,11 @@
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
     @GuardedBy("mDeviceBroker.mSetModeLock")
-    private int setModeInt(int mode, IBinder cb, int pid, String caller) {
-        if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
-                + caller + ")"); }
+    private int setModeInt(int mode, IBinder cb, int pid, int uid, String caller) {
+        if (DEBUG_MODE) {
+            Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid
+                    + ", uid=" + uid + ", caller=" + caller + ")");
+        }
         int newModeOwnerPid = 0;
         if (cb == null) {
             Log.e(TAG, "setModeInt() called with null binder");
@@ -3558,7 +3606,7 @@
                 }
             } else {
                 if (hdlr == null) {
-                    hdlr = new SetModeDeathHandler(cb, pid);
+                    hdlr = new SetModeDeathHandler(cb, pid, uid);
                 }
                 // Register for client death notification
                 try {
@@ -3576,7 +3624,7 @@
 
             if (actualMode != mMode) {
                 final long identity = Binder.clearCallingIdentity();
-                status = AudioSystem.setPhoneState(actualMode);
+                status = AudioSystem.setPhoneState(actualMode, getModeOwnerUid());
                 Binder.restoreCallingIdentity(identity);
                 if (status == AudioSystem.AUDIO_STATUS_OK) {
                     if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
@@ -3754,6 +3802,8 @@
                 enforceSafeMediaVolume(TAG);
             }
         }
+
+        readVolumeGroupsSettings();
     }
 
     /** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4654,6 +4704,310 @@
     ///////////////////////////////////////////////////////////////////////////
     // Inner classes
     ///////////////////////////////////////////////////////////////////////////
+    /**
+     * Key is the AudioManager VolumeGroupId
+     * Value is the VolumeGroupState
+     */
+    private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
+
+    private void initVolumeGroupStates() {
+        for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
+            try {
+                // if no valid attributes, this volume group is not controllable, throw exception
+                ensureValidAttributes(avg);
+            } catch (IllegalArgumentException e) {
+                // Volume Groups without attributes are not controllable through set/get volume
+                // using attributes. Do not append them.
+                Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+                continue;
+            }
+            sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+        }
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.applyAllVolumes();
+        }
+    }
+
+    private void ensureValidAttributes(AudioVolumeGroup avg) {
+        boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
+                .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
+        if (!hasAtLeastOneValidAudioAttributes) {
+            throw new IllegalArgumentException("Volume Group " + avg.name()
+                    + " has no valid audio attributes");
+        }
+    }
+
+    private void readVolumeGroupsSettings() {
+        Log.v(TAG, "readVolumeGroupsSettings");
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.readSettings();
+            vgs.applyAllVolumes();
+        }
+    }
+
+    // Called upon crash of AudioServer
+    private void restoreVolumeGroups() {
+        Log.v(TAG, "restoreVolumeGroups");
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.applyAllVolumes();
+        }
+    }
+
+    private void dumpVolumeGroups(PrintWriter pw) {
+        pw.println("\nVolume Groups (device: index)");
+        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+            vgs.dump(pw);
+            pw.println("");
+        }
+    }
+
+    // NOTE: Locking order for synchronized objects related to volume management:
+    //  1     mSettingsLock
+    //  2       VolumeGroupState.class
+    private class VolumeGroupState {
+        private final AudioVolumeGroup mAudioVolumeGroup;
+        private final SparseIntArray mIndexMap = new SparseIntArray(8);
+        private int mIndexMin;
+        private int mIndexMax;
+        private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+        private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
+        private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;
+
+        // No API in AudioSystem to get a device from strategy or from attributes.
+        // Need a valid public stream type to use current API getDeviceForStream
+        private int getDeviceForVolume() {
+            return getDeviceForStream(mPublicStreamType);
+        }
+
+        private VolumeGroupState(AudioVolumeGroup avg) {
+            mAudioVolumeGroup = avg;
+            Log.v(TAG, "VolumeGroupState for " + avg.toString());
+            for (final AudioAttributes aa : avg.getAudioAttributes()) {
+                if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
+                    mAudioAttributes = aa;
+                    break;
+                }
+            }
+            final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+            if (streamTypes.length != 0) {
+                // Uses already initialized MIN / MAX if a stream type is attached to group
+                mLegacyStreamType = streamTypes[0];
+                for (final int streamType : streamTypes) {
+                    if (streamType != AudioSystem.STREAM_DEFAULT
+                            && streamType < AudioSystem.getNumStreamTypes()) {
+                        mPublicStreamType = streamType;
+                        break;
+                    }
+                }
+                mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+                mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+            } else if (!avg.getAudioAttributes().isEmpty()) {
+                mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
+                mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
+            } else {
+                Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+                        + " has neither valid attributes nor valid stream types assigned");
+                return;
+            }
+            // Load volume indexes from data base
+            readSettings();
+        }
+
+        public @NonNull int[] getLegacyStreamTypes() {
+            return mAudioVolumeGroup.getLegacyStreamTypes();
+        }
+
+        public String name() {
+            return mAudioVolumeGroup.name();
+        }
+
+        public int getVolumeIndex() {
+            return getIndex(getDeviceForVolume());
+        }
+
+        public void setVolumeIndex(int index, int flags) {
+            if (mUseFixedVolume) {
+                return;
+            }
+            setVolumeIndex(index, getDeviceForVolume(), flags);
+        }
+
+        private void setVolumeIndex(int index, int device, int flags) {
+            // Set the volume index
+            setVolumeIndexInt(index, device, flags);
+
+            // Update local cache
+            mIndexMap.put(device, index);
+
+            // update data base - post a persist volume group msg
+            sendMsg(mAudioHandler,
+                    MSG_PERSIST_VOLUME_GROUP,
+                    SENDMSG_QUEUE,
+                    device,
+                    0,
+                    this,
+                    PERSIST_DELAY);
+        }
+
+        private void setVolumeIndexInt(int index, int device, int flags) {
+            // Set the volume index
+            AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+        }
+
+        public int getIndex(int device) {
+            synchronized (VolumeGroupState.class) {
+                int index = mIndexMap.get(device, -1);
+                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+                return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+            }
+        }
+
+        public boolean hasIndexForDevice(int device) {
+            synchronized (VolumeGroupState.class) {
+                return (mIndexMap.get(device, -1) != -1);
+            }
+        }
+
+        public int getMaxIndex() {
+            return mIndexMax;
+        }
+
+        public int getMinIndex() {
+            return mIndexMin;
+        }
+
+        public void applyAllVolumes() {
+            synchronized (VolumeGroupState.class) {
+                if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
+                    // No-op to avoid regression with stream based volume management
+                    return;
+                }
+                // apply device specific volumes first
+                int index;
+                for (int i = 0; i < mIndexMap.size(); i++) {
+                    final int device = mIndexMap.keyAt(i);
+                    if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+                        index = mIndexMap.valueAt(i);
+                        Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+                                + mAudioVolumeGroup.name() + " and device "
+                                + AudioSystem.getOutputDeviceName(device));
+                        setVolumeIndexInt(index, device, 0 /*flags*/);
+                    }
+                }
+                // apply default volume last: by convention , default device volume will be used
+                index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+                Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
+                        + mAudioVolumeGroup.name());
+                setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+            }
+        }
+
+        private void persistVolumeGroup(int device) {
+            if (mUseFixedVolume) {
+                return;
+            }
+            Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+                    + mAudioVolumeGroup.name() + " and device "
+                    + AudioSystem.getOutputDeviceName(device));
+            boolean success = Settings.System.putIntForUser(mContentResolver,
+                    getSettingNameForDevice(device),
+                    getIndex(device),
+                    UserHandle.USER_CURRENT);
+            if (!success) {
+                Log.e(TAG, "persistVolumeGroup failed for group " +  mAudioVolumeGroup.name());
+            }
+        }
+
+        public void readSettings() {
+            synchronized (VolumeGroupState.class) {
+                // First clear previously loaded (previous user?) settings
+                mIndexMap.clear();
+                // force maximum volume on all streams if fixed volume property is set
+                if (mUseFixedVolume) {
+                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+                    return;
+                }
+                for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+                    // retrieve current volume for device
+                    // if no volume stored for current volume group and device, use default volume
+                    // if default device, continue otherwise
+                    int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
+                            ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
+                    int index;
+                    String name = getSettingNameForDevice(device);
+                    index = Settings.System.getIntForUser(
+                            mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+                    if (index == -1) {
+                        Log.e(TAG, "readSettings: No index stored for group "
+                                + mAudioVolumeGroup.name() + ", device " + name);
+                        continue;
+                    }
+                    Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+                             + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+                    mIndexMap.put(device, getValidIndex(index));
+                }
+            }
+        }
+
+        private int getValidIndex(int index) {
+            if (index < mIndexMin) {
+                return mIndexMin;
+            } else if (mUseFixedVolume || index > mIndexMax) {
+                return mIndexMax;
+            }
+            return index;
+        }
+
+        public @NonNull String getSettingNameForDevice(int device) {
+            final String suffix = AudioSystem.getOutputDeviceName(device);
+            if (suffix.isEmpty()) {
+                return mAudioVolumeGroup.name();
+            }
+            return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+            pw.print("   Min: ");
+            pw.println(mIndexMin);
+            pw.print("   Max: ");
+            pw.println(mIndexMax);
+            pw.print("   Current: ");
+            for (int i = 0; i < mIndexMap.size(); i++) {
+                if (i > 0) {
+                    pw.print(", ");
+                }
+                final int device = mIndexMap.keyAt(i);
+                pw.print(Integer.toHexString(device));
+                final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+                        : AudioSystem.getOutputDeviceName(device);
+                if (!deviceName.isEmpty()) {
+                    pw.print(" (");
+                    pw.print(deviceName);
+                    pw.print(")");
+                }
+                pw.print(": ");
+                pw.print(mIndexMap.valueAt(i));
+            }
+            pw.println();
+            pw.print("   Devices: ");
+            int n = 0;
+            final int devices = getDeviceForVolume();
+            for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+                if ((devices & device) == device) {
+                    if (n++ > 0) {
+                        pw.print(", ");
+                    }
+                    pw.print(AudioSystem.getOutputDeviceName(device));
+                }
+            }
+        }
+    }
+
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
@@ -5298,6 +5652,11 @@
                     persistVolume((VolumeStreamState) msg.obj, msg.arg1);
                     break;
 
+                case MSG_PERSIST_VOLUME_GROUP:
+                    final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
+                    vgs.persistVolumeGroup(msg.arg1);
+                    break;
+
                 case MSG_PERSIST_RINGER_MODE:
                     // note that the value persisted is the current ringer mode, not the
                     // value of ringer mode as of the time the request was made to persist
@@ -6312,7 +6671,7 @@
     static final int LOG_NB_EVENTS_DYN_POLICY = 10;
 
     final private AudioEventLogger mModeLogger = new AudioEventLogger(LOG_NB_EVENTS_PHONE_STATE,
-            "phone state (logged after successfull call to AudioSystem.setPhoneState(int))");
+            "phone state (logged after successful call to AudioSystem.setPhoneState(int, int))");
 
     // logs for wired + A2DP device connections:
     // - wired: logged before onSetWiredDeviceConnectionState() is executed
@@ -6395,6 +6754,7 @@
         }
         mMediaFocusControl.dump(pw);
         dumpStreamStates(pw);
+        dumpVolumeGroups(pw);
         dumpRingerMode(pw);
         pw.println("\nAudio routes:");
         pw.print("  mMainType=0x"); pw.println(Integer.toHexString(
@@ -7549,22 +7909,16 @@
 
         int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
             final Integer Uid = new Integer(uid);
-            int res;
             if (mUidDeviceAffinities.remove(Uid) != null) {
-                final long identity = Binder.clearCallingIdentity();
-                res = AudioSystem.removeUidDeviceAffinities(uid);
-                Binder.restoreCallingIdentity(identity);
-                if (res != AudioSystem.SUCCESS) {
+                if (removeUidDeviceAffinitiesFromSystem(uid) != AudioSystem.SUCCESS) {
                     Log.e(TAG, "AudioSystem. removeUidDeviceAffinities(" + uid + ") failed, "
                             + " cannot call AudioSystem.setUidDeviceAffinities");
                     return AudioManager.ERROR;
                 }
             }
-            final long identity = Binder.clearCallingIdentity();
-            res = AudioSystem.setUidDeviceAffinities(uid, types, addresses);
-            Binder.restoreCallingIdentity(identity);
-            if (res == AudioSystem.SUCCESS) {
-                mUidDeviceAffinities.put(Uid, new AudioDeviceArray(types, addresses));
+            AudioDeviceArray deviceArray = new AudioDeviceArray(types, addresses);
+            if (setUidDeviceAffinitiesOnSystem(uid, deviceArray) == AudioSystem.SUCCESS) {
+                mUidDeviceAffinities.put(Uid, deviceArray);
                 return AudioManager.SUCCESS;
             }
             Log.e(TAG, "AudioSystem. setUidDeviceAffinities(" + uid + ") failed");
@@ -7573,10 +7927,7 @@
 
         int removeUidDeviceAffinities(int uid) {
             if (mUidDeviceAffinities.remove(new Integer(uid)) != null) {
-                final long identity = Binder.clearCallingIdentity();
-                final int res = AudioSystem.removeUidDeviceAffinities(uid);
-                Binder.restoreCallingIdentity(identity);
-                if (res == AudioSystem.SUCCESS) {
+                if (removeUidDeviceAffinitiesFromSystem(uid) == AudioSystem.SUCCESS) {
                     return AudioManager.SUCCESS;
                 }
             }
@@ -7584,26 +7935,41 @@
             return AudioManager.ERROR;
         }
 
+        @AudioSystem.AudioSystemError private int removeUidDeviceAffinitiesFromSystem(int uid) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return AudioSystem.removeUidDeviceAffinities(uid);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @AudioSystem.AudioSystemError private int setUidDeviceAffinitiesOnSystem(int uid,
+                AudioDeviceArray deviceArray) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return AudioSystem.setUidDeviceAffinities(uid, deviceArray.mDeviceTypes,
+                        deviceArray.mDeviceAddresses);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
         int setUserIdDeviceAffinities(int userId,
                 @NonNull int[] types, @NonNull String[] addresses) {
             final Integer UserId = new Integer(userId);
-            int res;
             if (mUserIdDeviceAffinities.remove(UserId) != null) {
-                final long identity = Binder.clearCallingIdentity();
-                res = AudioSystem.removeUserIdDeviceAffinities(UserId);
-                Binder.restoreCallingIdentity(identity);
-                if (res != AudioSystem.SUCCESS) {
+                if (removeUserIdDeviceAffinitiesFromSystem(userId) != AudioSystem.SUCCESS) {
                     Log.e(TAG, "AudioSystem. removeUserIdDeviceAffinities("
                             + UserId + ") failed, "
                             + " cannot call AudioSystem.setUserIdDeviceAffinities");
                     return AudioManager.ERROR;
                 }
             }
-            final long identity = Binder.clearCallingIdentity();
-            res = AudioSystem.setUserIdDeviceAffinities(userId, types, addresses);
-            Binder.restoreCallingIdentity(identity);
-            if (res == AudioSystem.SUCCESS) {
-                mUserIdDeviceAffinities.put(UserId, new AudioDeviceArray(types, addresses));
+            AudioDeviceArray audioDeviceArray = new AudioDeviceArray(types, addresses);
+            if (setUserIdDeviceAffinitiesOnSystem(userId, audioDeviceArray)
+                    == AudioSystem.SUCCESS) {
+                mUserIdDeviceAffinities.put(UserId, audioDeviceArray);
                 return AudioManager.SUCCESS;
             }
             Log.e(TAG, "AudioSystem.setUserIdDeviceAffinities(" + userId + ") failed");
@@ -7612,10 +7978,7 @@
 
         int removeUserIdDeviceAffinities(int userId) {
             if (mUserIdDeviceAffinities.remove(new Integer(userId)) != null) {
-                final long identity = Binder.clearCallingIdentity();
-                final int res = AudioSystem.removeUserIdDeviceAffinities(userId);
-                Binder.restoreCallingIdentity(identity);
-                if (res == AudioSystem.SUCCESS) {
+                if (removeUserIdDeviceAffinitiesFromSystem(userId) == AudioSystem.SUCCESS) {
                     return AudioManager.SUCCESS;
                 }
             }
@@ -7623,9 +7986,76 @@
             return AudioManager.ERROR;
         }
 
+        @AudioSystem.AudioSystemError private int removeUserIdDeviceAffinitiesFromSystem(
+                @UserIdInt int userId) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return AudioSystem.removeUserIdDeviceAffinities(userId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @AudioSystem.AudioSystemError private int setUserIdDeviceAffinitiesOnSystem(
+                @UserIdInt int userId, AudioDeviceArray deviceArray) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return AudioSystem.setUserIdDeviceAffinities(userId, deviceArray.mDeviceTypes,
+                        deviceArray.mDeviceAddresses);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @AudioSystem.AudioSystemError int setupDeviceAffinities() {
+            for (Map.Entry<Integer, AudioDeviceArray> uidEntry : mUidDeviceAffinities.entrySet()) {
+                int uidStatus = removeUidDeviceAffinitiesFromSystem(uidEntry.getKey());
+                if (uidStatus != AudioSystem.SUCCESS) {
+                    Log.e(TAG,
+                            "setupDeviceAffinities failed to remove device affinity for uid "
+                                    + uidEntry.getKey());
+                    return uidStatus;
+                }
+                uidStatus = setUidDeviceAffinitiesOnSystem(uidEntry.getKey(), uidEntry.getValue());
+                if (uidStatus != AudioSystem.SUCCESS) {
+                    Log.e(TAG,
+                            "setupDeviceAffinities failed to set device affinity for uid "
+                                    + uidEntry.getKey());
+                    return uidStatus;
+                }
+            }
+
+            for (Map.Entry<Integer, AudioDeviceArray> userIdEntry :
+                    mUserIdDeviceAffinities.entrySet()) {
+                int userIdStatus = removeUserIdDeviceAffinitiesFromSystem(userIdEntry.getKey());
+                if (userIdStatus != AudioSystem.SUCCESS) {
+                    Log.e(TAG,
+                            "setupDeviceAffinities failed to remove device affinity for userId "
+                                    + userIdEntry.getKey());
+                    return userIdStatus;
+                }
+                userIdStatus = setUserIdDeviceAffinitiesOnSystem(userIdEntry.getKey(),
+                                userIdEntry.getValue());
+                if (userIdStatus != AudioSystem.SUCCESS) {
+                    Log.e(TAG,
+                            "setupDeviceAffinities failed to set device affinity for userId "
+                                    + userIdEntry.getKey());
+                    return userIdStatus;
+                }
+            }
+            return AudioSystem.SUCCESS;
+        }
+
         /** @return human readable debug informations summarizing the state of the object. */
         public String toLogFriendlyString() {
             String textDump = super.toLogFriendlyString();
+            textDump += " Uid Device Affinities:\n";
+            String spacer = "     ";
+            textDump += logFriendlyAttributeDeviceArrayMap("Uid",
+                    mUidDeviceAffinities, spacer);
+            textDump += " UserId Device Affinities:\n";
+            textDump += logFriendlyAttributeDeviceArrayMap("UserId",
+                    mUserIdDeviceAffinities, spacer);
             textDump += " Proxy:\n";
             textDump += "   is focus policy= " + mIsFocusPolicy + "\n";
             if (mIsFocusPolicy) {
@@ -7636,6 +8066,24 @@
             textDump += "   media projection= " + mProjection + "\n";
             return textDump;
         }
+
+        private String logFriendlyAttributeDeviceArrayMap(String attribute,
+                Map<Integer, AudioDeviceArray> map, String spacer) {
+            final StringBuilder stringBuilder = new StringBuilder();
+            for (Map.Entry<Integer, AudioDeviceArray> mapEntry : map.entrySet()) {
+                stringBuilder.append(spacer).append(attribute).append(": ")
+                        .append(mapEntry.getKey()).append("\n");
+                AudioDeviceArray deviceArray = mapEntry.getValue();
+                String deviceSpacer = spacer + "   ";
+                for (int i = 0; i < deviceArray.mDeviceTypes.length; i++) {
+                    stringBuilder.append(deviceSpacer).append("Type: 0x")
+                            .append(Integer.toHexString(deviceArray.mDeviceTypes[i]))
+                            .append(" Address: ").append(deviceArray.mDeviceAddresses[i])
+                                    .append("\n");
+                }
+            }
+            return stringBuilder.toString();
+        }
     };
 
     //======================
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index fcd8701..add620e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,6 +16,7 @@
 
 package com.android.server.audio;
 
+import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 
@@ -97,12 +98,15 @@
         static final int VOL_ADJUST_VOL_UID = 5;
         static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
         static final int VOL_MODE_CHANGE_HEARING_AID = 7;
+        static final int VOL_SET_GROUP_VOL = 8;
 
         final int mOp;
         final int mStream;
         final int mVal1;
         final int mVal2;
         final String mCaller;
+        final String mGroupName;
+        final AudioAttributes mAudioAttributes;
 
         /** used for VOL_ADJUST_VOL_UID,
          *           VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@
             mVal1 = val1;
             mVal2 = val2;
             mCaller = caller;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@
             // unused
             mStream = -1;
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@
             mVal2 = 0;
             mStream = -1;
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@
             mVal2 = voiceActive ? 1 : 0;
             // unused
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
         }
 
         /** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@
             mVal2 = mode;
             // unused
             mCaller = null;
+            mGroupName = null;
+            mAudioAttributes = null;
+        }
+
+        /** used for VOL_SET_GROUP_VOL */
+        VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+            mOp = op;
+            mStream = -1;
+            mVal1 = index;
+            mVal2 = flags;
+            mCaller = caller;
+            mGroupName = group;
+            mAudioAttributes = aa;
         }
 
         @Override
@@ -208,6 +233,14 @@
                             .append(") causes setting HEARING_AID volume to idx:").append(mVal1)
                             .append(" stream:").append(AudioSystem.streamToString(mStream))
                             .toString();
+                case VOL_SET_GROUP_VOL:
+                    return new StringBuilder("setVolumeIndexForAttributes(attr:")
+                            .append(mAudioAttributes.toString())
+                            .append(" group: ").append(mGroupName)
+                            .append(" index:").append(mVal1)
+                            .append(" flags:0x").append(Integer.toHexString(mVal2))
+                            .append(") from ").append(mCaller)
+                            .toString();
                 default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index c9c2c96..204f072 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -153,7 +153,12 @@
 
             // Only allow internal clients to enable non-public options.
             if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS)
-                    || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
+                    || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)
+                    || bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE) != null
+                    || bundle.getCharSequence(
+                            BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE) != null
+                    || bundle.getCharSequence(
+                            BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null) {
                 checkInternalPermission();
             }
 
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 2eec419..8687f35 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -17,6 +17,7 @@
 package com.android.server.compat;
 
 import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.pm.ApplicationInfo;
 
@@ -39,6 +40,13 @@
 public final class CompatChange extends CompatibilityChangeInfo {
 
     /**
+     * A change ID to be used only in the CTS test for this SystemApi
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = 1234) // Needs to be > test APK targetSdkVersion.
+    private static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
+
+    /**
      * Callback listener for when compat changes are updated for a package.
      * See {@link #registerListener(ChangeListener)} for more details.
      */
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cb88c4e..1a68f1b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,8 +48,12 @@
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
+import android.net.Ikev2VpnProfile;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
@@ -65,6 +69,12 @@
 import android.net.UidRange;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionParams;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -113,6 +123,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -122,6 +133,9 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -176,14 +190,14 @@
 
     private final Context mContext;
     private final NetworkInfo mNetworkInfo;
-    private String mPackage;
+    @VisibleForTesting protected String mPackage;
     private int mOwnerUID;
     private boolean mIsPackageTargetingAtLeastQ;
     private String mInterface;
     private Connection mConnection;
 
     /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
-    private VpnRunner mVpnRunner;
+    @VisibleForTesting protected VpnRunner mVpnRunner;
 
     private PendingIntent mStatusIntent;
     private volatile boolean mEnableTeardown = true;
@@ -196,6 +210,7 @@
     @VisibleForTesting
     protected final NetworkCapabilities mNetworkCapabilities;
     private final SystemServices mSystemServices;
+    private final Ikev2SessionCreator mIkev2SessionCreator;
 
     /**
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -238,17 +253,20 @@
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
             @UserIdInt int userHandle) {
-        this(looper, context, netService, userHandle, new SystemServices(context));
+        this(looper, context, netService, userHandle,
+                new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, INetworkManagementService netService,
-            int userHandle, SystemServices systemServices) {
+            int userHandle, SystemServices systemServices,
+            Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
         mNetd = netService;
         mUserHandle = userHandle;
         mLooper = looper;
         mSystemServices = systemServices;
+        mIkev2SessionCreator = ikev2SessionCreator;
 
         mPackage = VpnConfig.LEGACY_VPN;
         mOwnerUID = getAppUid(mPackage, mUserHandle);
@@ -749,8 +767,9 @@
 
     private boolean isCurrentPreparedPackage(String packageName) {
         // We can't just check that packageName matches mPackage, because if the app was uninstalled
-        // and reinstalled it will no longer be prepared. Instead check the UID.
-        return getAppUid(packageName, mUserHandle) == mOwnerUID;
+        // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
+        // calling package may not be the same as the prepared package. Check both UID and package.
+        return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
     }
 
     /** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -979,7 +998,11 @@
         }
         lp.setDomains(buffer.toString().trim());
 
-        // TODO: Stop setting the MTU in jniCreate and set it here.
+        if (mConfig.mtu > 0) {
+            lp.setMtu(mConfig.mtu);
+        }
+
+        // TODO: Stop setting the MTU in jniCreate
 
         return lp;
     }
@@ -2004,30 +2027,369 @@
         protected abstract void exit();
     }
 
-    private class IkeV2VpnRunner extends VpnRunner {
-        private static final String TAG = "IkeV2VpnRunner";
+    interface IkeV2VpnRunnerCallback {
+        void onDefaultNetworkChanged(@NonNull Network network);
 
-        private final IpSecManager mIpSecManager;
-        private final VpnProfile mProfile;
+        void onChildOpened(
+                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
 
-        IkeV2VpnRunner(VpnProfile profile) {
+        void onChildTransformCreated(
+                @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+
+        void onSessionLost(@NonNull Network network);
+    }
+
+    /**
+     * Internal class managing IKEv2/IPsec VPN connectivity
+     *
+     * <p>The IKEv2 VPN will listen to, and run based on the lifecycle of Android's default Network.
+     * As a new default is selected, old IKE sessions will be torn down, and a new one will be
+     * started.
+     *
+     * <p>This class uses locking minimally - the Vpn instance lock is only ever held when fields of
+     * the outer class are modified. As such, care must be taken to ensure that no calls are added
+     * that might modify the outer class' state without acquiring a lock.
+     *
+     * <p>The overall structure of the Ikev2VpnRunner is as follows:
+     *
+     * <ol>
+     *   <li>Upon startup, a NetworkRequest is registered with ConnectivityManager. This is called
+     *       any time a new default network is selected
+     *   <li>When a new default is connected, an IKE session is started on that Network. If there
+     *       were any existing IKE sessions on other Networks, they are torn down before starting
+     *       the new IKE session
+     *   <li>Upon establishment, the onChildTransformCreated() callback is called twice, one for
+     *       each direction, and finally onChildOpened() is called
+     *   <li>Upon the onChildOpened() call, the VPN is fully set up.
+     *   <li>Subsequent Network changes result in new onDefaultNetworkChanged() callbacks. See (2).
+     * </ol>
+     */
+    class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
+        @NonNull private static final String TAG = "IkeV2VpnRunner";
+
+        @NonNull private final IpSecManager mIpSecManager;
+        @NonNull private final Ikev2VpnProfile mProfile;
+        @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+
+        /**
+         * Executor upon which ALL callbacks must be run.
+         *
+         * <p>This executor MUST be a single threaded executor, in order to ensure the consistency
+         * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
+         * virtue of everything being serialized on this executor.
+         */
+        @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+        /** Signal to ensure shutdown is honored even if a new Network is connected. */
+        private boolean mIsRunning = true;
+
+        @Nullable private UdpEncapsulationSocket mEncapSocket;
+        @Nullable private IpSecTunnelInterface mTunnelIface;
+        @Nullable private IkeSession mSession;
+        @Nullable private Network mActiveNetwork;
+
+        IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
             super(TAG);
             mProfile = profile;
-
-            // TODO: move this to startVpnRunnerPrivileged()
-            mConfig = new VpnConfig();
-            mIpSecManager = mContext.getSystemService(IpSecManager.class);
+            mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+            mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
         }
 
         @Override
         public void run() {
-            // TODO: Build IKE config, start IKE session
+            // Explicitly use only the network that ConnectivityService thinks is the "best." In
+            // other words, only ever use the currently selected default network. This does mean
+            // that in both onLost() and onConnected(), any old sessions MUST be torn down. This
+            // does NOT include VPNs.
+            final ConnectivityManager cm = ConnectivityManager.from(mContext);
+            cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
+        }
+
+        private boolean isActiveNetwork(@Nullable Network network) {
+            return Objects.equals(mActiveNetwork, network) && mIsRunning;
+        }
+
+        /**
+         * Called when an IKE Child session has been opened, signalling completion of the startup.
+         *
+         * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
+         * thread in order to ensure consistency of the Ikev2VpnRunner fields.
+         */
+        public void onChildOpened(
+                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "onOpened called for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+                // IKE session was already shut down (exited, or an error was encountered somewhere
+                // else). In both cases, all resources and sessions are torn down via
+                // resetIkeState().
+                return;
+            }
+
+            try {
+                final String interfaceName = mTunnelIface.getInterfaceName();
+                final int maxMtu = mProfile.getMaxMtu();
+                final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
+
+                final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
+                        childConfig.getOutboundTrafficSelectors());
+                for (final LinkAddress address : internalAddresses) {
+                    mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+                }
+
+                final NetworkAgent networkAgent;
+                final LinkProperties lp;
+
+                synchronized (Vpn.this) {
+                    mInterface = interfaceName;
+                    mConfig.mtu = maxMtu;
+                    mConfig.interfaze = mInterface;
+
+                    mConfig.addresses.clear();
+                    mConfig.addresses.addAll(internalAddresses);
+
+                    mConfig.routes.clear();
+                    mConfig.routes.addAll(newRoutes);
+
+                    // TODO: Add DNS servers from negotiation
+
+                    networkAgent = mNetworkAgent;
+
+                    // The below must be done atomically with the mConfig update, otherwise
+                    // isRunningLocked() will be racy.
+                    if (networkAgent == null) {
+                        if (isSettingsVpnLocked()) {
+                            prepareStatusIntent();
+                        }
+                        agentConnect();
+                        return; // Link properties are already sent.
+                    }
+
+                    lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
+                }
+
+                networkAgent.sendLinkProperties(lp);
+            } catch (Exception e) {
+                Log.d(TAG, "Error in ChildOpened for network " + network, e);
+                onSessionLost(network);
+            }
+        }
+
+        /**
+         * Called when an IPsec transform has been created, and should be applied.
+         *
+         * <p>This method is called multiple times over the lifetime of an IkeSession (or default
+         * network), and is MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        public void onChildTransformCreated(
+                @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+                // IKE session was already shut down (exited, or an error was encountered somewhere
+                // else). In both cases, all resources and sessions are torn down via
+                // resetIkeState().
+                return;
+            }
+
+            try {
+                // Transforms do not need to be persisted; the IkeSession will keep
+                // them alive for us
+                mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
+            } catch (IOException e) {
+                Log.d(TAG, "Transform application failed for network " + network, e);
+                onSessionLost(network);
+            }
+        }
+
+        /**
+         * Called when a new default network is connected.
+         *
+         * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
+         * state in the process, and starting a new IkeSession instance.
+         *
+         * <p>This method is called multiple times over the lifetime of the Ikev2VpnRunner, and is
+         * called on the ConnectivityService thread. Thus, the actual work MUST be proxied to the
+         * mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
+         */
+        public void onDefaultNetworkChanged(@NonNull Network network) {
+            Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
+
+            // Proxy to the Ikev2VpnRunner (single-thread) executor to ensure consistency in lieu
+            // of locking.
+            mExecutor.execute(() -> {
+                try {
+                    if (!mIsRunning) {
+                        Log.d(TAG, "onDefaultNetworkChanged after exit");
+                        return; // VPN has been shut down.
+                    }
+
+                    // Without MOBIKE, we have no way to seamlessly migrate. Close on old
+                    // (non-default) network, and start the new one.
+                    resetIkeState();
+                    mActiveNetwork = network;
+
+                    // TODO(b/149356682): Update this based on new IKE API
+                    mEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+
+                    // TODO(b/149356682): Update this based on new IKE API
+                    final IkeSessionParams ikeSessionParams =
+                            VpnIkev2Utils.buildIkeSessionParams(mProfile, mEncapSocket);
+                    final ChildSessionParams childSessionParams =
+                            VpnIkev2Utils.buildChildSessionParams();
+
+                    // TODO: Remove the need for adding two unused addresses with
+                    // IPsec tunnels.
+                    mTunnelIface =
+                            mIpSecManager.createIpSecTunnelInterface(
+                                    ikeSessionParams.getServerAddress() /* unused */,
+                                    ikeSessionParams.getServerAddress() /* unused */,
+                                    network);
+                    mNetd.setInterfaceUp(mTunnelIface.getInterfaceName());
+
+                    // Socket must be bound to prevent network switches from causing
+                    // the IKE teardown to fail/timeout.
+                    // TODO(b/149356682): Update this based on new IKE API
+                    network.bindSocket(mEncapSocket.getFileDescriptor());
+
+                    mSession = mIkev2SessionCreator.createIkeSession(
+                            mContext,
+                            ikeSessionParams,
+                            childSessionParams,
+                            mExecutor,
+                            new VpnIkev2Utils.IkeSessionCallbackImpl(
+                                    TAG, IkeV2VpnRunner.this, network),
+                            new VpnIkev2Utils.ChildSessionCallbackImpl(
+                                    TAG, IkeV2VpnRunner.this, network));
+                    Log.d(TAG, "Ike Session started for network " + network);
+                } catch (Exception e) {
+                    Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
+                    onSessionLost(network);
+                }
+            });
+        }
+
+        /**
+         * Handles loss of a session
+         *
+         * <p>The loss of a session might be due to an onLost() call, the IKE session getting torn
+         * down for any reason, or an error in updating state (transform application, VPN setup)
+         *
+         * <p>This method MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        public void onSessionLost(@NonNull Network network) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+                // IKE session was already shut down (exited, or an error was encountered somewhere
+                // else). In both cases, all resources and sessions are torn down via
+                // onSessionLost() and resetIkeState().
+                return;
+            }
+
+            mActiveNetwork = null;
+
+            // Close all obsolete state, but keep VPN alive incase a usable network comes up.
+            // (Mirrors VpnService behavior)
+            Log.d(TAG, "Resetting state for network: " + network);
+
+            synchronized (Vpn.this) {
+                // Since this method handles non-fatal errors only, set mInterface to null to
+                // prevent the NetworkManagementEventObserver from killing this VPN based on the
+                // interface going down (which we expect).
+                mInterface = null;
+                mConfig.interfaze = null;
+
+                // Set as unroutable to prevent traffic leaking while the interface is down.
+                if (mConfig != null && mConfig.routes != null) {
+                    final List<RouteInfo> oldRoutes = new ArrayList<>(mConfig.routes);
+
+                    mConfig.routes.clear();
+                    for (final RouteInfo route : oldRoutes) {
+                        mConfig.routes.add(new RouteInfo(route.getDestination(), RTN_UNREACHABLE));
+                    }
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendLinkProperties(makeLinkProperties());
+                    }
+                }
+            }
+
+            resetIkeState();
+        }
+
+        /**
+         * Cleans up all IKE state
+         *
+         * <p>This method MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        private void resetIkeState() {
+            if (mTunnelIface != null) {
+                // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
+                mTunnelIface.close();
+                mTunnelIface = null;
+            }
+            if (mSession != null) {
+                mSession.kill(); // Kill here to make sure all resources are released immediately
+                mSession = null;
+            }
+
+            // TODO(b/149356682): Update this based on new IKE API
+            if (mEncapSocket != null) {
+                try {
+                    mEncapSocket.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to close encap socket", e);
+                }
+                mEncapSocket = null;
+            }
+        }
+
+        /**
+         * Triggers cleanup of outer class' state
+         *
+         * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
+         */
+        private void cleanupVpnState() {
+            synchronized (Vpn.this) {
+                agentDisconnect();
+            }
+        }
+
+        /**
+         * Cleans up all Ikev2VpnRunner internal state
+         *
+         * <p>This method MUST always be called on the mExecutor thread in order to ensure
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        private void shutdownVpnRunner() {
+            mActiveNetwork = null;
+            mIsRunning = false;
+
+            resetIkeState();
+
+            final ConnectivityManager cm = ConnectivityManager.from(mContext);
+            cm.unregisterNetworkCallback(mNetworkCallback);
+
+            mExecutor.shutdown();
         }
 
         @Override
         public void exit() {
-            // TODO: Teardown IKE session & any resources.
-            agentDisconnect();
+            // Cleanup outer class' state immediately, otherwise race conditions may ensue.
+            cleanupVpnState();
+
+            mExecutor.execute(() -> {
+                shutdownVpnRunner();
+            });
         }
     }
 
@@ -2488,12 +2850,46 @@
                         throw new IllegalArgumentException("No profile found for " + packageName);
                     }
 
-                    startVpnProfilePrivileged(profile);
+                    startVpnProfilePrivileged(profile, packageName);
                 });
     }
 
-    private void startVpnProfilePrivileged(@NonNull VpnProfile profile) {
-        // TODO: Start PlatformVpnRunner
+    private void startVpnProfilePrivileged(
+            @NonNull VpnProfile profile, @NonNull String packageName) {
+        // Ensure that no other previous instance is running.
+        if (mVpnRunner != null) {
+            mVpnRunner.exit();
+            mVpnRunner = null;
+        }
+        updateState(DetailedState.CONNECTING, "startPlatformVpn");
+
+        try {
+            // Build basic config
+            mConfig = new VpnConfig();
+            mConfig.user = packageName;
+            mConfig.isMetered = profile.isMetered;
+            mConfig.startTime = SystemClock.elapsedRealtime();
+            mConfig.proxyInfo = profile.proxy;
+
+            switch (profile.type) {
+                case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+                case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+                    mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+                    mVpnRunner.start();
+                    break;
+                default:
+                    updateState(DetailedState.FAILED, "Invalid platform VPN type");
+                    Log.d(TAG, "Unknown VPN profile type: " + profile.type);
+                    break;
+            }
+        } catch (IOException | GeneralSecurityException e) {
+            // Reset mConfig
+            mConfig = null;
+
+            updateState(DetailedState.FAILED, "VPN startup failed");
+            throw new IllegalArgumentException("VPN startup failed", e);
+        }
     }
 
     /**
@@ -2507,13 +2903,37 @@
     public synchronized void stopVpnProfile(@NonNull String packageName) {
         checkNotNull(packageName, "No package name provided");
 
-        // To stop the VPN profile, the caller must be the current prepared package. Otherwise,
-        // the app is not prepared, and we can just return.
-        if (!isCurrentPreparedPackage(packageName)) {
-            // TODO: Also check to make sure that the running VPN is a VPN profile.
+        // To stop the VPN profile, the caller must be the current prepared package and must be
+        // running an Ikev2VpnProfile.
+        if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
             return;
         }
 
         prepareInternal(VpnConfig.LEGACY_VPN);
     }
+
+    /**
+     * Proxy to allow testing
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Ikev2SessionCreator {
+        /** Creates a IKE session */
+        public IkeSession createIkeSession(
+                @NonNull Context context,
+                @NonNull IkeSessionParams ikeSessionParams,
+                @NonNull ChildSessionParams firstChildSessionParams,
+                @NonNull Executor userCbExecutor,
+                @NonNull IkeSessionCallback ikeSessionCallback,
+                @NonNull ChildSessionCallback firstChildSessionCallback) {
+            return new IkeSession(
+                    context,
+                    ikeSessionParams,
+                    firstChildSessionParams,
+                    userCbExecutor,
+                    ikeSessionCallback,
+                    firstChildSessionCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
new file mode 100644
index 0000000..33fc32b
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+
+import android.annotation.NonNull;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.util.IpRange;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility class to build and convert IKEv2/IPsec parameters.
+ *
+ * @hide
+ */
+public class VpnIkev2Utils {
+    static IkeSessionParams buildIkeSessionParams(
+            @NonNull Ikev2VpnProfile profile, @NonNull UdpEncapsulationSocket socket) {
+        // TODO(b/149356682): Update this based on new IKE API. Only numeric addresses supported
+        //                    until then. All others throw IAE (caught by caller).
+        final InetAddress serverAddr = InetAddresses.parseNumericAddress(profile.getServerAddr());
+        final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
+        final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
+
+        // TODO(b/149356682): Update this based on new IKE API.
+        final IkeSessionParams.Builder ikeOptionsBuilder =
+                new IkeSessionParams.Builder()
+                        .setServerAddress(serverAddr)
+                        .setUdpEncapsulationSocket(socket)
+                        .setLocalIdentification(localId)
+                        .setRemoteIdentification(remoteId);
+        setIkeAuth(profile, ikeOptionsBuilder);
+
+        for (final IkeSaProposal ikeProposal : getIkeSaProposals()) {
+            ikeOptionsBuilder.addSaProposal(ikeProposal);
+        }
+
+        return ikeOptionsBuilder.build();
+    }
+
+    static ChildSessionParams buildChildSessionParams() {
+        final TunnelModeChildSessionParams.Builder childOptionsBuilder =
+                new TunnelModeChildSessionParams.Builder();
+
+        for (final ChildSaProposal childProposal : getChildSaProposals()) {
+            childOptionsBuilder.addSaProposal(childProposal);
+        }
+
+        childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET);
+        childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET6);
+        childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET);
+        childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET6);
+
+        return childOptionsBuilder.build();
+    }
+
+    private static void setIkeAuth(
+            @NonNull Ikev2VpnProfile profile, @NonNull IkeSessionParams.Builder builder) {
+        switch (profile.getType()) {
+            case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+                final EapSessionConfig eapConfig =
+                        new EapSessionConfig.Builder()
+                                .setEapMsChapV2Config(profile.getUsername(), profile.getPassword())
+                                .build();
+                builder.setAuthEap(profile.getServerRootCaCert(), eapConfig);
+                break;
+            case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                builder.setAuthPsk(profile.getPresharedKey());
+                break;
+            case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+                builder.setAuthDigitalSignature(
+                        profile.getServerRootCaCert(),
+                        profile.getUserCert(),
+                        profile.getRsaPrivateKey());
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown auth method set");
+        }
+    }
+
+    private static List<IkeSaProposal> getIkeSaProposals() {
+        // TODO: filter this based on allowedAlgorithms
+        final List<IkeSaProposal> proposals = new ArrayList<>();
+
+        // Encryption Algorithms: Currently only AES_CBC is supported.
+        final IkeSaProposal.Builder normalModeBuilder = new IkeSaProposal.Builder();
+
+        // Currently only AES_CBC is supported.
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+        // Authentication/Integrity Algorithms
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+        // Add AEAD options
+        final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder();
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+        // Add dh, prf for both builders
+        for (final IkeSaProposal.Builder builder : Arrays.asList(normalModeBuilder, aeadBuilder)) {
+            builder.addDhGroup(DH_GROUP_2048_BIT_MODP);
+            builder.addDhGroup(DH_GROUP_1024_BIT_MODP);
+            builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+            builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+        }
+
+        proposals.add(normalModeBuilder.build());
+        proposals.add(aeadBuilder.build());
+        return proposals;
+    }
+
+    private static List<ChildSaProposal> getChildSaProposals() {
+        // TODO: filter this based on allowedAlgorithms
+        final List<ChildSaProposal> proposals = new ArrayList<>();
+
+        // Add non-AEAD options
+        final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();
+
+        // Encryption Algorithms: Currently only AES_CBC is supported.
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+        normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+        // Authentication/Integrity Algorithms
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+        // Add AEAD options
+        final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder();
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+        aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+        proposals.add(normalModeBuilder.build());
+        proposals.add(aeadBuilder.build());
+        return proposals;
+    }
+
+    static class IkeSessionCallbackImpl implements IkeSessionCallback {
+        private final String mTag;
+        private final Vpn.IkeV2VpnRunnerCallback mCallback;
+        private final Network mNetwork;
+
+        IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+            mTag = tag;
+            mCallback = callback;
+            mNetwork = network;
+        }
+
+        @Override
+        public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
+            Log.d(mTag, "IkeOpened for network " + mNetwork);
+            // Nothing to do here.
+        }
+
+        @Override
+        public void onClosed() {
+            Log.d(mTag, "IkeClosed for network " + mNetwork);
+            mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+        }
+
+        @Override
+        public void onClosedExceptionally(@NonNull IkeException exception) {
+            Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
+            mCallback.onSessionLost(mNetwork);
+        }
+
+        @Override
+        public void onError(@NonNull IkeProtocolException exception) {
+            Log.d(mTag, "IkeError for network " + mNetwork, exception);
+            // Non-fatal, log and continue.
+        }
+    }
+
+    static class ChildSessionCallbackImpl implements ChildSessionCallback {
+        private final String mTag;
+        private final Vpn.IkeV2VpnRunnerCallback mCallback;
+        private final Network mNetwork;
+
+        ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+            mTag = tag;
+            mCallback = callback;
+            mNetwork = network;
+        }
+
+        @Override
+        public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
+            Log.d(mTag, "ChildOpened for network " + mNetwork);
+            mCallback.onChildOpened(mNetwork, childConfig);
+        }
+
+        @Override
+        public void onClosed() {
+            Log.d(mTag, "ChildClosed for network " + mNetwork);
+            mCallback.onSessionLost(mNetwork);
+        }
+
+        @Override
+        public void onClosedExceptionally(@NonNull IkeException exception) {
+            Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
+            mCallback.onSessionLost(mNetwork);
+        }
+
+        @Override
+        public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
+            Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
+            mCallback.onChildTransformCreated(mNetwork, transform, direction);
+        }
+
+        @Override
+        public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
+            // Nothing to be done; no references to the IpSecTransform are held by the
+            // Ikev2VpnRunner (or this callback class), and this transform will be closed by the
+            // IKE library.
+            Log.d(mTag,
+                    "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+        }
+    }
+
+    static class Ikev2VpnNetworkCallback extends NetworkCallback {
+        private final String mTag;
+        private final Vpn.IkeV2VpnRunnerCallback mCallback;
+
+        Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback) {
+            mTag = tag;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
+            mCallback.onDefaultNetworkChanged(network);
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            Log.d(mTag, "Tearing down; lost network: " + network);
+            mCallback.onSessionLost(network);
+        }
+    }
+
+    /**
+     * Identity parsing logic using similar logic to open source implementations of IKEv2
+     *
+     * <p>This method does NOT support using type-prefixes (eg 'fqdn:' or 'keyid'), or ASN.1 encoded
+     * identities.
+     */
+    private static IkeIdentification parseIkeIdentification(@NonNull String identityStr) {
+        // TODO: Add identity formatting to public API javadocs.
+        if (identityStr.contains("@")) {
+            if (identityStr.startsWith("@#")) {
+                // KEY_ID
+                final String hexStr = identityStr.substring(2);
+                return new IkeKeyIdIdentification(HexDump.hexStringToByteArray(hexStr));
+            } else if (identityStr.startsWith("@@")) {
+                // RFC822 (USER_FQDN)
+                return new IkeRfc822AddrIdentification(identityStr.substring(2));
+            } else if (identityStr.startsWith("@")) {
+                // FQDN
+                return new IkeFqdnIdentification(identityStr.substring(1));
+            } else {
+                // RFC822 (USER_FQDN)
+                return new IkeRfc822AddrIdentification(identityStr);
+            }
+        } else if (InetAddresses.isNumericAddress(identityStr)) {
+            final InetAddress addr = InetAddresses.parseNumericAddress(identityStr);
+            if (addr instanceof Inet4Address) {
+                // IPv4
+                return new IkeIpv4AddrIdentification((Inet4Address) addr);
+            } else if (addr instanceof Inet6Address) {
+                // IPv6
+                return new IkeIpv6AddrIdentification((Inet6Address) addr);
+            } else {
+                throw new IllegalArgumentException("IP version not supported");
+            }
+        } else {
+            if (identityStr.contains(":")) {
+                // KEY_ID
+                return new IkeKeyIdIdentification(identityStr.getBytes());
+            } else {
+                // FQDN
+                return new IkeFqdnIdentification(identityStr);
+            }
+        }
+    }
+
+    static Collection<RouteInfo> getRoutesFromTrafficSelectors(
+            List<IkeTrafficSelector> trafficSelectors) {
+        final HashSet<RouteInfo> routes = new HashSet<>();
+
+        for (final IkeTrafficSelector selector : trafficSelectors) {
+            for (final IpPrefix prefix :
+                    new IpRange(selector.startingAddress, selector.endingAddress).asIpPrefixes()) {
+                routes.add(new RouteInfo(prefix, null));
+            }
+        }
+
+        return routes;
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9140589..1ff8a94 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -121,7 +121,7 @@
  * The display manager service relies on a collection of {@link DisplayAdapter} components,
  * for discovering and configuring physical display devices attached to the system.
  * There are separate display adapters for each manner that devices are attached:
- * one display adapter for built-in local displays, one for simulated non-functional
+ * one display adapter for physical displays, one for simulated non-functional
  * displays when the system is headless, one for simulated overlay displays used for
  * development, one for wifi displays, etc.
  * </p><p>
@@ -231,7 +231,7 @@
     private int mGlobalDisplayState = Display.STATE_ON;
 
     // The overall display brightness.
-    // For now, this only applies to the built-in display but we may split it up eventually.
+    // For now, this only applies to the default display but we may split it up eventually.
     private float mGlobalDisplayBrightness;
 
     // Set to true when there are pending display changes that have yet to be applied
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2b225e5..4ebbdda 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -35,7 +35,6 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
-import android.view.Surface;
 import android.view.SurfaceControl;
 
 import com.android.internal.BrightnessSynchronizer;
@@ -53,7 +52,7 @@
 import java.util.Objects;
 
 /**
- * A display adapter for the local displays managed by Surface Flinger.
+ * A display adapter for the local displays managed by SurfaceFlinger.
  * <p>
  * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
  * </p>
@@ -129,10 +128,10 @@
             LocalDisplayDevice device = mDevices.get(physicalDisplayId);
             if (device == null) {
                 // Display was added.
-                final boolean isInternal = mDevices.size() == 0;
+                final boolean isDefaultDisplay = mDevices.size() == 0;
                 device = new LocalDisplayDevice(displayToken, physicalDisplayId, info,
                         configs, activeConfig, configSpecs, colorModes, activeColorMode,
-                        hdrCapabilities, isInternal);
+                        hdrCapabilities, isDefaultDisplay);
                 mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
             } else if (device.updateDisplayProperties(configs, activeConfig,
@@ -175,7 +174,7 @@
         private final LogicalLight mBacklight;
         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
         private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
-        private final boolean mIsInternal;
+        private final boolean mIsDefaultDisplay;
 
         private DisplayDeviceInfo mInfo;
         private boolean mHavePendingChanges;
@@ -207,15 +206,15 @@
                 SurfaceControl.DisplayInfo info, SurfaceControl.DisplayConfig[] configs,
                 int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs,
                 int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities,
-                boolean isInternal) {
+                boolean isDefaultDisplay) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
             mPhysicalDisplayId = physicalDisplayId;
-            mIsInternal = isInternal;
+            mIsDefaultDisplay = isDefaultDisplay;
             mDisplayInfo = info;
             updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes,
                     activeColorMode, hdrCapabilities);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
-            if (mIsInternal) {
+            if (mIsDefaultDisplay) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
                 mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
             } else {
@@ -523,11 +522,10 @@
                 }
 
                 final Resources res = getOverlayContext().getResources();
-                if (mIsInternal) {
-                    mInfo.name = res.getString(
-                            com.android.internal.R.string.display_manager_built_in_display_name);
-                    mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
-                            | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+
+                if (mIsDefaultDisplay) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY;
+
                     if (res.getBoolean(com.android.internal.R.bool.config_mainBuiltInDisplayIsRound)
                             || (Build.IS_EMULATOR
                             && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
@@ -539,28 +537,7 @@
                     }
                     mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
                             mInfo.width, mInfo.height);
-                    mInfo.type = Display.TYPE_BUILT_IN;
-                    mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
                 } else {
-                    mInfo.displayCutout = null;
-                    mInfo.type = Display.TYPE_HDMI;
-                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
-                    mInfo.name = getContext().getResources().getString(
-                            com.android.internal.R.string.display_manager_hdmi_display_name);
-                    mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
-
-                    // For demonstration purposes, allow rotation of the external display.
-                    // In the future we might allow the user to configure this directly.
-                    if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
-                        mInfo.rotation = Surface.ROTATION_270;
-                    }
-
-                    // For demonstration purposes, allow rotation of the external display
-                    // to follow the built-in display.
-                    if (SystemProperties.getBoolean("persist.demo.hdmirotates", false)) {
-                        mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
-                    }
-
                     if (!res.getBoolean(
                                 com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
                         mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
@@ -570,6 +547,20 @@
                         mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
                     }
                 }
+
+                if (mDisplayInfo.isInternal) {
+                    mInfo.type = Display.TYPE_INTERNAL;
+                    mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+                    mInfo.name = res.getString(
+                            com.android.internal.R.string.display_manager_built_in_display_name);
+                } else {
+                    mInfo.type = Display.TYPE_EXTERNAL;
+                    mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
+                    mInfo.name = getContext().getResources().getString(
+                            com.android.internal.R.string.display_manager_hdmi_display_name);
+                }
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 648e07a..ac3a653 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -395,7 +395,7 @@
     static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
 
     // TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event.
-    //            True by default.
+    //            False by default.
     static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug";
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 0944324..5541b11 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -45,7 +45,7 @@
     private static final String TAG = "HdmiCecLocalDevicePlayback";
 
     private static final boolean WAKE_ON_HOTPLUG =
-            SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
+            SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, false);
 
     private static final boolean SET_MENU_LANGUAGE =
             HdmiProperties.set_menu_language().orElse(false);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7c2ec78..d9e3025 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -105,32 +105,57 @@
  */
 public class HdmiControlService extends SystemService {
     private static final String TAG = "HdmiControlService";
-    private final Locale HONG_KONG = new Locale("zh", "HK");
-    private final Locale MACAU = new Locale("zh", "MO");
+    private static final Locale HONG_KONG = new Locale("zh", "HK");
+    private static final Locale MACAU = new Locale("zh", "MO");
 
-    private static final Map<String, String> mTerminologyToBibliographicMap;
-    static {
-        mTerminologyToBibliographicMap = new HashMap<>();
+    private static final Map<String, String> sTerminologyToBibliographicMap =
+            createsTerminologyToBibliographicMap();
+
+    private static Map<String, String> createsTerminologyToBibliographicMap() {
+        Map<String, String> temp = new HashMap<>();
         // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
-        mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian
-        mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian
-        mTerminologyToBibliographicMap.put("eus", "baq"); // Basque
-        mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese
-        mTerminologyToBibliographicMap.put("ces", "cze"); // Czech
-        mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch
-        mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian
-        mTerminologyToBibliographicMap.put("deu", "ger"); // German
-        mTerminologyToBibliographicMap.put("ell", "gre"); // Greek
-        mTerminologyToBibliographicMap.put("fra", "fre"); // French
-        mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic
-        mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian
-        mTerminologyToBibliographicMap.put("mri", "mao"); // Maori
-        mTerminologyToBibliographicMap.put("msa", "may"); // Malay
-        mTerminologyToBibliographicMap.put("fas", "per"); // Persian
-        mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian
-        mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak
-        mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan
-        mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh
+        temp.put("sqi", "alb"); // Albanian
+        temp.put("hye", "arm"); // Armenian
+        temp.put("eus", "baq"); // Basque
+        temp.put("mya", "bur"); // Burmese
+        temp.put("ces", "cze"); // Czech
+        temp.put("nld", "dut"); // Dutch
+        temp.put("kat", "geo"); // Georgian
+        temp.put("deu", "ger"); // German
+        temp.put("ell", "gre"); // Greek
+        temp.put("fra", "fre"); // French
+        temp.put("isl", "ice"); // Icelandic
+        temp.put("mkd", "mac"); // Macedonian
+        temp.put("mri", "mao"); // Maori
+        temp.put("msa", "may"); // Malay
+        temp.put("fas", "per"); // Persian
+        temp.put("ron", "rum"); // Romanian
+        temp.put("slk", "slo"); // Slovak
+        temp.put("bod", "tib"); // Tibetan
+        temp.put("cym", "wel"); // Welsh
+        return Collections.unmodifiableMap(temp);
+    }
+
+    @VisibleForTesting static String localeToMenuLanguage(Locale locale) {
+        if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
+            // Android always returns "zho" for all Chinese variants.
+            // Use "bibliographic" code defined in CEC639-2 for traditional
+            // Chinese used in Taiwan/Hong Kong/Macau.
+            return "chi";
+        } else {
+            String language = locale.getISO3Language();
+
+            // locale.getISO3Language() returns terminology code and need to
+            // send it as bibliographic code instead since the Bibliographic
+            // codes of ISO/FDIS 639-2 shall be used.
+            // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
+            // But, as it depends on the locale, is not handled here.
+            if (sTerminologyToBibliographicMap.containsKey(language)) {
+                language = sTerminologyToBibliographicMap.get(language);
+            }
+
+            return language;
+        }
     }
 
     static final String PERMISSION = "android.permission.HDMI_CEC";
@@ -208,8 +233,8 @@
                     }
                     break;
                 case Intent.ACTION_CONFIGURATION_CHANGED:
-                    String language = getMenuLanguage();
-                    if (!mLanguage.equals(language)) {
+                    String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault());
+                    if (!mMenuLanguage.equals(language)) {
                         onLanguageChanged(language);
                     }
                     break;
@@ -221,28 +246,6 @@
             }
         }
 
-        private String getMenuLanguage() {
-            Locale locale = Locale.getDefault();
-            if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
-                // Android always returns "zho" for all Chinese variants.
-                // Use "bibliographic" code defined in CEC639-2 for traditional
-                // Chinese used in Taiwan/Hong Kong/Macau.
-                return "chi";
-            } else {
-                String language = locale.getISO3Language();
-
-                // locale.getISO3Language() returns terminology code and need to
-                // send it as bibliographic code instead since the Bibliographic
-                // codes of ISO/FDIS 639-2 shall be used.
-                // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
-                // But, as it depends on the locale, is not handled here.
-                if (mTerminologyToBibliographicMap.containsKey(language)) {
-                    language = mTerminologyToBibliographicMap.get(language);
-                }
-
-                return language;
-            }
-        }
     }
 
     // A thread to handle synchronous IO of CEC and MHL control service.
@@ -339,7 +342,7 @@
     private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
 
     @ServiceThreadOnly
-    private String mLanguage = Locale.getDefault().getISO3Language();
+    private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
 
     @ServiceThreadOnly
     private boolean mStandbyMessageReceived = false;
@@ -759,7 +762,7 @@
     private void initializeCec(int initiatedBy) {
         mAddressAllocated = false;
         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
-        mCecController.setLanguage(mLanguage);
+        mCecController.setLanguage(mMenuLanguage);
         initializeLocalDevices(initiatedBy);
     }
 
@@ -2818,7 +2821,7 @@
     @ServiceThreadOnly
     private void onLanguageChanged(String language) {
         assertRunOnServiceThread();
-        mLanguage = language;
+        mMenuLanguage = language;
 
         if (isTvDeviceEnabled()) {
             tv().broadcastMenuLanguage(language);
@@ -2826,10 +2829,19 @@
         }
     }
 
+    /**
+     * Gets the CEC menu language.
+     *
+     * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu
+     * Language>}.
+     * See HDMI 1.4b section CEC 13.6.2
+     *
+     * @see {@link Locale#getISO3Language()}
+     */
     @ServiceThreadOnly
     String getLanguage() {
         assertRunOnServiceThread();
-        return mLanguage;
+        return mMenuLanguage;
     }
 
     private void disableDevices(PendingActionClearedCallback callback) {
@@ -2838,7 +2850,6 @@
                 device.disableDevice(mStandbyMessageReceived, callback);
             }
         }
-
         mMhlController.clearAllLocalDevices();
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
new file mode 100644
index 0000000..25ef9fa
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+ogunwale@google.com
+yukawa@google.com
+tarandeep@google.com
+lumark@google.com
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 68ced79..b9a30bb 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -23,6 +23,7 @@
 import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
 import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
 import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
 import static android.content.integrity.IntegrityUtils.getHexDigest;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 
@@ -95,7 +96,7 @@
      * This string will be used as the "installer" for formula evaluation when the app is being
      * installed via ADB.
      */
-    private static final String ADB_INSTALLER = "adb";
+    public static final String ADB_INSTALLER = "adb";
 
     private static final String TAG = "AppIntegrityManagerServiceImpl";
 
@@ -106,8 +107,6 @@
     private static final String ALLOWED_INSTALLER_DELIMITER = ",";
     private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
 
-    private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
-
     // Access to files inside mRulesDir is protected by mRulesLock;
     private final Context mContext;
     private final Handler mHandler;
@@ -282,15 +281,16 @@
             builder.setInstallerName(getPackageNameNormalized(installerPackageName));
             builder.setInstallerCertificates(installerCertificates);
             builder.setIsPreInstalled(isSystemApp(packageName));
+            builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo));
 
             AppInstallMetadata appInstallMetadata = builder.build();
-            Map<String, String> allowedInstallers = getAllowedInstallers(packageInfo);
 
             Slog.i(
                     TAG,
-                    "To be verified: " + appInstallMetadata + " installers " + allowedInstallers);
+                    "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(
+                            packageInfo));
             IntegrityCheckResult result =
-                    mEvaluationEngine.evaluate(appInstallMetadata, allowedInstallers);
+                    mEvaluationEngine.evaluate(appInstallMetadata);
             Slog.i(
                     TAG,
                     "Integrity check result: "
@@ -449,9 +449,9 @@
                         String packageName = getPackageNameNormalized(packageAndCert[0]);
                         String cert = packageAndCert[1];
                         packageCertMap.put(packageName, cert);
-                    } else if (packageAndCert.length == 1
-                            && packageAndCert[0].equals(ADB_INSTALLER)) {
-                        packageCertMap.put(ADB_INSTALLER, INSTALLER_CERT_NOT_APPLICABLE);
+                    } else if (packageAndCert.length == 1) {
+                        packageCertMap.put(getPackageNameNormalized(packageAndCert[0]),
+                                INSTALLER_CERTIFICATE_NOT_EVALUATED);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/integrity/TEST_MAPPING b/services/core/java/com/android/server/integrity/TEST_MAPPING
index b45b4ea..ca7f396 100644
--- a/services/core/java/com/android/server/integrity/TEST_MAPPING
+++ b/services/core/java/com/android/server/integrity/TEST_MAPPING
@@ -5,12 +5,6 @@
       "options": [
         {
           "include-filter": "com.android.server.integrity."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     }
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index 79e69e1..61da45d 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -17,9 +17,6 @@
 package com.android.server.integrity.engine;
 
 import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
 import android.content.integrity.Rule;
 import android.util.Slog;
 
@@ -28,10 +25,8 @@
 import com.android.server.integrity.model.IntegrityCheckResult;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
 
 /**
  * The engine used to evaluate rules against app installs.
@@ -69,16 +64,15 @@
      * @return result of the integrity check
      */
     public IntegrityCheckResult evaluate(
-            AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
+            AppInstallMetadata appInstallMetadata) {
         List<Rule> rules = loadRules(appInstallMetadata);
-        allowedInstallersRule(allowedInstallers).ifPresent(rules::add);
         return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
     }
 
     private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
         if (!mIntegrityFileManager.initialized()) {
-            Slog.w(TAG, "Integrity rule files are not available. Evaluating only manifest rules.");
-            return new ArrayList<>();
+            Slog.w(TAG, "Integrity rule files are not available.");
+            return Collections.emptyList();
         }
 
         try {
@@ -88,41 +82,4 @@
             return new ArrayList<>();
         }
     }
-
-    private static Optional<Rule> allowedInstallersRule(Map<String, String> allowedInstallers) {
-        if (allowedInstallers.isEmpty()) {
-            return Optional.empty();
-        }
-
-        List<IntegrityFormula> formulas = new ArrayList<>(allowedInstallers.size());
-        allowedInstallers.forEach(
-                (installer, cert) -> {
-                    formulas.add(allowedInstallerFormula(installer, cert));
-                });
-
-        // We need this special case since OR-formulas require at least two operands.
-        IntegrityFormula allInstallersFormula =
-                formulas.size() == 1
-                        ? formulas.get(0)
-                        : new CompoundFormula(CompoundFormula.OR, formulas);
-
-        return Optional.of(
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT, Arrays.asList(allInstallersFormula)),
-                        Rule.DENY));
-    }
-
-    private static IntegrityFormula allowedInstallerFormula(String installer, String cert) {
-        return new CompoundFormula(
-                CompoundFormula.AND,
-                Arrays.asList(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.INSTALLER_NAME,
-                                installer,
-                                /* isHashedValue= */ false),
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.INSTALLER_CERTIFICATE, cert, /* isHashedValue= */
-                                false)));
-    }
 }
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 997f21c..433ec43 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
@@ -335,7 +337,8 @@
      */
     public final void setRequest(ProviderRequest request) {
         // all calls into the provider must be moved onto the provider thread to prevent deadlock
-        mExecutor.execute(() -> onSetRequest(request));
+        mExecutor.execute(obtainRunnable(AbstractLocationProvider::onSetRequest, this, request)
+                .recycleOnUse());
     }
 
     /**
@@ -348,7 +351,13 @@
      */
     public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
         // all calls into the provider must be moved onto the provider thread to prevent deadlock
-        mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
+
+        // the integer boxing done here likely cancels out any gains from removing lambda
+        // allocation, but since this an infrequently used api with no real performance needs, we
+        // we use pooled lambdas anyways for consistency.
+        mExecutor.execute(
+                obtainRunnable(AbstractLocationProvider::onExtraCommand, this, uid, pid, command,
+                        extras).recycleOnUse());
     }
 
     /**
@@ -361,7 +370,9 @@
      */
     public final void requestSetAllowed(boolean allowed) {
         // all calls into the provider must be moved onto the provider thread to prevent deadlock
-        mExecutor.execute(() -> onRequestSetAllowed(allowed));
+        mExecutor.execute(
+                obtainRunnable(AbstractLocationProvider::onRequestSetAllowed, this, allowed)
+                        .recycleOnUse());
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java
index 370a3f7..5fe21bd 100644
--- a/services/core/java/com/android/server/location/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/SettingsHelper.java
@@ -27,6 +27,9 @@
 import static android.provider.Settings.Secure.LOCATION_MODE;
 import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
 
+import static com.android.server.LocationManagerService.D;
+import static com.android.server.LocationManagerService.TAG;
+
 import android.app.ActivityManager;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -37,6 +40,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
@@ -423,6 +427,10 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
+            if (D) {
+                Log.d(TAG, "location setting changed [u" + userId + "]: " + uri);
+            }
+
             for (UserSettingChangedListener listener : mListeners) {
                 listener.onSettingChanged(userId);
             }
diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java
index a33e2da..28f3f47 100644
--- a/services/core/java/com/android/server/location/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/UserInfoHelper.java
@@ -18,6 +18,9 @@
 
 import static android.os.UserManager.DISALLOW_SHARE_LOCATION;
 
+import static com.android.server.LocationManagerService.D;
+import static com.android.server.LocationManagerService.TAG;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -30,6 +33,7 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
@@ -122,13 +126,13 @@
                     case Intent.ACTION_USER_STARTED:
                         userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
                         if (userId != UserHandle.USER_NULL) {
-                            onUserChanged(userId, UserListener.USER_STARTED);
+                            onUserStarted(userId);
                         }
                         break;
                     case Intent.ACTION_USER_STOPPED:
                         userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
                         if (userId != UserHandle.USER_NULL) {
-                            onUserChanged(userId, UserListener.USER_STOPPED);
+                            onUserStopped(userId);
                         }
                         break;
                     case Intent.ACTION_MANAGED_PROFILE_ADDED:
@@ -161,6 +165,10 @@
             return;
         }
 
+        if (D) {
+            Log.d(TAG, "current user switched from u" + mCurrentUserId + " to u" + newUserId);
+        }
+
         int oldUserId = mCurrentUserId;
         mCurrentUserId = newUserId;
 
@@ -168,6 +176,22 @@
         onUserChanged(newUserId, UserListener.USER_SWITCHED);
     }
 
+    private void onUserStarted(@UserIdInt int userId) {
+        if (D) {
+            Log.d(TAG, "u" + userId + " started");
+        }
+
+        onUserChanged(userId, UserListener.USER_STARTED);
+    }
+
+    private void onUserStopped(@UserIdInt int userId) {
+        if (D) {
+            Log.d(TAG, "u" + userId + " stopped");
+        }
+
+        onUserChanged(userId, UserListener.USER_STOPPED);
+    }
+
     private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
         for (UserListener listener : mListeners) {
             listener.onUserChanged(userId, change);
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 6474303..8379614 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -29,7 +29,6 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
-import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 
@@ -49,6 +48,8 @@
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
     @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothRouteInfo mSelectedRoute = null;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
     BluetoothA2dp mA2dpProfile;
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     BluetoothHearingAid mHearingAidProfile;
@@ -62,8 +63,6 @@
     private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
 
-    private BluetoothDevice mActiveDevice = null;
-
     static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
             @NonNull BluetoothRoutesUpdatedListener listener) {
         Objects.requireNonNull(context);
@@ -108,27 +107,21 @@
     }
 
     /**
-     * Clears the active device for all known profiles.
+     * Transfers to a given bluetooth route.
+     * The dedicated BT device with the route would be activated.
+     *
+     * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of
+     *               BT routes.
      */
-    public void clearActiveDevices() {
-        BluetoothA2dp a2dpProfile = mA2dpProfile;
-        BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
-        if (a2dpProfile != null) {
-            a2dpProfile.setActiveDevice(null);
+    public void transferTo(@Nullable String routeId) {
+        if (routeId == null) {
+            clearActiveDevices();
+            return;
         }
-        if (hearingAidProfile != null) {
-            hearingAidProfile.setActiveDevice(null);
-        }
-    }
 
-    /**
-     * Sets the active device.
-     * @param deviceId the id of the Bluetooth device
-     */
-    public void setActiveDevice(@NonNull String deviceId) {
-        BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(deviceId);
+        BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(routeId);
         if (btRouteInfo == null) {
-            Slog.w(TAG, "setActiveDevice: unknown device id=" + deviceId);
+            Slog.w(TAG, "setActiveDevice: unknown route id=" + routeId);
             return;
         }
         BluetoothA2dp a2dpProfile = mA2dpProfile;
@@ -144,6 +137,20 @@
         }
     }
 
+    /**
+     * Clears the active device for all known profiles.
+     */
+    private void clearActiveDevices() {
+        BluetoothA2dp a2dpProfile = mA2dpProfile;
+        BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
+        if (a2dpProfile != null) {
+            a2dpProfile.setActiveDevice(null);
+        }
+        if (hearingAidProfile != null) {
+            hearingAidProfile.setActiveDevice(null);
+        }
+    }
+
     private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
         mEventReceiverMap.put(action, eventReceiver);
         mIntentFilter.addAction(action);
@@ -159,7 +166,8 @@
         }
     }
 
-    @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+    @NonNull
+    List<MediaRoute2Info> getAllBluetoothRoutes() {
         ArrayList<MediaRoute2Info> routes = new ArrayList<>();
         for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
             routes.add(btRoute.route);
@@ -167,17 +175,14 @@
         return routes;
     }
 
-    @Nullable String getActiveDeviceAddress() {
-        BluetoothDevice device = mActiveDevice;
-        if (device == null) {
-            return null;
-        }
-        return device.getAddress();
+    @Nullable
+    String getSelectedRouteId() {
+        return mSelectedRoute == null ? null : mSelectedRoute.route.getId();
     }
 
     private void notifyBluetoothRoutesUpdated() {
         if (mListener != null) {
-            mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+            mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
         }
     }
 
@@ -198,15 +203,10 @@
         return newBtRoute;
     }
 
-    private void setRouteConnectionStateForDevice(BluetoothDevice device,
+    private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
             @MediaRoute2Info.ConnectionState int state) {
-        if (device == null) {
-            Slog.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
-            return;
-        }
-        BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
         if (btRoute == null) {
-            Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+            Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
             return;
         }
         if (btRoute.route.getConnectionState() == state) {
@@ -260,8 +260,8 @@
                     mBluetoothRoutes.put(device.getAddress(), btRoute);
                 }
                 if (activeDevices.contains(device)) {
-                    mActiveDevice = device;
-                    setRouteConnectionStateForDevice(device,
+                    mSelectedRoute = btRoute;
+                    setRouteConnectionState(mSelectedRoute,
                             MediaRoute2Info.CONNECTION_STATE_CONNECTED);
                 }
 
@@ -344,20 +344,18 @@
         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
             switch (intent.getAction()) {
                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
-                    String prevActiveDeviceAddress =
-                            (mActiveDevice == null) ? null : mActiveDevice.getAddress();
-                    String curActiveDeviceAddress =
-                            (device == null) ? null : device.getAddress();
-                    if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
-                        if (mActiveDevice != null) {
-                            setRouteConnectionStateForDevice(mActiveDevice,
+                    if (mSelectedRoute == null
+                            || !mSelectedRoute.btDevice.equals(device)) {
+                        if (mSelectedRoute != null) {
+                            setRouteConnectionState(mSelectedRoute,
                                     MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
                         }
-                        if (device != null) {
-                            setRouteConnectionStateForDevice(device,
+                        mSelectedRoute = (device == null) ? null
+                                : mBluetoothRoutes.get(device.getAddress());
+                        if (mSelectedRoute != null) {
+                            setRouteConnectionState(mSelectedRoute,
                                     MediaRoute2Info.CONNECTION_STATE_CONNECTED);
                         }
-                        mActiveDevice = device;
                         notifyBluetoothRoutesUpdated();
                     }
                     break;
@@ -386,10 +384,8 @@
                     btRoute.connectedProfiles.delete(profile);
                     if (btRoute.connectedProfiles.size() == 0) {
                         mBluetoothRoutes.remove(device.getAddress());
-                        if (mActiveDevice != null
-                                && TextUtils.equals(mActiveDevice.getAddress(),
-                                device.getAddress())) {
-                            mActiveDevice = null;
+                        if (mSelectedRoute != null && mSelectedRoute.btDevice.equals(device)) {
+                            mSelectedRoute = null;
                         }
                         notifyBluetoothRoutesUpdated();
                     }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 18383c4..7f7a663 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -145,9 +145,9 @@
     @Override
     public void transferToRoute(String sessionId, String routeId) {
         if (TextUtils.equals(routeId, mDefaultRoute.getId())) {
-            mBtRouteProvider.clearActiveDevices();
+            mBtRouteProvider.transferTo(null);
         } else {
-            mBtRouteProvider.setActiveDevice(routeId);
+            mBtRouteProvider.transferTo(routeId);
         }
     }
 
@@ -196,7 +196,7 @@
     }
 
     private void initializeSessionInfo() {
-        mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+        mBluetoothRoutes = mBtRouteProvider.getAllBluetoothRoutes();
 
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
         builder.addRoute(mDefaultRoute);
@@ -251,18 +251,18 @@
         RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
                 SYSTEM_SESSION_ID, "" /* clientPackageName */)
                 .setSystemSession(true);
-        String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
+        String activeBtDeviceAddress = mBtRouteProvider.getSelectedRouteId();
         mSelectedRouteId = TextUtils.isEmpty(activeBtDeviceAddress) ? mDefaultRoute.getId()
                 : activeBtDeviceAddress;
         builder.addSelectedRoute(mSelectedRouteId);
 
         if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
-            builder.addTransferrableRoute(mDefaultRoute.getId());
+            builder.addTransferableRoute(mDefaultRoute.getId());
         }
 
         for (MediaRoute2Info route : mBluetoothRoutes) {
             if (!TextUtils.equals(mSelectedRouteId, route.getId())) {
-                builder.addTransferrableRoute(route.getId());
+                builder.addTransferableRoute(route.getId());
             }
         }
 
@@ -311,7 +311,7 @@
                     AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
 
             if (newVolume != oldVolume) {
-                String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
+                String activeBtDeviceAddress = mBtRouteProvider.getSelectedRouteId();
                 if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
                     for (int i = mBluetoothRoutes.size() - 1; i >= 0; i--) {
                         MediaRoute2Info route = mBluetoothRoutes.get(i);
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
index 2831d37..7ba993b 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
@@ -19,6 +19,7 @@
 import android.app.NotificationHistory.HistoricalNotification;
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -141,6 +142,16 @@
                     loadIcon(parser, notification, pkg);
                     parser.end(iconToken);
                     break;
+                case (int) Notification.CONVERSATION_ID_INDEX:
+                    String conversationId =
+                            stringPool.get(parser.readInt(Notification.CONVERSATION_ID_INDEX) - 1);
+                    notification.setConversationId(conversationId);
+                    break;
+                case (int) Notification.CONVERSATION_ID:
+                    conversationId = parser.readString(Notification.CONVERSATION_ID);
+                    notification.setConversationId(conversationId);
+                    stringPool.add(conversationId);
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     return notification.build();
             }
@@ -271,6 +282,17 @@
                     + ") not found in string cache");
             proto.write(Notification.CHANNEL_ID, notification.getChannelId());
         }
+        if (!TextUtils.isEmpty(notification.getConversationId())) {
+            final int conversationIdIndex = Arrays.binarySearch(
+                    stringPool, notification.getConversationId());
+            if (conversationIdIndex >= 0) {
+                proto.write(Notification.CONVERSATION_ID_INDEX, conversationIdIndex + 1);
+            } else {
+                Slog.w(TAG, "notification conversation id (" + notification.getConversationId()
+                        + ") not found in string cache");
+                proto.write(Notification.CONVERSATION_ID, notification.getConversationId());
+            }
+        }
         proto.write(Notification.UID, notification.getUid());
         proto.write(Notification.USER_ID, notification.getUserId());
         proto.write(Notification.POSTED_TIME_MS, notification.getPostedTimeMs());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 916b63b..2e4a977 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -932,7 +932,7 @@
                 StatusBarNotification sbn = r.getSbn();
                 cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
                         sbn.getId(), Notification.FLAG_AUTO_CANCEL,
-                        FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
+                        FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
                         REASON_CLICK, nv.rank, nv.count, null);
                 nv.recycle();
                 reportUserInteraction(r);
@@ -1055,7 +1055,7 @@
                         if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
                         reportSeen(r);
                     }
-                    r.setVisibility(true, nv.rank, nv.count);
+                    r.setVisibility(true, nv.rank, nv.count, mNotificationRecordLogger);
                     mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true);
                     boolean isHun = (nv.location
                             == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
@@ -1074,7 +1074,7 @@
                 for (NotificationVisibility nv : noLongerVisibleKeys) {
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
-                    r.setVisibility(false, nv.rank, nv.count);
+                    r.setVisibility(false, nv.rank, nv.count, mNotificationRecordLogger);
                     mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false);
                     nv.recycle();
                 }
@@ -3979,6 +3979,29 @@
         }
 
         /**
+         * Allows the notification assistant to un-snooze a single notification.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void unsnoozeNotificationFromSystemListener(INotificationListener token,
+                String key) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationLock) {
+                    final ManagedServiceInfo info =
+                            mListeners.checkServiceTokenLocked(token);
+                    if (!info.isSystem) {
+                        throw new SecurityException("Not allowed to unsnooze before deadline");
+                    }
+                    unsnoozeNotificationInt(key, info);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
          * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
          *
          * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
@@ -6220,6 +6243,7 @@
         private final int mRank;
         private final int mCount;
         private final ManagedServiceInfo mListener;
+        private final long mWhen;
 
         CancelNotificationRunnable(final int callingUid, final int callingPid,
                 final String pkg, final String tag, final int id,
@@ -6239,6 +6263,7 @@
             this.mRank = rank;
             this.mCount = count;
             this.mListener = listener;
+            this.mWhen = System.currentTimeMillis();
         }
 
         @Override
@@ -6250,13 +6275,28 @@
             }
 
             synchronized (mNotificationLock) {
-                // If the notification is currently enqueued, repost this runnable so it has a
-                // chance to notify listeners
-                if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
-                        != null) {
+                // Check to see if there is a notification in the enqueued list that hasn't had a
+                // chance to post yet.
+                List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria(
+                        mPkg, mTag, mId, mUserId);
+                boolean repost = false;
+                if (enqueued.size() > 0) {
+                    // Found something, let's see what it was
+                    repost = true;
+                    // If all enqueues happened before this cancel then wait for them to happen,
+                    // otherwise we should let this cancel through so the next enqueue happens
+                    for (NotificationRecord r : enqueued) {
+                        if (r.mUpdateTimeMs > mWhen) {
+                            // At least one enqueue was posted after the cancel, so we're invalid
+                            return;
+                        }
+                    }
+                }
+                if (repost) {
                     mHandler.post(this);
                     return;
                 }
+
                 // Look for the notification in the posted list, since we already checked enqueued.
                 NotificationRecord r =
                         findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
@@ -6275,6 +6315,10 @@
                     if ((r.getNotification().flags & mMustNotHaveFlags) != 0) {
                         return;
                     }
+                    if (r.getUpdateTimeMs() > mWhen) {
+                        // In this case, a post must have slipped by when this runnable reposted
+                        return;
+                    }
 
                     // Bubbled children get to stick around if the summary was manually cancelled
                     // (user removed) from systemui.
@@ -6468,7 +6512,7 @@
                         mUsageStats.registerPostedByApp(r);
                         r.setInterruptive(isVisuallyInterruptive(null, r));
                     } else {
-                        old = mNotificationList.get(index);
+                        old = mNotificationList.get(index);  // Potentially *changes* old
                         mNotificationList.set(index, r);
                         mUsageStats.registerUpdatedByApp(r, old);
                         // Make sure we don't lose the foreground service state.
@@ -6537,7 +6581,7 @@
                     maybeRecordInterruptionLocked(r);
 
                     // Log event to statsd
-                    mNotificationRecordLogger.logNotificationReported(r, old, position,
+                    mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,
                             buzzBeepBlinkLoggingCode);
                 } finally {
                     int N = mEnqueuedNotifications.size();
@@ -7986,7 +8030,8 @@
 
                     FlagChecker flagChecker = (int flags) -> {
                         int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
-                        if (REASON_LISTENER_CANCEL_ALL == reason) {
+                        if (REASON_LISTENER_CANCEL_ALL == reason
+                                || REASON_CANCEL_ALL == reason) {
                             flagsToCheck |= FLAG_BUBBLE;
                         }
                         if ((flags & flagsToCheck) != 0) {
@@ -8196,6 +8241,29 @@
         return null;
     }
 
+    /**
+     * There may be multiple records that match your criteria. For instance if there have been
+     * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
+     * method will find all of them in the given list
+     * @return
+     */
+    @GuardedBy("mNotificationLock")
+    private List<NotificationRecord> findEnqueuedNotificationsForCriteria(
+            String pkg, String tag, int id, int userId) {
+        final ArrayList<NotificationRecord> records = new ArrayList<>();
+        final int n = mEnqueuedNotifications.size();
+        for (int i = 0; i < n; i++) {
+            NotificationRecord r = mEnqueuedNotifications.get(i);
+            if (notificationMatchesUserId(r, userId)
+                    && r.getSbn().getId() == id
+                    && TextUtils.equals(r.getSbn().getTag(), tag)
+                    && r.getSbn().getPackageName().equals(pkg)) {
+                records.add(r);
+            }
+        }
+        return records;
+    }
+
     @GuardedBy("mNotificationLock")
     int indexOfNotificationLocked(String key) {
         final int N = mNotificationList.size();
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 4785da9..f92e1fc 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -906,10 +906,15 @@
         return (int) (now - mInterruptionTimeMs);
     }
 
+    public long getUpdateTimeMs() {
+        return mUpdateTimeMs;
+    }
+
     /**
      * Set the visibility of the notification.
      */
-    public void setVisibility(boolean visible, int rank, int count) {
+    public void setVisibility(boolean visible, int rank, int count,
+            NotificationRecordLogger notificationRecordLogger) {
         final long now = System.currentTimeMillis();
         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
         stats.onVisibilityChanged(visible);
@@ -927,6 +932,7 @@
                 getFreshnessMs(now),
                 0, // exposure time
                 rank);
+        notificationRecordLogger.logNotificationVisibility(this, visible);
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 2f78542..eaca066f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -41,13 +41,14 @@
 public interface NotificationRecordLogger {
 
     /**
-     * Logs a NotificationReported atom reflecting the posting or update of a notification.
+     * May log a NotificationReported atom reflecting the posting or update of a notification.
      * @param r The new NotificationRecord. If null, no action is taken.
      * @param old The previous NotificationRecord.  Null if there was no previous record.
      * @param position The position at which this notification is ranked.
      * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
      */
-    void logNotificationReported(@Nullable NotificationRecord r, @Nullable NotificationRecord old,
+    void maybeLogNotificationPosted(@Nullable NotificationRecord r,
+            @Nullable NotificationRecord old,
             int position, int buzzBeepBlink);
 
     /**
@@ -62,6 +63,14 @@
             @NotificationStats.DismissalSurface int dismissalSurface);
 
     /**
+     * Logs a notification visibility change event using UiEventReported (event ids from the
+     * NotificationEvents enum).
+     * @param r The NotificationRecord. If null, no action is taken.
+     * @param visible True if the notification became visible.
+     */
+    void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible);
+
+    /**
      * The UiEvent enums that this class can log.
      */
     enum NotificationReportedEvent implements UiEventLogger.UiEventEnum {
@@ -145,6 +154,7 @@
         @Override public int getId() {
             return mId;
         }
+
         public static NotificationCancelledEvent fromCancelReason(
                 @NotificationListenerService.NotificationCancelReason int reason,
                 @NotificationStats.DismissalSurface int surface) {
@@ -191,6 +201,24 @@
         }
     }
 
+    enum NotificationEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Notification became visible.")
+        NOTIFICATION_OPEN(197),
+        @UiEvent(doc = "Notification stopped being visible.")
+        NOTIFICATION_CLOSE(198);
+
+        private final int mId;
+        NotificationEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+
+        public static NotificationEvent fromVisibility(boolean visible) {
+            return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE;
+        }
+    }
     /**
      * A helper for extracting logging information from one or two NotificationRecords.
      */
@@ -209,7 +237,7 @@
         /**
          * @return True if old is null, alerted, or important logged fields have changed.
          */
-        boolean shouldLog(int buzzBeepBlink) {
+        boolean shouldLogReported(int buzzBeepBlink) {
             if (r == null) {
                 return false;
             }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index bb23d1e..9fcac25 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -26,13 +26,13 @@
  */
 public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
 
-    UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+    private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
 
     @Override
-    public void logNotificationReported(NotificationRecord r, NotificationRecord old,
+    public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
             int position, int buzzBeepBlink) {
         NotificationRecordPair p = new NotificationRecordPair(r, old);
-        if (!p.shouldLog(buzzBeepBlink)) {
+        if (!p.shouldLogReported(buzzBeepBlink)) {
             return;
         }
         FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
@@ -66,8 +66,19 @@
 
     @Override
     public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
-        mUiEventLogger.logWithInstanceId(
-                NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface),
-                r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId());
+        log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
+    }
+
+    @Override
+    public void logNotificationVisibility(NotificationRecord r, boolean visible) {
+        log(NotificationEvent.fromVisibility(visible), r);
+    }
+
+    void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+        if (r == null) {
+            return;
+        }
+        mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(),
+                r.getSbn().getInstanceId());
     }
 }
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 661297a..bae1dd3 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -16,6 +16,7 @@
 package com.android.server.notification;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -42,7 +43,9 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.sql.Array;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -77,26 +80,26 @@
     private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE";
     private static final int REQUEST_CODE_REPOST = 1;
     private static final String REPOST_SCHEME = "repost";
-    private static final String EXTRA_KEY = "key";
+    static final String EXTRA_KEY = "key";
     private static final String EXTRA_USER_ID = "userId";
 
     private final Context mContext;
     private AlarmManager mAm;
     private final ManagedServices.UserProfiles mUserProfiles;
 
-    // User id : package name : notification key : record.
-    private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>>
+    // User id | package name : notification key : record.
+    private ArrayMap<String, ArrayMap<String, NotificationRecord>>
             mSnoozedNotifications = new ArrayMap<>();
-    // User id : package name : notification key : time-milliseconds .
+    // User id | package name : notification key : time-milliseconds .
     // This member stores persisted snoozed notification trigger times. it persists through reboots
     // It should have the notifications that haven't expired or re-posted yet
-    private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>>
+    private final ArrayMap<String, ArrayMap<String, Long>>
             mPersistedSnoozedNotifications = new ArrayMap<>();
-    // User id : package name : notification key : creation ID .
+    // User id | package name : notification key : creation ID .
     // This member stores persisted snoozed notification trigger context for the assistant
     // it persists through reboots.
     // It should have the notifications that haven't expired or re-posted yet
-    private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>>
+    private final ArrayMap<String, ArrayMap<String, String>>
             mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
     // notification key : package.
     private ArrayMap<String, String> mPackages = new ArrayMap<>();
@@ -115,6 +118,10 @@
         mUserProfiles = userProfiles;
     }
 
+    private String getPkgKey(@UserIdInt int userId, String pkg) {
+        return userId + "|" + pkg;
+    }
+
     void cleanupPersistedContext(String key){
         int userId = mUsers.get(key);
         String pkg = mPackages.get(key);
@@ -144,15 +151,13 @@
     }
 
     protected boolean isSnoozed(int userId, String pkg, String key) {
-        return mSnoozedNotifications.containsKey(userId)
-                && mSnoozedNotifications.get(userId).containsKey(pkg)
-                && mSnoozedNotifications.get(userId).get(pkg).containsKey(key);
+        return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))
+                && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key);
     }
 
     protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) {
-        if (mSnoozedNotifications.containsKey(userId)
-                && mSnoozedNotifications.get(userId).containsKey(pkg)) {
-            return mSnoozedNotifications.get(userId).get(pkg).values();
+        if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) {
+            return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values();
         }
         return Collections.EMPTY_LIST;
     }
@@ -161,14 +166,14 @@
     ArrayList<NotificationRecord> getNotifications(String pkg,
             String groupKey, Integer userId) {
         ArrayList<NotificationRecord> records =  new ArrayList<>();
-        if (mSnoozedNotifications.containsKey(userId)
-                && mSnoozedNotifications.get(userId).containsKey(pkg)) {
-            ArrayMap<String, NotificationRecord> packages =
-                    mSnoozedNotifications.get(userId).get(pkg);
-            for (int i = 0; i < packages.size(); i++) {
-                String currentGroupKey = packages.valueAt(i).getSbn().getGroup();
+        ArrayMap<String, NotificationRecord> allRecords =
+                mSnoozedNotifications.get(getPkgKey(userId, pkg));
+        if (allRecords != null) {
+            for (int i = 0; i < allRecords.size(); i++) {
+                NotificationRecord r = allRecords.valueAt(i);
+                String currentGroupKey = r.getSbn().getGroup();
                 if (currentGroupKey.equals(groupKey)) {
-                    records.add(packages.valueAt(i));
+                    records.add(r);
                 }
             }
         }
@@ -176,47 +181,30 @@
     }
 
     protected NotificationRecord getNotification(String key) {
-        List<NotificationRecord> snoozedForUser = new ArrayList<>();
-        IntArray userIds = mUserProfiles.getCurrentProfileIds();
-        if (userIds != null) {
-            final int userIdsSize = userIds.size();
-            for (int i = 0; i < userIdsSize; i++) {
-                final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
-                        mSnoozedNotifications.get(userIds.get(i));
-                if (snoozedPkgs != null) {
-                    final int snoozedPkgsSize = snoozedPkgs.size();
-                    for (int j = 0; j < snoozedPkgsSize; j++) {
-                        final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
-                        if (records != null) {
-                            return records.get(key);
-                        }
-                    }
-                }
-            }
+        if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) {
+            Slog.w(TAG, "Snoozed data sets no longer agree for " + key);
+            return null;
         }
-        return null;
+        int userId = mUsers.get(key);
+        String pkg = mPackages.get(key);
+        ArrayMap<String, NotificationRecord> snoozed =
+                mSnoozedNotifications.get(getPkgKey(userId, pkg));
+        if (snoozed == null) {
+            return null;
+        }
+        return snoozed.get(key);
     }
 
     protected @NonNull List<NotificationRecord> getSnoozed() {
-        List<NotificationRecord> snoozedForUser = new ArrayList<>();
-        IntArray userIds = mUserProfiles.getCurrentProfileIds();
-        if (userIds != null) {
-            final int N = userIds.size();
-            for (int i = 0; i < N; i++) {
-                final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
-                        mSnoozedNotifications.get(userIds.get(i));
-                if (snoozedPkgs != null) {
-                    final int M = snoozedPkgs.size();
-                    for (int j = 0; j < M; j++) {
-                        final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
-                        if (records != null) {
-                            snoozedForUser.addAll(records.values());
-                        }
-                    }
-                }
-            }
+        // caller filters records based on the current user profiles and listener access, so just
+        // return everything
+        List<NotificationRecord> snoozed= new ArrayList<>();
+        for (String userPkgKey : mSnoozedNotifications.keySet()) {
+            ArrayMap<String, NotificationRecord> snoozedRecords =
+                    mSnoozedNotifications.get(userPkgKey);
+            snoozed.addAll(snoozedRecords.values());
         }
-        return snoozedForUser;
+        return snoozed;
     }
 
     /**
@@ -261,120 +249,88 @@
     }
 
     private <T> void storeRecord(String pkg, String key, Integer userId,
-            ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) {
+            ArrayMap<String, ArrayMap<String, T>> targets, T object) {
 
-        ArrayMap<String, ArrayMap<String, T>> records =
-                targets.get(userId);
-        if (records == null) {
-            records = new ArrayMap<>();
+        ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
+        if (keyToValue == null) {
+            keyToValue = new ArrayMap<>();
         }
-        ArrayMap<String, T> pkgRecords = records.get(pkg);
-        if (pkgRecords == null) {
-            pkgRecords = new ArrayMap<>();
-        }
-        pkgRecords.put(key, object);
-        records.put(pkg, pkgRecords);
-        targets.put(userId, records);
+        keyToValue.put(key, object);
+        targets.put(getPkgKey(userId, pkg), keyToValue);
 
     }
 
     private <T> T removeRecord(String pkg, String key, Integer userId,
-            ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) {
+            ArrayMap<String, ArrayMap<String, T>> targets) {
         T object = null;
-
-        ArrayMap<String, ArrayMap<String, T>> records =
-                targets.get(userId);
-        if (records == null) {
+        ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
+        if (keyToValue == null) {
             return null;
         }
-        ArrayMap<String, T> pkgRecords = records.get(pkg);
-        if (pkgRecords == null) {
-            return null;
-        }
-        object = pkgRecords.remove(key);
-        if (pkgRecords.size() == 0) {
-            records.remove(pkg);
-        }
-        if (records.size() == 0) {
-            targets.remove(userId);
+        object = keyToValue.remove(key);
+        if (keyToValue.size() == 0) {
+            targets.remove(getPkgKey(userId, pkg));
         }
         return object;
     }
 
     protected boolean cancel(int userId, String pkg, String tag, int id) {
-        if (mSnoozedNotifications.containsKey(userId)) {
-            ArrayMap<String, NotificationRecord> recordsForPkg =
-                    mSnoozedNotifications.get(userId).get(pkg);
-            if (recordsForPkg != null) {
-                final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
-                for (Map.Entry<String, NotificationRecord> record : records) {
-                    final StatusBarNotification sbn = record.getValue().getSbn();
-                    if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
-                        record.getValue().isCanceled = true;
-                        return true;
-                    }
+        ArrayMap<String, NotificationRecord> recordsForPkg =
+                mSnoozedNotifications.get(getPkgKey(userId, pkg));
+        if (recordsForPkg != null) {
+            final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
+            for (Map.Entry<String, NotificationRecord> record : records) {
+                final StatusBarNotification sbn = record.getValue().getSbn();
+                if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
+                    record.getValue().isCanceled = true;
+                    return true;
                 }
             }
         }
         return false;
     }
 
-    protected boolean cancel(int userId, boolean includeCurrentProfiles) {
-        int[] userIds = {userId};
-        if (includeCurrentProfiles) {
-            userIds = mUserProfiles.getCurrentProfileIds().toArray();
+    protected void cancel(int userId, boolean includeCurrentProfiles) {
+        if (mSnoozedNotifications.size() == 0) {
+            return;
         }
-        final int N = userIds.length;
-        for (int i = 0; i < N; i++) {
-            final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
-                    mSnoozedNotifications.get(userIds[i]);
-            if (snoozedPkgs != null) {
-                final int M = snoozedPkgs.size();
-                for (int j = 0; j < M; j++) {
-                    final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
-                    if (records != null) {
-                        int P = records.size();
-                        for (int k = 0; k < P; k++) {
-                            records.valueAt(k).isCanceled = true;
-                        }
-                    }
+        IntArray userIds = new IntArray();
+        userIds.add(userId);
+        if (includeCurrentProfiles) {
+            userIds = mUserProfiles.getCurrentProfileIds();
+        }
+        for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) {
+            for (NotificationRecord r : snoozedRecords.values()) {
+                if (userIds.binarySearch(r.getUserId()) >= 0) {
+                    r.isCanceled = true;
                 }
-                return true;
             }
         }
-        return false;
     }
 
     protected boolean cancel(int userId, String pkg) {
-        if (mSnoozedNotifications.containsKey(userId)) {
-            if (mSnoozedNotifications.get(userId).containsKey(pkg)) {
-                ArrayMap<String, NotificationRecord> records =
-                        mSnoozedNotifications.get(userId).get(pkg);
-                int N = records.size();
-                for (int i = 0; i < N; i++) {
-                    records.valueAt(i).isCanceled = true;
-                }
-                return true;
-            }
+        ArrayMap<String, NotificationRecord> records =
+                mSnoozedNotifications.get(getPkgKey(userId, pkg));
+        if (records == null) {
+            return false;
         }
-        return false;
+        int N = records.size();
+        for (int i = 0; i < N; i++) {
+            records.valueAt(i).isCanceled = true;
+        }
+        return true;
     }
 
     /**
      * Updates the notification record so the most up to date information is shown on re-post.
      */
     protected void update(int userId, NotificationRecord record) {
-        ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
-                mSnoozedNotifications.get(userId);
+        ArrayMap<String, NotificationRecord> records =
+                mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName()));
         if (records == null) {
             return;
         }
-        ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName());
-        if (pkgRecords == null) {
-            return;
-        }
-        NotificationRecord existing = pkgRecords.get(record.getKey());
-        pkgRecords.put(record.getKey(), record);
+        records.put(record.getKey(), record);
     }
 
     protected void repost(String key) {
@@ -386,20 +342,18 @@
 
     protected void repost(String key, int userId) {
         final String pkg = mPackages.remove(key);
-        ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
-                mSnoozedNotifications.get(userId);
+        ArrayMap<String, NotificationRecord> records =
+                mSnoozedNotifications.get(getPkgKey(userId, pkg));
         if (records == null) {
             return;
         }
-        ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
-        if (pkgRecords == null) {
-            return;
-        }
-        final NotificationRecord record = pkgRecords.remove(key);
+        final NotificationRecord record = records.remove(key);
         mPackages.remove(key);
         mUsers.remove(key);
 
         if (record != null && !record.isCanceled) {
+            final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId);
+            mAm.cancel(pi);
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
                     .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -408,55 +362,46 @@
     }
 
     protected void repostGroupSummary(String pkg, int userId, String groupKey) {
-        if (mSnoozedNotifications.containsKey(userId)) {
-            ArrayMap<String, ArrayMap<String, NotificationRecord>> keysByPackage
-                    = mSnoozedNotifications.get(userId);
+        ArrayMap<String, NotificationRecord> recordsByKey
+                = mSnoozedNotifications.get(getPkgKey(userId, pkg));
+        if (recordsByKey == null) {
+            return;
+        }
 
-            if (keysByPackage != null && keysByPackage.containsKey(pkg)) {
-                ArrayMap<String, NotificationRecord> recordsByKey = keysByPackage.get(pkg);
+        String groupSummaryKey = null;
+        int N = recordsByKey.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
+            if (potentialGroupSummary.getSbn().isGroup()
+                    && potentialGroupSummary.getNotification().isGroupSummary()
+                    && groupKey.equals(potentialGroupSummary.getGroupKey())) {
+                groupSummaryKey = potentialGroupSummary.getKey();
+                break;
+            }
+        }
 
-                if (recordsByKey != null) {
-                    String groupSummaryKey = null;
-                    int N = recordsByKey.size();
-                    for (int i = 0; i < N; i++) {
-                        final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
-                        if (potentialGroupSummary.getSbn().isGroup()
-                                && potentialGroupSummary.getNotification().isGroupSummary()
-                                && groupKey.equals(potentialGroupSummary.getGroupKey())) {
-                            groupSummaryKey = potentialGroupSummary.getKey();
-                            break;
-                        }
-                    }
+        if (groupSummaryKey != null) {
+            NotificationRecord record = recordsByKey.remove(groupSummaryKey);
+            mPackages.remove(groupSummaryKey);
+            mUsers.remove(groupSummaryKey);
 
-                    if (groupSummaryKey != null) {
-                        NotificationRecord record = recordsByKey.remove(groupSummaryKey);
-                        mPackages.remove(groupSummaryKey);
-                        mUsers.remove(groupSummaryKey);
-
-                        if (record != null && !record.isCanceled) {
-                            MetricsLogger.action(record.getLogMaker()
-                                    .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
-                                    .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
-                            mCallback.repost(userId, record);
-                        }
-                    }
-                }
+            if (record != null && !record.isCanceled) {
+                MetricsLogger.action(record.getLogMaker()
+                        .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
+                        .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
+                mCallback.repost(userId, record);
             }
         }
     }
 
     protected void clearData(int userId, String pkg) {
-        ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
-                mSnoozedNotifications.get(userId);
+        ArrayMap<String, NotificationRecord> records =
+                mSnoozedNotifications.get(getPkgKey(userId, pkg));
         if (records == null) {
             return;
         }
-        ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
-        if (pkgRecords == null) {
-            return;
-        }
-        for (int i = pkgRecords.size() - 1; i >= 0; i--) {
-            final NotificationRecord r = pkgRecords.removeAt(i);
+        for (int i = records.size() - 1; i >= 0; i--) {
+            final NotificationRecord r = records.removeAt(i);
             if (r != null) {
                 mPackages.remove(r.getKey());
                 mUsers.remove(r.getKey());
@@ -495,22 +440,36 @@
 
     public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
         pw.println("\n  Snoozed notifications:");
-        for (int userId : mSnoozedNotifications.keySet()) {
+        for (String userPkgKey : mSnoozedNotifications.keySet()) {
             pw.print(INDENT);
-            pw.println("user: " + userId);
-            ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
-                    mSnoozedNotifications.get(userId);
-            for (String pkg : snoozedPkgs.keySet()) {
+            pw.println("key: " + userPkgKey);
+            ArrayMap<String, NotificationRecord> snoozedRecords =
+                    mSnoozedNotifications.get(userPkgKey);
+            Set<String> snoozedKeys = snoozedRecords.keySet();
+            for (String key : snoozedKeys) {
                 pw.print(INDENT);
                 pw.print(INDENT);
-                pw.println("package: " + pkg);
-                Set<String> snoozedKeys = snoozedPkgs.get(pkg).keySet();
-                for (String key : snoozedKeys) {
-                    pw.print(INDENT);
-                    pw.print(INDENT);
-                    pw.print(INDENT);
-                    pw.println(key);
-                }
+                pw.print(INDENT);
+                pw.println(key);
+            }
+        }
+        pw.println("\n Pending snoozed notifications");
+        for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) {
+            pw.print(INDENT);
+            pw.println("key: " + userPkgKey);
+            ArrayMap<String, Long> snoozedRecords =
+                    mPersistedSnoozedNotifications.get(userPkgKey);
+            if (snoozedRecords == null) {
+                continue;
+            }
+            Set<String> snoozedKeys = snoozedRecords.keySet();
+            for (String key : snoozedKeys) {
+                pw.print(INDENT);
+                pw.print(INDENT);
+                pw.print(INDENT);
+                pw.print(key);
+                pw.print(INDENT);
+                pw.println(snoozedRecords.get(key));
             }
         }
     }
@@ -538,41 +497,34 @@
         void insert(T t) throws IOException;
     }
     private <T> void writeXml(XmlSerializer out,
-            ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag,
+            ArrayMap<String, ArrayMap<String, T>> targets, String tag,
             Inserter<T> attributeInserter)
             throws IOException {
         synchronized (targets) {
             final int M = targets.size();
             for (int i = 0; i < M; i++) {
-                final ArrayMap<String, ArrayMap<String, T>> packages =
-                        targets.valueAt(i);
-                if (packages == null) {
-                    continue;
-                }
-                final int N = packages.size();
-                for (int j = 0; j < N; j++) {
-                    final ArrayMap<String, T> keyToValue = packages.valueAt(j);
-                    if (keyToValue == null) {
-                        continue;
-                    }
-                    final int O = keyToValue.size();
-                    for (int k = 0; k < O; k++) {
-                        T value = keyToValue.valueAt(k);
+                String userIdPkgKey = targets.keyAt(i);
+                // T is a String (snoozed until context) or Long (snoozed until time)
+                ArrayMap<String, T> keyToValue = targets.valueAt(i);
+                for (int j = 0; j < keyToValue.size(); j++) {
+                    String key = keyToValue.keyAt(j);
+                    T value = keyToValue.valueAt(j);
 
-                        out.startTag(null, tag);
+                    out.startTag(null, tag);
 
-                        attributeInserter.insert(value);
+                    attributeInserter.insert(value);
 
-                        out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
-                                XML_SNOOZED_NOTIFICATION_VERSION);
-                        out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k));
-                        out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j));
-                        out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
-                                targets.keyAt(i).toString());
+                    out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
+                            XML_SNOOZED_NOTIFICATION_VERSION);
+                    out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
 
-                        out.endTag(null, tag);
+                    String pkg = mPackages.get(key);
+                    int userId = mUsers.get(key);
+                    out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
+                    out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
+                            String.valueOf(userId));
 
-                    }
+                    out.endTag(null, tag);
                 }
             }
         }
@@ -606,7 +558,6 @@
                             }
                             scheduleRepost(pkg, key, userId, time - System.currentTimeMillis());
                         }
-                        continue;
                     }
                     if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
                         final String creationId = parser.getAttributeValue(
@@ -615,18 +566,9 @@
                             storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
                                     creationId);
                         }
-                        continue;
                     }
-
-
                 } catch (Exception e) {
-                    //we dont cre if it is a number format exception or a null pointer exception.
-                    //we just want to debug it and continue with our lives
-                    if (DEBUG) {
-                        Slog.d(TAG,
-                                "Exception in reading snooze data from policy xml: "
-                                        + e.getMessage());
-                    }
+                    Slog.e(TAG,  "Exception in reading snooze data from policy xml", e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index c5b868f..31fa4d1 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -17,6 +17,8 @@
 package com.android.server.people;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.CancellationSignal;
 import android.service.appprediction.IPredictionService;
 
 /**
@@ -25,16 +27,23 @@
 public abstract class PeopleServiceInternal extends IPredictionService.Stub {
 
     /**
+     * Prunes the data for the specified user. Called by {@link
+     * com.android.server.people.data.DataMaintenanceService} when the device is idle.
+     */
+    public abstract void pruneDataForUser(@UserIdInt int userId,
+            @NonNull CancellationSignal signal);
+
+    /**
      * The number conversation infos will be dynamic, based on the currently installed apps on the
      * device. All of which should be combined into a single blob to be backed up.
      */
-    public abstract byte[] backupConversationInfos(@NonNull int userId);
+    public abstract byte[] backupConversationInfos(@UserIdInt int userId);
 
     /**
      * Multiple conversation infos may exist in the restore payload, child classes are required to
      * manage the restoration based on how individual conversation infos were originally combined
      * during backup.
      */
-    public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+    public abstract void restoreConversationInfos(@UserIdInt int userId, @NonNull String key,
             @NonNull byte[] payload);
 }
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index d629b54..0fb889c 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -69,7 +69,7 @@
     // Logs all filtering instead of enforcing
     private static final boolean DEBUG_ALLOW_ALL = false;
     private static final boolean DEBUG_LOGGING = false;
-    private static final boolean FEATURE_ENABLED_BY_DEFAULT = false;
+    private static final boolean FEATURE_ENABLED_BY_DEFAULT = true;
 
     /**
      * This contains a list of app UIDs that are implicitly queryable because another app explicitly
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 96d3cc1..3939f26 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -68,7 +68,6 @@
 
     private Context mContext;
     private Injector mInjector;
-    private AppOpsManager mAppOpsManager;
 
     public CrossProfileAppsServiceImpl(Context context) {
         this(context, new InjectorImpl(context));
@@ -217,19 +216,27 @@
 
         mInjector.getActivityTaskManagerInternal().startActivityAsUser(caller, callingPackage,
                 callingFeatureId, launchIntent, /* options= */ null, userId);
+        logStartActivityByIntent(callingPackage);
+    }
+
+    private void logStartActivityByIntent(String packageName) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.START_ACTIVITY_BY_INTENT)
+                .setStrings(packageName)
+                .setBoolean(isCallingUserAManagedProfile())
+                .write();
     }
 
     @Override
     public boolean canRequestInteractAcrossProfiles(String callingPackage) {
         Objects.requireNonNull(callingPackage);
         verifyCallingPackage(callingPackage);
-        return canRequestInteractAcrossProfilesUnchecked(
-                callingPackage, mInjector.getCallingUserId());
+        return canRequestInteractAcrossProfilesUnchecked(callingPackage);
     }
 
-    private boolean canRequestInteractAcrossProfilesUnchecked(
-            String packageName, @UserIdInt int userId) {
-        List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(packageName, userId);
+    private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) {
+        List<UserHandle> targetUserProfiles =
+                getTargetUserProfilesUnchecked(packageName, mInjector.getCallingUserId());
         if (targetUserProfiles.isEmpty()) {
             return false;
         }
@@ -256,12 +263,11 @@
         Objects.requireNonNull(callingPackage);
         verifyCallingPackage(callingPackage);
 
-        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(callingPackage,
-                mInjector.getCallingUserId());
+        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
         if (targetUserProfiles.isEmpty()) {
             return false;
         }
-
         return hasInteractAcrossProfilesPermission(callingPackage);
     }
 
@@ -363,6 +369,12 @@
         });
     }
 
+    /**
+     * See {@link android.content.pm.CrossProfileApps#setInteractAcrossProfilesAppOp(String, int)}.
+     *
+     * <p>Logs metrics. Use {@link #setInteractAcrossProfilesAppOpUnchecked(String, int, boolean)}
+     * to avoid permission checks or to specify not to log metrics.
+     */
     @Override
     public void setInteractAcrossProfilesAppOp(String packageName, @Mode int newMode) {
         final int callingUid = mInjector.getCallingUid();
@@ -379,6 +391,11 @@
                     "MANAGE_APP_OPS_MODES or CONFIGURE_INTERACT_ACROSS_PROFILES is required to set"
                             + " the app-op for interacting across profiles.");
         }
+        setInteractAcrossProfilesAppOpUnchecked(packageName, newMode, /* logMetrics= */ true);
+    }
+
+    private void setInteractAcrossProfilesAppOpUnchecked(
+            String packageName, @Mode int newMode, boolean logMetrics) {
         if (newMode == AppOpsManager.MODE_ALLOWED
                 && !canConfigureInteractAcrossProfiles(packageName)) {
             // The user should not be prompted for apps that cannot request to interact across
@@ -394,7 +411,7 @@
             if (!isPackageInstalled(packageName, profileId)) {
                 continue;
             }
-            setInteractAcrossProfilesAppOpForUser(packageName, newMode, profileId);
+            setInteractAcrossProfilesAppOpForUser(packageName, newMode, profileId, logMetrics);
         }
     }
 
@@ -413,16 +430,16 @@
     }
 
     private void setInteractAcrossProfilesAppOpForUser(
-            String packageName, @Mode int newMode, @UserIdInt int userId) {
+            String packageName, @Mode int newMode, @UserIdInt int userId, boolean logMetrics) {
         try {
-            setInteractAcrossProfilesAppOpForUserOrThrow(packageName, newMode, userId);
+            setInteractAcrossProfilesAppOpForUserOrThrow(packageName, newMode, userId, logMetrics);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Missing package " + packageName + " on user ID " + userId, e);
         }
     }
 
     private void setInteractAcrossProfilesAppOpForUserOrThrow(
-            String packageName, @Mode int newMode, @UserIdInt int userId)
+            String packageName, @Mode int newMode, @UserIdInt int userId, boolean logMetrics)
             throws PackageManager.NameNotFoundException {
         final int uid = mInjector.getPackageManager()
                 .getPackageUidAsUser(packageName, /* flags= */ 0, userId);
@@ -444,6 +461,28 @@
                     .setMode(OP_INTERACT_ACROSS_PROFILES, uid, packageName, newMode);
         }
         sendCanInteractAcrossProfilesChangedBroadcast(packageName, uid, UserHandle.of(userId));
+        maybeLogSetInteractAcrossProfilesAppOp(packageName, newMode, userId, logMetrics, uid);
+    }
+
+    private void maybeLogSetInteractAcrossProfilesAppOp(
+            String packageName,
+            @Mode int newMode,
+            @UserIdInt int userId,
+            boolean logMetrics,
+            int uid) {
+        if (!logMetrics) {
+            return;
+        }
+        if (userId != mInjector.getCallingUserId()) {
+            // Only log once per profile group by checking for the calling user ID.
+            return;
+        }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_INTERACT_ACROSS_PROFILES_APP_OP)
+                .setStrings(packageName)
+                .setInt(newMode)
+                .setBoolean(appDeclaresCrossProfileAttribute(uid))
+                .write();
     }
 
     /**
@@ -541,11 +580,13 @@
                 permission, uid, /* owningUid= */-1, /* exported= */ true);
     }
 
-    private AppOpsManager getAppOpsManager() {
-        if (mAppOpsManager == null) {
-            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
-        }
-        return mAppOpsManager;
+    private boolean isCallingUserAManagedProfile() {
+        return isManagedProfile(mInjector.getCallingUserId());
+    }
+
+    private boolean isManagedProfile(@UserIdInt int userId) {
+        return mInjector.withCleanCallingIdentity(()
+                -> mContext.getSystemService(UserManager.class).isManagedProfile(userId));
     }
 
     private static class InjectorImpl implements Injector {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b98bb08..8ad3e9d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,10 +611,10 @@
     /**
      * Bind mount private volume CE and DE mirror storage.
      */
-    public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+    public void tryMountDataMirror(String volumeUuid) throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            mInstalld.onPrivateVolumeMounted(volumeUuid);
+            mInstalld.tryMountDataMirror(volumeUuid);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2c85d06..8c6e6916 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4600,7 +4600,7 @@
         synchronized (mLock) {
             final AndroidPackage p = mPackages.get(packageName);
             if (p != null && p.isMatch(flags)) {
-                PackageSetting ps = getPackageSetting(p.getPackageName());
+                PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid);
                 if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
                     return -1;
                 }
@@ -5924,7 +5924,10 @@
      */
     @Override
     public String[] getPackagesForUid(int uid) {
-        final int callingUid = Binder.getCallingUid();
+        return getPackagesForUidInternal(uid, Binder.getCallingUid());
+    }
+
+    private String[] getPackagesForUidInternal(int uid, int callingUid) {
         final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
         final int userId = UserHandle.getUserId(uid);
         final int appId = UserHandle.getAppId(uid);
@@ -14704,8 +14707,7 @@
         void handleVerificationFinished() {
             if (!mVerificationCompleted) {
                 mVerificationCompleted = true;
-                if (mIntegrityVerificationCompleted || mRet != INSTALL_SUCCEEDED) {
-                    mIntegrityVerificationCompleted = true;
+                if (mIntegrityVerificationCompleted) {
                     handleReturnCode();
                 }
                 // integrity verification still pending.
@@ -14715,8 +14717,7 @@
         void handleIntegrityVerificationFinished() {
             if (!mIntegrityVerificationCompleted) {
                 mIntegrityVerificationCompleted = true;
-                if (mVerificationCompleted || mRet != INSTALL_SUCCEEDED) {
-                    mVerificationCompleted = true;
+                if (mVerificationCompleted) {
                     handleReturnCode();
                 }
                 // verifier still pending
@@ -17380,6 +17381,13 @@
 
     @GuardedBy("mLock")
     private String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+        final int callingUid = Binder.getCallingUid();
+        return resolveInternalPackageNameInternalLocked(packageName, versionCode,
+                callingUid);
+    }
+
+    private String resolveInternalPackageNameInternalLocked(
+            String packageName, long versionCode, int callingUid) {
         // Handle renamed packages
         String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
         packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -17393,12 +17401,12 @@
 
         // Figure out which lib versions the caller can see
         LongSparseLongArray versionsCallerCanSee = null;
-        final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+        final int callingAppId = UserHandle.getAppId(callingUid);
         if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
                 && callingAppId != Process.ROOT_UID) {
             versionsCallerCanSee = new LongSparseLongArray();
             String libName = versionedLib.valueAt(0).getName();
-            String[] uidPackages = getPackagesForUid(Binder.getCallingUid());
+            String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
             if (uidPackages != null) {
                 for (String uidPackage : uidPackages) {
                     PackageSetting ps = mSettings.getPackageLPr(uidPackage);
@@ -23003,7 +23011,7 @@
         @Override
         public AndroidPackage getPackage(int uid) {
             synchronized (mLock) {
-                final String[] packageNames = getPackagesForUid(uid);
+                final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
                 AndroidPackage pkg = null;
                 final int numPackages = packageNames == null ? 0 : packageNames.length;
                 for (int i = 0; pkg == null && i < numPackages; i++) {
@@ -24017,9 +24025,13 @@
 
     @Nullable
     public PackageSetting getPackageSetting(String packageName) {
+        return getPackageSettingInternal(packageName, Binder.getCallingUid());
+    }
+
+    private PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
         synchronized (mLock) {
-            packageName = resolveInternalPackageNameLPr(
-                    packageName, PackageManager.VERSION_CODE_HIGHEST);
+            packageName = resolveInternalPackageNameInternalLocked(
+                    packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
             return mSettings.mPackages.get(packageName);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 9395c97..a9035b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -578,7 +578,9 @@
 
     /** Returns true if standard APK Verity is enabled. */
     static boolean isApkVerityEnabled() {
-        return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_ENABLED;
+        return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
+                || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED)
+                        == FSVERITY_ENABLED;
     }
 
     static boolean isLegacyApkVerityEnabled() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index c267cea..f1e403b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -119,6 +119,7 @@
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -135,7 +136,6 @@
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
-    private static final byte[] STDIN_PATH_BYTES = "-".getBytes(StandardCharsets.UTF_8);
     /** Path where ART profiles snapshots are dumped for the shell user */
     private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
     private static final int DEFAULT_WAIT_MS = 60 * 1000;
@@ -2988,8 +2988,10 @@
         try {
             // 1. Single file from stdin.
             if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
-                String name = "base." + (isApex ? "apex" : "apk");
-                session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, STDIN_PATH_BYTES, null);
+                final String name = "base." + (isApex ? "apex" : "apk");
+                final String metadata = "-" + name;
+                session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
+                        metadata.getBytes(StandardCharsets.UTF_8), null);
                 return 0;
             }
 
@@ -2998,24 +3000,58 @@
 
                 // 2. File with specified size read from stdin.
                 if (delimLocation != -1) {
-                    String name = arg.substring(0, delimLocation);
-                    String sizeStr = arg.substring(delimLocation + 1);
-                    long sizeBytes;
+                    final String[] fileDesc = arg.split(":");
+                    String name = null;
+                    long sizeBytes = -1;
+                    String metadata;
+                    byte[] signature = null;
+
+                    try {
+                        if (fileDesc.length > 0) {
+                            name = fileDesc[0];
+                        }
+                        if (fileDesc.length > 1) {
+                            sizeBytes = Long.parseUnsignedLong(fileDesc[1]);
+                        }
+                        if (fileDesc.length > 2 && !TextUtils.isEmpty(fileDesc[2])) {
+                            metadata = fileDesc[2];
+                        } else {
+                            metadata = name;
+                        }
+                        if (fileDesc.length > 3) {
+                            signature = Base64.getDecoder().decode(fileDesc[3]);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        getErrPrintWriter().println(
+                                "Unable to parse file parameters: " + arg + ", reason: " + e);
+                        return 1;
+                    }
 
                     if (TextUtils.isEmpty(name)) {
                         getErrPrintWriter().println("Empty file name in: " + arg);
                         return 1;
                     }
+
+                    if (signature != null) {
+                        // Streaming/adb mode.
+                        metadata = "+" + metadata;
+                    } else {
+                        // Singleshot read from stdin.
+                        metadata = "-" + metadata;
+                    }
+
                     try {
-                        sizeBytes = Long.parseUnsignedLong(sizeStr);
-                    } catch (NumberFormatException e) {
-                        getErrPrintWriter().println("Unable to parse size from: " + arg);
+                        if (V4Signature.readFrom(signature) == null) {
+                            getErrPrintWriter().println("V4 signature is invalid in: " + arg);
+                            return 1;
+                        }
+                    } catch (Exception e) {
+                        getErrPrintWriter().println("V4 signature is invalid: " + e + " in " + arg);
                         return 1;
                     }
 
-                    // Incremental requires unique metadatas, let's add a name to the dash.
                     session.addFile(LOCATION_DATA_APP, name, sizeBytes,
-                            ("-" + name).getBytes(StandardCharsets.UTF_8), null);
+                            metadata.getBytes(StandardCharsets.UTF_8), signature);
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0fb4cb0..d451152 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4393,6 +4393,7 @@
             ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT, "SYSTEM_EXT",
             ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD, "VIRTUAL_PRELOAD",
             ApplicationInfo.PRIVATE_FLAG_ODM, "ODM",
+            ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING, "PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING",
     };
 
     void dumpVersionLPr(IndentingPrintWriter pw) {
@@ -5232,9 +5233,7 @@
                         List<RuntimePermissionsState.PermissionState> permissions =
                                 getPermissionsFromPermissionsState(
                                         packageSetting.getPermissionsState(), userId);
-                        if (permissions != null) {
-                            packagePermissions.put(packageName, permissions);
-                        }
+                        packagePermissions.put(packageName, permissions);
                     }
                 }
 
@@ -5247,9 +5246,7 @@
                     List<RuntimePermissionsState.PermissionState> permissions =
                             getPermissionsFromPermissionsState(
                                     sharedUserSetting.getPermissionsState(), userId);
-                    if (permissions != null) {
-                        sharedUserPermissions.put(sharedUserName, permissions);
-                    }
+                    sharedUserPermissions.put(sharedUserName, permissions);
                 }
 
                 runtimePermissions = new RuntimePermissionsState(version, fingerprint,
@@ -5259,15 +5256,11 @@
             mPersistence.write(runtimePermissions, UserHandle.of(userId));
         }
 
-        @Nullable
+        @NonNull
         private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState(
                 @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
             List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates(
                     userId);
-            if (permissionStates.isEmpty()) {
-                return null;
-            }
-
             List<RuntimePermissionsState.PermissionState> permissions =
                     new ArrayList<>();
             int permissionStatesSize = permissionStates.size();
@@ -5339,31 +5332,60 @@
             boolean defaultPermissionsGranted = Build.FINGERPRINT.equals(fingerprint);
             mDefaultPermissionsGranted.put(userId, defaultPermissionsGranted);
 
-            for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
-                    : runtimePermissions.getPackagePermissions().entrySet()) {
-                String packageName = entry.getKey();
-                List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+            boolean isUpgradeToR = getInternalVersion().sdkVersion < Build.VERSION_CODES.R;
 
-                PackageSetting packageSetting = mPackages.get(packageName);
-                if (packageSetting == null) {
-                    Slog.w(PackageManagerService.TAG, "Unknown package:" + packageName);
-                    continue;
+            Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                    runtimePermissions.getPackagePermissions();
+            int packagesSize = mPackages.size();
+            for (int i = 0; i < packagesSize; i++) {
+                String packageName = mPackages.keyAt(i);
+                PackageSetting packageSetting = mPackages.valueAt(i);
+
+                List<RuntimePermissionsState.PermissionState> permissions =
+                        packagePermissions.get(packageName);
+                if (permissions != null) {
+                    readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(),
+                            userId);
+                } else if (packageSetting.sharedUser == null && !isUpgradeToR) {
+                    Slog.w(TAG, "Missing permission state for package: " + packageName);
+                    generateFallbackPermissionsStateLpr(
+                            packageSetting.pkg.getRequestedPermissions(),
+                            packageSetting.pkg.getTargetSdkVersion(),
+                            packageSetting.getPermissionsState(), userId);
                 }
-                readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(), userId);
             }
 
-            for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
-                    : runtimePermissions.getSharedUserPermissions().entrySet()) {
-                String sharedUserName = entry.getKey();
-                List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+            Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                    runtimePermissions.getSharedUserPermissions();
+            int sharedUsersSize = mSharedUsers.size();
+            for (int i = 0; i < sharedUsersSize; i++) {
+                String sharedUserName = mSharedUsers.keyAt(i);
+                SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
 
-                SharedUserSetting sharedUserSetting = mSharedUsers.get(sharedUserName);
-                if (sharedUserSetting == null) {
-                    Slog.w(PackageManagerService.TAG, "Unknown shared user:" + sharedUserName);
-                    continue;
+                List<RuntimePermissionsState.PermissionState> permissions =
+                        sharedUserPermissions.get(sharedUserName);
+                if (permissions != null) {
+                    readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
+                            userId);
+                } else if (!isUpgradeToR) {
+                    Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
+                    ArraySet<String> requestedPermissions = new ArraySet<>();
+                    int targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+                    int sharedUserPackagesSize = sharedUserSetting.packages.size();
+                    for (int packagesI = 0; packagesI < sharedUserPackagesSize; packagesI++) {
+                        PackageSetting packageSetting = sharedUserSetting.packages.valueAt(
+                                packagesI);
+                        if (packageSetting == null || packageSetting.pkg == null
+                                || !packageSetting.getInstalled(userId)) {
+                            continue;
+                        }
+                        AndroidPackage pkg = packageSetting.pkg;
+                        requestedPermissions.addAll(pkg.getRequestedPermissions());
+                        targetSdkVersion = Math.min(targetSdkVersion, pkg.getTargetSdkVersion());
+                    }
+                    generateFallbackPermissionsStateLpr(requestedPermissions, targetSdkVersion,
+                            sharedUserSetting.getPermissionsState(), userId);
                 }
-                readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
-                        userId);
             }
         }
 
@@ -5394,6 +5416,30 @@
             }
         }
 
+        private void generateFallbackPermissionsStateLpr(
+                @NonNull Collection<String> requestedPermissions, int targetSdkVersion,
+                @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+            for (String permissionName : requestedPermissions) {
+                BasePermission permission = mPermissions.getPermission(permissionName);
+                if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
+                        && permission.isRuntime() && !permission.isRemoved()) {
+                    if (permission.isHardOrSoftRestricted() || permission.isImmutablyRestricted()) {
+                        permissionsState.updatePermissionFlags(permission, userId,
+                                PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+                                PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+                    }
+                    if (targetSdkVersion < Build.VERSION_CODES.M) {
+                        permissionsState.updatePermissionFlags(permission, userId,
+                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                                        | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                                        | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
+                        permissionsState.grantRuntimePermission(permission, userId);
+                    }
+                }
+            }
+        }
+
         @GuardedBy("Settings.this.mLock")
         private void readLegacyStateForUserSyncLPr(int userId) {
             File permissionsFile = getUserRuntimePermissionsFile(userId);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a45a996..19e6062 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -45,7 +45,6 @@
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -2058,11 +2057,8 @@
 
     /** {@inheritDoc} */
     @Override
-    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
-        final int type = attrs.type;
-        final boolean isRoundedCornerOverlay =
-                (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
-
+    public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
+            int[] outAppOp) {
         if (isRoundedCornerOverlay && mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
                 != PERMISSION_GRANTED) {
             return ADD_PERMISSION_DENIED;
@@ -2119,7 +2115,7 @@
         ApplicationInfo appInfo;
         try {
             appInfo = mPackageManager.getApplicationInfoAsUser(
-                            attrs.packageName,
+                            packageName,
                             0 /* flags */,
                             UserHandle.getUserId(callingUid));
         } catch (PackageManager.NameNotFoundException e) {
@@ -2139,7 +2135,7 @@
 
         // check if user has enabled this operation. SecurityException will be thrown if this app
         // has not been allowed by the user
-        final int mode = mAppOpsManager.noteOpNoThrow(outAppOp[0], callingUid, attrs.packageName);
+        final int mode = mAppOpsManager.noteOpNoThrow(outAppOp[0], callingUid, packageName);
         switch (mode) {
             case AppOpsManager.MODE_ALLOWED:
             case AppOpsManager.MODE_IGNORED:
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index a83c58d..740472e 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -35,14 +35,17 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
 import android.util.Log;
 
 import com.android.internal.compat.IPlatformCompat;
+import com.android.server.LocalServices;
 
 /**
  * The behavior of soft restricted permissions is different for each permission. This class collects
@@ -107,11 +110,16 @@
                 final boolean isWhiteListed;
                 boolean shouldApplyRestriction;
                 final boolean hasRequestedLegacyExternalStorage;
+                final boolean shouldPreserveLegacyExternalStorage;
                 final boolean hasWriteMediaStorageGrantedForUid;
                 final boolean isScopedStorageEnabled;
 
                 if (appInfo != null) {
                     PackageManager pm = context.getPackageManager();
+                    PackageManagerInternal pmInternal =
+                            LocalServices.getService(PackageManagerInternal.class);
+                    StorageManagerInternal smInternal =
+                            LocalServices.getService(StorageManagerInternal.class);
                     int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
                     hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
@@ -123,12 +131,16 @@
                     isScopedStorageEnabled =
                             isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE)
                             || isScopedStorageRequired;
+                    shouldPreserveLegacyExternalStorage = pmInternal.getPackage(
+                            appInfo.packageName).hasPreserveLegacyExternalStorage()
+                            && smInternal.hasLegacyExternalStorage(appInfo.uid);
                     shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
-                            || isScopedStorageRequired;
+                            || (isScopedStorageRequired && !shouldPreserveLegacyExternalStorage);
                 } else {
                     isWhiteListed = false;
                     shouldApplyRestriction = false;
                     hasRequestedLegacyExternalStorage = false;
+                    shouldPreserveLegacyExternalStorage = false;
                     hasWriteMediaStorageGrantedForUid = false;
                     isScopedStorageEnabled = false;
                 }
@@ -150,7 +162,8 @@
                     public boolean mayAllowExtraAppOp() {
                         return !shouldApplyRestriction
                                 && (hasRequestedLegacyExternalStorage
-                                        || hasWriteMediaStorageGrantedForUid);
+                                        || hasWriteMediaStorageGrantedForUid
+                                        || shouldPreserveLegacyExternalStorage);
                     }
                     @Override
                     public boolean mayDenyExtraAppOpIfGranted() {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 67a22d3..9d417c9 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -686,17 +686,25 @@
             WindowManagerFuncs windowManagerFuncs);
 
     /**
-     * Check permissions when adding a window.
+     * Check permissions when adding a window or a window token from
+     * {@link android.app.WindowContext}.
      *
-     * @param attrs The window's LayoutParams.
+     * @param type The window type
+     * @param isRoundedCornerOverlay {@code true} to indicate the adding window is
+     *                                           round corner overlay.
+     * @param packageName package name
      * @param outAppOp First element will be filled with the app op corresponding to
      *                 this window, or OP_NONE.
      *
      * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
      *      else an error code, usually
      *      {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
+     *
+     * @see IWindowManager#addWindowTokenWithOptions(IBinder, int, int, Bundle, String)
+     * @see WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY
      */
-    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
+    int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
+            int[] outAppOp);
 
     /**
      * After the window manager has computed the current configuration based
@@ -866,6 +874,10 @@
         }
     }
 
+    default int getMaxWindowLayer() {
+        return 35;
+    }
+
     /**
      * Return how to Z-order sub-windows in relation to the window they are attached to.
      * Return positive to have them ordered in front, negative for behind.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 002ab9c..a7b0d84 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -25,6 +25,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.SynchronousUserSwitchObserver;
@@ -95,6 +96,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.LightsManager;
@@ -2486,7 +2488,8 @@
         boolean changed = false;
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
                 | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
-                | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE)) != 0) {
+                | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
+                | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
             if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
@@ -3153,7 +3156,10 @@
     }
 
     private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
-            final String reason, boolean wait) {
+            @Nullable final String reason, boolean wait) {
+        if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+            UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+        }
         if (mHandler == null || !mSystemReady) {
             if (RescueParty.isAttemptingFactoryReset()) {
                 // If we're stuck in a really low-level reboot loop, and a
@@ -5038,7 +5044,7 @@
          * @param wait If true, this call waits for the reboot to complete and does not return.
          */
         @Override // Binder call
-        public void reboot(boolean confirm, String reason, boolean wait) {
+        public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
             if (PowerManager.REBOOT_RECOVERY.equals(reason)
                     || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 91e7cc9..f9981d0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -802,7 +802,6 @@
             newRollback = getRollbackForSessionLocked(packageSession.getSessionId());
             if (newRollback == null) {
                 newRollback = createNewRollbackLocked(parentSession);
-                mRollbacks.add(newRollback);
             }
         }
         newRollback.addToken(token);
@@ -869,14 +868,6 @@
             return false;
         }
 
-        ApplicationInfo appInfo = pkgInfo.applicationInfo;
-        boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
-                pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
-                appInfo.splitSourceDirs, session.rollbackDataPolicy);
-        if (!success) {
-            return success;
-        }
-
         if (isApex) {
             // Check if this apex contains apks inside it. If true, then they should be added as
             // a RollbackPackageInfo into this rollback
@@ -894,12 +885,24 @@
                     Slog.e(TAG, apkInApex + " is not installed");
                     return false;
                 }
-                success = rollback.enableForPackageInApex(
-                        apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
-                if (!success) return success;
+                if (!rollback.enableForPackageInApex(
+                        apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy)) {
+                    return false;
+                }
             }
         }
-        return true;
+
+        /**
+         * The order is important here! Always enable the embedded apk-in-apex (if any) before
+         * enabling the embedding apex. Otherwise the rollback object might be in an inconsistent
+         * state where an embedding apex is successfully enabled while one of its embedded
+         * apk-in-apex failed. Note {@link Rollback#allPackagesEnabled()} won't behave correctly if
+         * a rollback object is inconsistent because it doesn't count apk-in-apex.
+         */
+        ApplicationInfo appInfo = pkgInfo.applicationInfo;
+        return rollback.enableForPackage(packageName, newPackage.versionCode,
+                pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
+                appInfo.splitSourceDirs, session.rollbackDataPolicy);
     }
 
     @Override
@@ -982,8 +985,6 @@
             if (!session.isMultiPackage()) {
                 if (!enableRollbackForPackageSession(newRollback, session)) {
                     Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
-                    result.offer(-1);
-                    return;
                 }
             } else {
                 for (int childSessionId : session.getChildSessionIds()) {
@@ -991,22 +992,19 @@
                             installer.getSessionInfo(childSessionId);
                     if (childSession == null) {
                         Slog.e(TAG, "No matching child install session for: " + childSessionId);
-                        result.offer(-1);
-                        return;
+                        break;
                     }
                     if (!enableRollbackForPackageSession(newRollback, childSession)) {
                         Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
-                        result.offer(-1);
-                        return;
+                        break;
                     }
                 }
             }
 
-            Rollback rollback = completeEnableRollback(newRollback);
-            if (rollback == null) {
+            if (!completeEnableRollback(newRollback)) {
                 result.offer(-1);
             } else {
-                result.offer(rollback.info.getRollbackId());
+                result.offer(newRollback.info.getRollbackId());
             }
         });
 
@@ -1158,19 +1156,10 @@
                 Rollback rollback;
                 synchronized (mLock) {
                     rollback = getRollbackForSessionLocked(sessionId);
-                    if (rollback == null || rollback.isStaged() || !rollback.isEnabling()
-                            || !rollback.notifySessionWithSuccess()) {
-                        return;
-                    }
-                    // All child sessions finished with success. We can enable this rollback now.
-                    // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from
-                    // mRollbacks here and add it back in #completeEnableRollback later.
-                    mRollbacks.remove(rollback);
                 }
-                // TODO: Now #completeEnableRollback returns the same rollback object as the
-                // parameter on success. It would be more readable to return a boolean to indicate
-                // success or failure.
-                if (completeEnableRollback(rollback) != null) {
+                if (rollback != null && !rollback.isStaged() && rollback.isEnabling()
+                        && rollback.notifySessionWithSuccess()
+                        && completeEnableRollback(rollback)) {
                     makeRollbackAvailable(rollback);
                 }
             } else {
@@ -1188,14 +1177,14 @@
     }
 
     /**
-     * Add a rollback to the list of rollbacks. This should be called after rollback has been
-     * enabled for all packages in the rollback. It does not make the rollback available yet.
+     * Persist a rollback as enable-completed. It does not make the rollback available yet.
+     * This rollback will be deleted and removed from {@link #mRollbacks} should any error happens.
      *
-     * @return the Rollback instance for a successfully enable-completed rollback,
-     * or null on error.
+     * @return {code true} if {code rollback} is successfully enable-completed,
+     * or {code false} otherwise.
      */
     @WorkerThread
-    private Rollback completeEnableRollback(Rollback rollback) {
+    private boolean completeEnableRollback(Rollback rollback) {
         if (LOCAL_LOGV) {
             Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
         }
@@ -1206,26 +1195,24 @@
         // rollback for the embedded apk-in-apex, if any.
         if (!rollback.allPackagesEnabled()) {
             Slog.e(TAG, "Failed to enable rollback for all packages in session.");
+            mRollbacks.remove(rollback);
             rollback.delete(mAppDataRollbackHelper);
-            return null;
+            return false;
         }
 
+        // Note: There is a small window of time between when
+        // the session has been committed by the package
+        // manager and when we make the rollback available
+        // here. Presumably the window is small enough that
+        // nobody will want to roll back the newly installed
+        // package before we make the rollback available.
+        // TODO: We'll lose the rollback if the
+        // device reboots between when the session is
+        // committed and this point. Revisit this after
+        // adding support for rollback of staged installs.
         rollback.saveRollback();
-        synchronized (mLock) {
-            // Note: There is a small window of time between when
-            // the session has been committed by the package
-            // manager and when we make the rollback available
-            // here. Presumably the window is small enough that
-            // nobody will want to roll back the newly installed
-            // package before we make the rollback available.
-            // TODO: We'll lose the rollback if the
-            // device reboots between when the session is
-            // committed and this point. Revisit this after
-            // adding support for rollback of staged installs.
-            mRollbacks.add(rollback);
-        }
 
-        return rollback;
+        return true;
     }
 
     @WorkerThread
@@ -1305,6 +1292,10 @@
         }
     }
 
+    /**
+     * Creates and returns a Rollback according to the given SessionInfo
+     * and adds it to {@link #mRollbacks}.
+     */
     @WorkerThread
     @GuardedBy("mLock")
     private Rollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
@@ -1339,6 +1330,7 @@
                     installerPackageName, packageSessionIds);
         }
 
+        mRollbacks.add(rollback);
         return rollback;
     }
 
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index e90c02a..8cf2d03 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.SystemProperties;
@@ -55,7 +56,8 @@
     private final IBinder mService = new IFileIntegrityService.Stub() {
         @Override
         public boolean isApkVeritySupported() {
-            return SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+            return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
+                    || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 372c1f5..cd5e360 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -169,7 +169,7 @@
     }
 
     @Override
-    public synchronized  void serviceDied(long cookie) {
+    public synchronized void serviceDied(long cookie) {
         Log.w(TAG, String.format("Underlying HAL driver died."));
         for (Session session : mActiveSessions) {
             session.moduleDied();
@@ -248,44 +248,77 @@
         @Override
         public int loadModel(@NonNull SoundModel model) {
             Log.d(TAG, String.format("loadModel(model=%s)", model));
-            synchronized (SoundTriggerModule.this) {
-                checkValid();
-                if (mNumLoadedModels == mProperties.maxSoundModels) {
-                    throw new RecoverableException(Status.RESOURCE_CONTENTION,
-                            "Maximum number of models loaded.");
+
+            // We must do this outside the lock, to avoid possible deadlocks with the remote process
+            // that provides the audio sessions, which may also be calling into us.
+            SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
+                    mAudioSessionProvider.acquireSession();
+
+            try {
+                synchronized (SoundTriggerModule.this) {
+                    checkValid();
+                    if (mNumLoadedModels == mProperties.maxSoundModels) {
+                        throw new RecoverableException(Status.RESOURCE_CONTENTION,
+                                "Maximum number of models loaded.");
+                    }
+                    Model loadedModel = new Model();
+                    int result = loadedModel.load(model, audioSession);
+                    ++mNumLoadedModels;
+                    return result;
                 }
-                Model loadedModel = new Model();
-                int result = loadedModel.load(model);
-                ++mNumLoadedModels;
-                return result;
+            } catch (Exception e) {
+                // We must do this outside the lock, to avoid possible deadlocks with the remote
+                // process that provides the audio sessions, which may also be calling into us.
+                mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
+                throw e;
             }
         }
 
         @Override
         public int loadPhraseModel(@NonNull PhraseSoundModel model) {
             Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
-            synchronized (SoundTriggerModule.this) {
-                checkValid();
-                if (mNumLoadedModels == mProperties.maxSoundModels) {
-                    throw new RecoverableException(Status.RESOURCE_CONTENTION,
-                            "Maximum number of models loaded.");
+
+            // We must do this outside the lock, to avoid possible deadlocks with the remote process
+            // that provides the audio sessions, which may also be calling into us.
+            SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
+                    mAudioSessionProvider.acquireSession();
+
+            try {
+                synchronized (SoundTriggerModule.this) {
+                    checkValid();
+                    if (mNumLoadedModels == mProperties.maxSoundModels) {
+                        throw new RecoverableException(Status.RESOURCE_CONTENTION,
+                                "Maximum number of models loaded.");
+                    }
+                    Model loadedModel = new Model();
+                    int result = loadedModel.load(model, audioSession);
+                    ++mNumLoadedModels;
+                    Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+                    return result;
                 }
-                Model loadedModel = new Model();
-                int result = loadedModel.load(model);
-                ++mNumLoadedModels;
-                Log.d(TAG, String.format("loadPhraseModel()->%d", result));
-                return result;
+            } catch (Exception e) {
+                // We must do this outside the lock, to avoid possible deadlocks with the remote
+                // process that provides the audio sessions, which may also be calling into us.
+                mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
+                throw e;
             }
         }
 
         @Override
         public void unloadModel(int modelHandle) {
             Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+
+            int sessionId;
+
             synchronized (SoundTriggerModule.this) {
                 checkValid();
-                mLoadedModels.get(modelHandle).unload();
+                sessionId = mLoadedModels.get(modelHandle).unload();
                 --mNumLoadedModels;
             }
+
+            // We must do this outside the lock, to avoid possible deadlocks with the remote process
+            // that provides the audio sessions, which may also be calling into us.
+            mAudioSessionProvider.releaseSession(sessionId);
         }
 
         @Override
@@ -413,45 +446,40 @@
                 SoundTriggerModule.this.notifyAll();
             }
 
-            private int load(@NonNull SoundModel model) {
+            private int load(@NonNull SoundModel model,
+                    SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                 mModelType = model.type;
+                mSession = audioSession;
                 ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
 
-                mSession = mAudioSessionProvider.acquireSession();
-                try {
-                    mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
-                } catch (Exception e) {
-                    mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
-                    throw e;
-                }
-
+                mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
                 setState(ModelState.LOADED);
                 mLoadedModels.put(mHandle, this);
                 return mHandle;
             }
 
-            private int load(@NonNull PhraseSoundModel model) {
+            private int load(@NonNull PhraseSoundModel model,
+                    SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                 mModelType = model.common.type;
+                mSession = audioSession;
                 ISoundTriggerHw.PhraseSoundModel hidlModel =
                         ConversionUtil.aidl2hidlPhraseSoundModel(model);
 
-                mSession = mAudioSessionProvider.acquireSession();
-                try {
-                    mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
-                } catch (Exception e) {
-                    mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
-                    throw e;
-                }
+                mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
 
                 setState(ModelState.LOADED);
                 mLoadedModels.put(mHandle, this);
                 return mHandle;
             }
 
-            private void unload() {
-                mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+            /**
+             * Unloads the model.
+             * @return The audio session handle.
+             */
+            private int unload() {
                 mHalService.unloadSoundModel(mHandle);
                 mLoadedModels.remove(mHandle);
+                return mSession.mSessionHandle;
             }
 
             private void startRecognition(@NonNull RecognitionConfig config) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 0502d0b..68a7188 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -581,6 +581,9 @@
 
     /**
      * Notifies the tracker that all windows of the app have been drawn.
+     *
+     * @return Non-null info if the activity was pending to draw, otherwise it might have been set
+     *         to invisible (removed from active transition) or it was already drawn.
      */
     @Nullable
     TransitionInfoSnapshot notifyWindowsDrawn(@NonNull ActivityRecord r, long timestampNs) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e26dac6..b3b8159 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2679,7 +2679,8 @@
         // DisplayContent#topRunningActivity().
         final ActivityRecord next = display.topRunningActivity();
         final boolean isLastStackOverEmptyHome =
-                next == null && stack.isFocusedStackOnDisplay() && display.getRootHomeTask() != null;
+                next == null && stack.isFocusedStackOnDisplay()
+                        && display.getOrCreateRootHomeTask() != null;
         if (isLastStackOverEmptyHome) {
             // Don't destroy activity immediately if this is the last activity on the display and
             // the display contains home stack. Although there is no next activity at the moment,
@@ -5201,11 +5202,17 @@
         }
         final TransitionInfoSnapshot info = mStackSupervisor
                 .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
-        final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
-        final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
-        mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
-                windowsDrawnDelayMs, launchState);
-        mStackSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
+        final boolean validInfo = info != null;
+        final int windowsDrawnDelayMs = validInfo ? info.windowsDrawnDelayMs : INVALID_DELAY;
+        final @LaunchState int launchState = validInfo ? info.getLaunchState() : -1;
+        // The activity may have been requested to be invisible (another activity has been launched)
+        // so there is no valid info. But if it is the current top activity (e.g. sleeping), the
+        // invalid state is still reported to make sure the waiting result is notified.
+        if (validInfo || this == mDisplayContent.topRunningActivity()) {
+            mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
+                    windowsDrawnDelayMs, launchState);
+            mStackSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
+        }
         finishLaunchTickingLocked();
         if (task != null) {
             task.hasBeenVisible = true;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index ab8e975..3c39b39 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -111,6 +111,7 @@
 import static com.android.server.wm.TaskProto.ADJUST_IME_AMOUNT;
 import static com.android.server.wm.TaskProto.ANIMATING_BOUNDS;
 import static com.android.server.wm.TaskProto.BOUNDS;
+import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
 import static com.android.server.wm.TaskProto.DEFER_REMOVAL;
 import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS;
 import static com.android.server.wm.TaskProto.DISPLAY_ID;
@@ -731,53 +732,14 @@
                         newBounds);
                 hasNewOverrideBounds = true;
             }
-
-            // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
-            if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                    || overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                // If entering split screen or if something about the available split area changes,
-                // recalculate the split windows to match the new configuration.
-                if (rotationChanged || windowingModeChanged
-                        || prevDensity != getConfiguration().densityDpi
-                        || prevScreenW != getConfiguration().screenWidthDp
-                        || prevScreenH != getConfiguration().screenHeightDp) {
-                    calculateDockedBoundsForConfigChange(newParentConfig, newBounds);
-                    hasNewOverrideBounds = true;
-                }
-            }
         }
 
         if (windowingModeChanged) {
-            // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
-            if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
-                        newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
-                // immediately resize so docked bounds are available in onSplitScreenModeActivated
-                setTaskDisplayedBounds(null);
-                setTaskBounds(newBounds);
-                setBounds(newBounds);
-                newBounds.set(newBounds);
-            } else if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                Rect dockedBounds = display.getRootSplitScreenPrimaryTask().getBounds();
-                final boolean isMinimizedDock =
-                        display.mDisplayContent.getDockedDividerController().isMinimizedDock();
-                if (isMinimizedDock) {
-                    Task topTask = display.getRootSplitScreenPrimaryTask().getTopMostTask();
-                    if (topTask != null) {
-                        dockedBounds = topTask.getBounds();
-                    }
-                }
-                getStackDockedModeBounds(dockedBounds, null /* currentTempTaskBounds */,
-                        newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
-                hasNewOverrideBounds = true;
-            }
+            display.onStackWindowingModeChanged(this);
         }
         if (hasNewOverrideBounds) {
-            if (inSplitScreenPrimaryWindowingMode()) {
-                mStackSupervisor.resizeDockedStackLocked(new Rect(newBounds),
-                        null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                        null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
-                        PRESERVE_WINDOWS, true /* deferResume */);
+            if (inSplitScreenWindowingMode()) {
+                setBounds(newBounds);
             } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) {
                 // For pinned stack, resize is now part of the {@link WindowContainerTransaction}
                 resize(new Rect(newBounds), null /* tempTaskBounds */,
@@ -920,11 +882,7 @@
                 // warning toast about it.
                 mAtmService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                final ActivityStack primarySplitStack = display.getRootSplitScreenPrimaryTask();
-                primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED,
-                        false /* animate */, false /* showRecents */,
-                        false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
-                        primarySplitStack == this ? creating : false);
+                display.onSplitScreenModeDismissed();
             }
         }
 
@@ -1218,7 +1176,7 @@
                     display.getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
             if (topFullScreenStack != null) {
                 final ActivityStack primarySplitScreenStack = display.getRootSplitScreenPrimaryTask();
-                if (display.getIndexOf(topFullScreenStack)
+                if (primarySplitScreenStack != null && display.getIndexOf(topFullScreenStack)
                         > display.getIndexOf(primarySplitScreenStack)) {
                     primarySplitScreenStack.moveToFront(reason + " splitScreenToTop");
                 }
@@ -3994,17 +3952,6 @@
                 ? ((WindowContainer) oldParent).getDisplayContent() : null;
         super.onParentChanged(newParent, oldParent);
 
-        if (display != null && inSplitScreenPrimaryWindowingMode()
-                // only do this for the base stack
-                && !newParent.inSplitScreenPrimaryWindowingMode()) {
-            // If we created a docked stack we want to resize it so it resizes all other stacks
-            // in the system.
-            getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
-                    mTmpRect /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
-            mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect,
-                    mTmpRect2, null, null, PRESERVE_WINDOWS);
-        }
-
         // Resume next focusable stack after reparenting to another display if we aren't removing
         // the prevous display.
         if (oldDisplay != null && oldDisplay.isRemoving()) {
@@ -4950,6 +4897,12 @@
     }
 
     @Override
+    public SurfaceControl getParentSurfaceControl() {
+        // Tile is a "virtual" parent, so we need to intercept the parent surface here
+        return mTile != null ? mTile.getSurfaceControl() : super.getParentSurfaceControl();
+    }
+
+    @Override
     void removeImmediately() {
         // TODO(task-hierarchy): remove this override when tiles are in hierarchy
         if (mTile != null) {
@@ -5002,6 +4955,10 @@
         if (!matchParentBounds()) {
             final Rect bounds = getRequestedOverrideBounds();
             bounds.dumpDebug(proto, BOUNDS);
+        } else if (getStack().getTile() != null) {
+            // use tile's bounds here for cts.
+            final Rect bounds = getStack().getTile().getRequestedOverrideBounds();
+            bounds.dumpDebug(proto, BOUNDS);
         }
         getOverrideDisplayedBounds().dumpDebug(proto, DISPLAYED_BOUNDS);
         mAdjustedBounds.dumpDebug(proto, ADJUSTED_BOUNDS);
@@ -5021,6 +4978,8 @@
             proto.write(SURFACE_HEIGHT, mSurfaceControl.getHeight());
         }
 
+        proto.write(CREATED_BY_ORGANIZER, this instanceof TaskTile);
+
         proto.end(token);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a582f21..a2e8801 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2447,7 +2447,9 @@
                 // split-screen in split-screen.
                 mService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
+                dockedStack.getDisplay().onSplitScreenModeDismissed();
+                dockedStack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
+                        true /* notifyClients */);
             }
             return;
         }
@@ -2809,7 +2811,7 @@
                 final DisplayContent display = task.getStack().getDisplay();
                 final ActivityStack topSecondaryStack =
                         display.getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-                if (topSecondaryStack.isActivityTypeHome()) {
+                if (topSecondaryStack != null && topSecondaryStack.isActivityTypeHome()) {
                     // If the home activity is the top split-screen secondary stack, then the
                     // primary split-screen stack is in the minimized mode which means it can't
                     // receive input keys, so we should move the focused app to the home app so that
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8ed798c..e77d8ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1374,7 +1374,7 @@
                     break;
                 case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
                     final ActivityStack homeStack =
-                            startedActivityStack.getDisplay().getRootHomeTask();
+                            startedActivityStack.getDisplay().getOrCreateRootHomeTask();
                     if (homeStack != null && homeStack.shouldBeVisible(null /* starting */)) {
                         mService.mWindowManager.showRecentApps();
                     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1859fae..f777e90 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -37,7 +37,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -229,6 +228,7 @@
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
+import android.view.WindowContainerTransaction;
 import android.view.WindowManager;
 
 import com.android.internal.R;
@@ -2269,6 +2269,9 @@
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
+                if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
+                    return setTaskWindowingModeSplitScreen(taskId, windowingMode, toTop);
+                }
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
                         MATCH_TASK_IN_STACKS_ONLY);
                 if (task == null) {
@@ -2286,10 +2289,16 @@
                 }
 
                 final ActivityStack stack = task.getStack();
+                // Convert some windowing-mode changes into root-task reparents for split-screen.
+                if (stack.getTile() != null) {
+                    stack.getDisplay().onSplitScreenModeDismissed();
+                }
                 if (toTop) {
                     stack.moveToFront("setTaskWindowingMode", task);
                 }
                 stack.setWindowingMode(windowingMode);
+                stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
+                        true /* notifyClients */);
                 return true;
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -2719,36 +2728,8 @@
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                if (isInLockTaskMode()) {
-                    Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: Is in lock task mode="
-                            + getLockTaskModeState());
-                    return false;
-                }
-
-                final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
-                if (task == null) {
-                    Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
-                    return false;
-                }
-                if (!task.isActivityTypeStandardOrUndefined()) {
-                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
-                            + " non-standard task " + taskId + " to split-screen windowing mode");
-                }
-
-                if (DEBUG_STACK) Slog.d(TAG_STACK,
-                        "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId
-                                + " to createMode=" + createMode + " toTop=" + toTop);
-                mWindowManager.setDockedStackCreateStateLocked(createMode, initialBounds);
-                final int windowingMode = task.getWindowingMode();
-                final ActivityStack stack = task.getStack();
-                if (toTop) {
-                    stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
-                }
-                stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents,
-                        false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
-                        false /* creating */);
-                return windowingMode != task.getWindowingMode();
+                return setTaskWindowingModeSplitScreen(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                        toTop);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2756,6 +2737,49 @@
     }
 
     /**
+     * Moves the specified task into a split-screen tile.
+     */
+    private boolean setTaskWindowingModeSplitScreen(int taskId, int windowingMode, boolean toTop) {
+        if (!WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
+            throw new IllegalArgumentException("Calling setTaskWindowingModeSplitScreen with non"
+                    + "split-screen mode: " + windowingMode);
+        }
+        if (isInLockTaskMode()) {
+            Slog.w(TAG, "setTaskWindowingModeSplitScreen: Is in lock task mode="
+                    + getLockTaskModeState());
+            return false;
+        }
+
+        final Task task = mRootWindowContainer.anyTaskForId(taskId,
+                MATCH_TASK_IN_STACKS_ONLY);
+        if (task == null) {
+            Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
+            return false;
+        }
+        if (!task.isActivityTypeStandardOrUndefined()) {
+            throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+                    + " non-standard task " + taskId + " to split-screen windowing mode");
+        }
+
+        final int prevMode = task.getWindowingMode();
+        final ActivityStack stack = task.getStack();
+        TaskTile tile = null;
+        for (int i = stack.getDisplay().getStackCount() - 1; i >= 0; --i) {
+            tile = stack.getDisplay().getStackAt(i).asTile();
+            if (tile != null && tile.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                break;
+            }
+        }
+        if (tile == null) {
+            throw new IllegalStateException("Can't enter split without associated tile");
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop);
+        mTaskOrganizerController.applyContainerTransaction(wct, null);
+        return prevMode != task.getWindowingMode();
+    }
+
+    /**
      * Removes stacks in the input windowing modes from the system if they are of activity type
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      */
@@ -3963,46 +3987,6 @@
     }
 
     /**
-     * Dismisses split-screen multi-window mode.
-     * @param toTop If true the current primary split-screen stack will be placed or left on top.
-     */
-    @Override
-    public void dismissSplitScreenMode(boolean toTop) {
-        enforceCallerIsRecentsOrHasPermission(
-                MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                final ActivityStack stack =
-                        mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask();
-                if (stack == null) {
-                    Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
-                    return;
-                }
-
-                if (toTop) {
-                    // Caller wants the current split-screen primary stack to be the top stack after
-                    // it goes fullscreen, so move it to the front.
-                    stack.moveToFront("dismissSplitScreenMode");
-                } else {
-                    // In this case the current split-screen primary stack shouldn't be the top
-                    // stack after it goes fullscreen, so we move the focus to the top-most
-                    // split-screen secondary stack next to it.
-                    final ActivityStack otherStack = stack.getDisplay().getTopStackInWindowingMode(
-                            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-                    if (otherStack != null) {
-                        otherStack.moveToFront("dismissSplitScreenMode_other");
-                    }
-                }
-
-                stack.setWindowingMode(WINDOWING_MODE_UNDEFINED);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    /**
      * Dismisses Pip
      * @param animate True if the dismissal should be animated.
      * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
@@ -5568,11 +5552,20 @@
         if (mLastResumedActivity != null && r.mUserId != mLastResumedActivity.mUserId) {
             mAmInternal.sendForegroundProfileChanged(r.mUserId);
         }
+        final Task prevTask = mLastResumedActivity != null ? mLastResumedActivity.getTask() : null;
+
         updateResumedAppTrace(r);
         mLastResumedActivity = r;
 
         r.getDisplay().setFocusedApp(r, true);
 
+        if (prevTask == null || task != prevTask) {
+            if (prevTask != null) {
+                mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
+            }
+            mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+        }
+
         applyUpdateLockStateLocked(r);
         applyUpdateVrModeLocked(r);
 
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index f72020e..3f4e791 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -713,7 +713,11 @@
 
         final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
         final boolean showWallpaper = wallpaperTarget != null
-                && (wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+                && ((wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
+                // Update task open transition to wallpaper transition when wallpaper is visible.
+                // (i.e.launching app info activity from recent tasks)
+                || ((transit == TRANSIT_TASK_OPEN || transit == TRANSIT_TASK_TO_FRONT)
+                && mWallpaperControllerLocked.isWallpaperVisible()));
         // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
         // don't consider upgrading to wallpaper transition.
         final WindowState oldWallpaper =
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b3edc91..0d365b1 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -102,6 +102,11 @@
     }
 
     @Override
+    public String toString() {
+        return mName + "@" + System.identityHashCode(this);
+    }
+
+    @Override
     public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9e93e14..0ec0c7b 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-
 import android.content.res.Resources;
 import android.text.TextUtils;
 
@@ -43,7 +41,7 @@
     /**
      * The Tasks container. Tasks etc. are automatically added to this container.
      */
-    protected final TaskContainers mTaskContainers;
+    protected final DisplayArea<? extends ActivityStack> mTaskContainers;
 
     /**
      * Construct a new {@link DisplayAreaPolicy}
@@ -58,7 +56,8 @@
      */
     protected DisplayAreaPolicy(WindowManagerService wmService,
             DisplayContent content, DisplayArea.Root root,
-            DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
+            DisplayArea<? extends WindowContainer> imeContainer,
+            DisplayArea<? extends ActivityStack> taskContainers) {
         mWmService = wmService;
         mContent = content;
         mRoot = root;
@@ -83,67 +82,15 @@
      */
     public abstract void addWindow(WindowToken token);
 
-    /**
-     * Default policy that has no special features.
-     */
-    public static class Default extends DisplayAreaPolicy {
-
-        public Default(WindowManagerService wmService, DisplayContent content,
-                DisplayArea.Root root,
+    /** Provider for platform-default display area policy. */
+    static final class DefaultProvider implements DisplayAreaPolicy.Provider {
+        @Override
+        public DisplayAreaPolicy instantiate(WindowManagerService wmService,
+                DisplayContent content, DisplayArea.Root root,
                 DisplayArea<? extends WindowContainer> imeContainer,
                 TaskContainers taskContainers) {
-            super(wmService, content, root, imeContainer, taskContainers);
-        }
-
-        private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService,
-                DisplayArea.Type.BELOW_TASKS, "BelowTasks");
-        private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService,
-                DisplayArea.Type.ABOVE_TASKS, "AboveTasks");
-        private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService,
-                DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme");
-        private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService,
-                DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme");
-
-        @Override
-        public void attachDisplayAreas() {
-            mRoot.addChild(mBelow, 0);
-            mRoot.addChild(mTaskContainers, 1);
-            mRoot.addChild(mAbove, 2);
-
-            mAbove.addChild(mAboveBelowIme, 0);
-            mAbove.addChild(mImeContainer, 1);
-            mAbove.addChild(mAboveAboveIme, 2);
-        }
-
-        @Override
-        public void addWindow(WindowToken token) {
-            switch (DisplayArea.Type.typeOf(token)) {
-                case ABOVE_TASKS:
-                    if (token.getWindowLayerFromType()
-                            < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) {
-                        mAboveBelowIme.addChild(token);
-                    } else {
-                        mAboveAboveIme.addChild(token);
-                    }
-                    break;
-                case BELOW_TASKS:
-                    mBelow.addChild(token);
-                    break;
-                default:
-                    throw new IllegalArgumentException("don't know how to sort " + token);
-            }
-        }
-
-        /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */
-        static class Provider implements DisplayAreaPolicy.Provider {
-            @Override
-            public DisplayAreaPolicy instantiate(WindowManagerService wmService,
-                    DisplayContent content, DisplayArea.Root root,
-                    DisplayArea<? extends WindowContainer> imeContainer,
-                    TaskContainers taskContainers) {
-                return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer,
-                        taskContainers);
-            }
+            return new DisplayAreaPolicyBuilder()
+                    .build(wmService, content, root, imeContainer, taskContainers);
         }
     }
 
@@ -172,7 +119,7 @@
             String name = res.getString(
                     com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
             if (TextUtils.isEmpty(name)) {
-                return new DisplayAreaPolicy.Default.Provider();
+                return new DisplayAreaPolicy.DefaultProvider();
             }
             try {
                 return (Provider) Class.forName(name).newInstance();
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
new file mode 100644
index 0000000..885456a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder for instantiating a complex {@link DisplayAreaPolicy}
+ *
+ * <p>Given a set of features (that each target a set of window types), it builds the necessary
+ * DisplayArea hierarchy.
+ *
+ * <p>Example: <br />
+ *
+ * <pre>
+ *     // Feature for targeting everything below the magnification overlay:
+ *     new DisplayAreaPolicyBuilder(...)
+ *             .addFeature(new Feature.Builder(..., "Magnification")
+ *                     .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
+ *                     .build())
+ *             .build(...)
+ *
+ *     // Builds a policy with the following hierarchy:
+ *      - DisplayArea.Root
+ *        - Magnification
+ *          - DisplayArea.Tokens (Wallpapers are attached here)
+ *          - TaskContainers
+ *          - DisplayArea.Tokens (windows above Tasks up to IME are attached here)
+ *          - ImeContainers
+ *          - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here)
+ *        - DisplayArea.Tokens (TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY and up are attached here)
+ *
+ * </pre>
+ *
+ * // TODO(display-area): document more complex scenarios where we need multiple areas per feature.
+ */
+class DisplayAreaPolicyBuilder {
+
+    private final ArrayList<Feature> mFeatures = new ArrayList<>();
+
+    /**
+     * A feature that requires {@link DisplayArea DisplayArea(s)}.
+     */
+    static class Feature {
+        private final String mName;
+        private final boolean[] mWindowLayers;
+
+        private Feature(String name, boolean[] windowLayers) {
+            mName = name;
+            mWindowLayers = windowLayers;
+        }
+
+        static class Builder {
+            private final WindowManagerPolicy mPolicy;
+            private final String mName;
+            private final boolean[] mLayers;
+
+            /**
+             * Build a new feature that applies to a set of window types as specified by the builder
+             * methods.
+             *
+             * <p>The set of types is updated iteratively in the order of the method invocations.
+             * For example, {@code all().except(TYPE_STATUS_BAR)} expresses that a feature should
+             * apply to all types except TYPE_STATUS_BAR.
+             *
+             * The builder starts out with the feature not applying to any types.
+             *
+             * @param name the name of the feature.
+             */
+            Builder(WindowManagerPolicy policy, String name) {
+                mPolicy = policy;
+                mName = name;
+                mLayers = new boolean[mPolicy.getMaxWindowLayer()];
+            }
+
+            /**
+             * Set that the feature applies to all window types.
+             */
+            Builder all() {
+                Arrays.fill(mLayers, true);
+                return this;
+            }
+
+            /**
+             * Set that the feature applies to the given window types.
+             */
+            Builder and(int... types) {
+                for (int i = 0; i < types.length; i++) {
+                    int type = types[i];
+                    set(type, true);
+                }
+                return this;
+            }
+
+            /**
+             * Set that the feature does not apply to the given window types.
+             */
+            Builder except(int... types) {
+                for (int i = 0; i < types.length; i++) {
+                    int type = types[i];
+                    set(type, false);
+                }
+                return this;
+            }
+
+            /**
+             * Set that the feature applies window types that are layerd at or below the layer of
+             * the given window type.
+             */
+            Builder upTo(int typeInclusive) {
+                final int max = layerFromType(typeInclusive, false);
+                for (int i = 0; i < max; i++) {
+                    mLayers[i] = true;
+                }
+                set(typeInclusive, true);
+                return this;
+            }
+
+            Feature build() {
+                return new Feature(mName, mLayers.clone());
+            }
+
+            private void set(int type, boolean value) {
+                mLayers[layerFromType(type, true)] = value;
+                if (type == TYPE_APPLICATION_OVERLAY) {
+                    mLayers[layerFromType(type, true)] = value;
+                    mLayers[layerFromType(TYPE_SYSTEM_ALERT, false)] = value;
+                    mLayers[layerFromType(TYPE_SYSTEM_OVERLAY, false)] = value;
+                    mLayers[layerFromType(TYPE_SYSTEM_ERROR, false)] = value;
+                }
+            }
+
+            private int layerFromType(int type, boolean internalWindows) {
+                return mPolicy.getWindowLayerFromTypeLw(type, internalWindows);
+            }
+        }
+    }
+
+    static class Result extends DisplayAreaPolicy {
+        private static final int LEAF_TYPE_TASK_CONTAINERS = 1;
+        private static final int LEAF_TYPE_IME_CONTAINERS = 2;
+        private static final int LEAF_TYPE_TOKENS = 0;
+
+        private final int mMaxWindowLayer = mWmService.mPolicy.getMaxWindowLayer();
+
+        private final ArrayList<Feature> mFeatures;
+        private final Map<Feature, List<DisplayArea<? extends WindowContainer>>> mAreas;
+        private final DisplayArea.Tokens[] mAreaForLayer = new DisplayArea.Tokens[mMaxWindowLayer];
+
+        Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root,
+                DisplayArea<? extends WindowContainer> imeContainer,
+                DisplayArea<? extends ActivityStack> taskStacks, ArrayList<Feature> features) {
+            super(wmService, content, root, imeContainer, taskStacks);
+            mFeatures = features;
+            mAreas = new HashMap<>(features.size());
+            for (int i = 0; i < mFeatures.size(); i++) {
+                mAreas.put(mFeatures.get(i), new ArrayList<>());
+            }
+        }
+
+        @Override
+        public void attachDisplayAreas() {
+            // This method constructs the layer hierarchy with the following properties:
+            // (1) Every feature maps to a set of DisplayAreas
+            // (2) After adding a window, for every feature the window's type belongs to,
+            //     it is a descendant of one of the corresponding DisplayAreas of the feature.
+            // (3) Z-order is maintained, i.e. if z-range(area) denotes the set of layers of windows
+            //     within a DisplayArea:
+            //      for every pair of DisplayArea siblings (a,b), where a is below b, it holds that
+            //      max(z-range(a)) <= min(z-range(b))
+            //
+            // The algorithm below iteratively creates such a hierarchy:
+            //  - Initially, all windows are attached to the root.
+            //  - For each feature we create a set of DisplayAreas, by looping over the layers
+            //    - if the feature does apply to the current layer, we need to find a DisplayArea
+            //      for it to satisfy (2)
+            //      - we can re-use the previous layer's area if:
+            //         the current feature also applies to the previous layer, (to satisfy (3))
+            //         and the last feature that applied to the previous layer is the same as
+            //           the last feature that applied to the current layer (to satisfy (2))
+            //      - otherwise we create a new DisplayArea below the last feature that applied
+            //        to the current layer
+
+
+            PendingArea[] areaForLayer = new PendingArea[mMaxWindowLayer];
+            final PendingArea root = new PendingArea(null, 0, null);
+            Arrays.fill(areaForLayer, root);
+
+            final int size = mFeatures.size();
+            for (int i = 0; i < size; i++) {
+                PendingArea featureArea = null;
+                for (int layer = 0; layer < mMaxWindowLayer; layer++) {
+                    final Feature feature = mFeatures.get(i);
+                    if (feature.mWindowLayers[layer]) {
+                        if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {
+                            // No suitable DisplayArea - create a new one under the previous area
+                            // for this layer.
+                            featureArea = new PendingArea(feature, layer, areaForLayer[layer]);
+                            areaForLayer[layer].mChildren.add(featureArea);
+                        }
+                        areaForLayer[layer] = featureArea;
+                    } else {
+                        featureArea = null;
+                    }
+                }
+            }
+
+            PendingArea leafArea = null;
+            int leafType = LEAF_TYPE_TOKENS;
+            for (int layer = 0; layer < mMaxWindowLayer; layer++) {
+                int type = typeOfLayer(mWmService.mPolicy, layer);
+                if (leafArea == null || leafArea.mParent != areaForLayer[layer]
+                        || type != leafType) {
+                    leafArea = new PendingArea(null, layer, areaForLayer[layer]);
+                    areaForLayer[layer].mChildren.add(leafArea);
+                    leafType = type;
+                    if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
+                        leafArea.mExisting = mTaskContainers;
+                    } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
+                        leafArea.mExisting = mImeContainer;
+                    }
+                }
+                leafArea.mMaxLayer = layer;
+            }
+            root.computeMaxLayer();
+            root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas);
+        }
+
+        @Override
+        public void addWindow(WindowToken token) {
+            DisplayArea.Tokens area = findAreaForToken(token);
+            area.addChild(token);
+        }
+
+        @VisibleForTesting
+        DisplayArea.Tokens findAreaForToken(WindowToken token) {
+            int windowLayerFromType = token.getWindowLayerFromType();
+            if (windowLayerFromType == APPLICATION_LAYER) {
+                // TODO(display-area): Better handle AboveAppWindows in APPLICATION_LAYER
+                windowLayerFromType += 1;
+            } else if (token.mRoundedCornerOverlay) {
+                windowLayerFromType = mMaxWindowLayer - 1;
+            }
+            return mAreaForLayer[windowLayerFromType];
+        }
+
+        public List<DisplayArea<? extends WindowContainer>> getDisplayAreas(Feature feature) {
+            return mAreas.get(feature);
+        }
+
+        private static int typeOfLayer(WindowManagerPolicy policy, int layer) {
+            if (layer == APPLICATION_LAYER) {
+                return LEAF_TYPE_TASK_CONTAINERS;
+            } else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)
+                    || layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) {
+                return LEAF_TYPE_IME_CONTAINERS;
+            } else {
+                return LEAF_TYPE_TOKENS;
+            }
+        }
+    }
+
+    DisplayAreaPolicyBuilder addFeature(Feature feature) {
+        mFeatures.add(feature);
+        return this;
+    }
+
+    Result build(WindowManagerService wmService,
+            DisplayContent content, DisplayArea.Root root,
+            DisplayArea<? extends WindowContainer> imeContainer,
+            DisplayArea<? extends ActivityStack> taskContainers) {
+
+        return new Result(wmService, content, root, imeContainer, taskContainers, new ArrayList<>(
+                mFeatures));
+    }
+
+    static class PendingArea {
+        final int mMinLayer;
+        final ArrayList<PendingArea> mChildren = new ArrayList<>();
+        final Feature mFeature;
+        final PendingArea mParent;
+        int mMaxLayer;
+        DisplayArea mExisting;
+
+        PendingArea(Feature feature,
+                int minLayer,
+                PendingArea parent) {
+            mMinLayer = minLayer;
+            mFeature = feature;
+            mParent = parent;
+        }
+
+        int computeMaxLayer() {
+            for (int i = 0; i < mChildren.size(); i++) {
+                mMaxLayer = Math.max(mMaxLayer, mChildren.get(i).computeMaxLayer());
+            }
+            return mMaxLayer;
+        }
+
+        void instantiateChildren(DisplayArea<DisplayArea> parent,
+                DisplayArea.Tokens[] areaForLayer, int level, Map<Feature, List<DisplayArea<?
+                extends WindowContainer>>> areas) {
+            mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer));
+            for (int i = 0; i < mChildren.size(); i++) {
+                final PendingArea child = mChildren.get(i);
+                final DisplayArea area = child.createArea(parent, areaForLayer);
+                parent.addChild(area, WindowContainer.POSITION_TOP);
+                if (mFeature != null) {
+                    areas.get(mFeature).add(area);
+                }
+                child.instantiateChildren(area, areaForLayer, level + 1, areas);
+            }
+        }
+
+        private DisplayArea createArea(DisplayArea<DisplayArea> parent,
+                DisplayArea.Tokens[] areaForLayer) {
+            if (mExisting != null) {
+                return mExisting;
+            }
+            DisplayArea.Type type;
+            if (mMinLayer > APPLICATION_LAYER) {
+                type = DisplayArea.Type.ABOVE_TASKS;
+            } else if (mMaxLayer < APPLICATION_LAYER) {
+                type = DisplayArea.Type.BELOW_TASKS;
+            } else {
+                type = DisplayArea.Type.ANY;
+            }
+            if (mFeature == null) {
+                final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type,
+                        "Leaf:" + mMinLayer + ":" + mMaxLayer);
+                for (int i = mMinLayer; i <= mMaxLayer; i++) {
+                    areaForLayer[i] = leaf;
+                }
+                return leaf;
+            } else {
+                return new DisplayArea(parent.mWmService, type, mFeature.mName + ":"
+                        + mMinLayer + ":" + mMaxLayer);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3b658c0..55a41ab 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,9 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -32,6 +30,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -56,9 +55,6 @@
 import static android.view.View.GONE;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_TOP;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -84,9 +80,6 @@
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
-import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
-import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
-import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -105,12 +98,15 @@
 import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CONTROLLER;
 import static com.android.server.wm.DisplayContentProto.DPI;
 import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
+import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
 import static com.android.server.wm.DisplayContentProto.ID;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
 import static com.android.server.wm.DisplayContentProto.ROTATION;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
 import static com.android.server.wm.DisplayContentProto.TASKS;
 import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -1938,6 +1934,20 @@
         return mTaskContainers.getRootHomeTask();
     }
 
+    /**
+     * Returns the existing home stack or creates and returns a new one if it should exist for the
+     * display.
+     */
+    @Nullable
+    ActivityStack getOrCreateRootHomeTask() {
+        ActivityStack homeTask = getRootHomeTask();
+        if (homeTask == null && supportsSystemDecorations() && !isUntrustedVirtualDisplay()) {
+            homeTask = createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
+                    false /* onTop */);
+        }
+        return homeTask;
+    }
+
     /** @return The primary split-screen task, and {@code null} otherwise. */
     ActivityStack getRootSplitScreenPrimaryTask() {
         return mTaskContainers.getRootSplitScreenPrimaryTask();
@@ -2613,54 +2623,9 @@
 
     void adjustForImeIfNeeded() {
         final WindowState imeWin = mInputMethodWindow;
-        final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
-                && !mDividerControllerLocked.isImeHideRequested();
-        final ActivityStack dockedStack = getRootSplitScreenPrimaryTask();
-        final boolean dockVisible = dockedStack != null;
-        final Task topDockedTask = dockVisible ? dockedStack.getTask((t) -> true): null;
-        final ActivityStack imeTargetStack = mWmService.getImeFocusStackLocked();
-        final int imeDockSide = (dockVisible && imeTargetStack != null) ?
-                imeTargetStack.getDockSide() : DOCKED_INVALID;
-        final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
-        final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
+        final boolean imeVisible = imeWin != null && imeWin.isVisibleLw()
+                && imeWin.isDisplayedLw();
         final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight();
-        final boolean imeHeightChanged = imeVisible &&
-                imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor();
-
-        // This includes a case where the docked stack is unminimizing and IME is visible for the
-        // bottom side stack. The condition prevents adjusting the override task bounds for IME to
-        // the minimized docked stack bounds.
-        final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock()
-                || (topDockedTask != null && imeOnBottom && !dockedStack.isAdjustedForIme()
-                && dockedStack.getBounds().height() < topDockedTask.getBounds().height());
-
-        // The divider could be adjusted for IME position, or be thinner than usual,
-        // or both. There are three possible cases:
-        // - If IME is visible, and focus is on top, divider is not moved for IME but thinner.
-        // - If IME is visible, and focus is on bottom, divider is moved for IME and thinner.
-        // - If IME is not visible, divider is not moved and is normal width.
-
-        if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
-            for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = mTaskContainers.getChildAt(i);
-                final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
-                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
-                        && stack.inSplitScreenWindowingMode()) {
-                    stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
-                } else {
-                    stack.resetAdjustedForIme(false);
-                }
-            }
-            mDividerControllerLocked.setAdjustedForIme(
-                    imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
-        } else {
-            for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = mTaskContainers.getChildAt(i);
-                stack.resetAdjustedForIme(!dockVisible);
-            }
-            mDividerControllerLocked.setAdjustedForIme(
-                    false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
-        }
         mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
     }
 
@@ -3255,9 +3220,9 @@
             mWmService.mAtmInternal.onImeWindowSetOnDisplay(imePid,
                     mInputMethodWindow.getDisplayId());
         }
-        computeImeTarget(true /* updateImeTarget */);
         mInsetsStateController.getSourceProvider(ITYPE_IME).setWindow(win,
                 null /* frameProvider */, null /* imeFrameProvider */);
+        computeImeTarget(true /* updateImeTarget */);
     }
 
     /**
@@ -3440,8 +3405,6 @@
         mInputMethodTarget = target;
         mInputMethodTargetWaitingAnim = targetWaitingAnim;
         assignWindowLayers(false /* setLayoutNeeded */);
-        mInputMethodControlTarget = computeImeControlTarget();
-        mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget);
         updateImeParent();
     }
 
@@ -3452,7 +3415,7 @@
      *
      * @see #getImeControlTarget()
      */
-    void updateImeControlTarget(WindowState target) {
+    void updateImeControlTarget(InsetsControlTarget target) {
         mInputMethodControlTarget = target;
         mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget);
     }
@@ -3490,13 +3453,13 @@
      * Computes which control-target the IME should be attached to.
      */
     @VisibleForTesting
-    InsetsControlTarget computeImeControlTarget() {
+    InsetsControlTarget computeImeControlTarget(InsetsControlTarget appTarget) {
         if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) {
             return mRemoteInsetsControlTarget;
         }
 
         // Otherwise, we just use the ime target
-        return mInputMethodTarget;
+        return appTarget;
     }
 
     void setLayoutNeeded() {
@@ -3817,6 +3780,10 @@
         }
         prepareSurfaces();
 
+        // This should be called after the insets have been dispatched to clients and we have
+        // committed finish drawing windows.
+        mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
+
         mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
         mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
                 mLastHasContent,
@@ -3880,7 +3847,7 @@
         }
     }
 
-    /** @returns the orientation of the display when it's rotation is ROTATION_0. */
+    /** @return the orientation of the display when it's rotation is ROTATION_0. */
     int getNaturalOrientation() {
         return mBaseDisplayWidth < mBaseDisplayHeight
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
@@ -4255,9 +4222,6 @@
         }
 
         ActivityStack getRootHomeTask() {
-            if (mRootHomeTask == null && mDisplayId == DEFAULT_DISPLAY) {
-                Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
-            }
             return mRootHomeTask;
         }
 
@@ -4324,8 +4288,6 @@
                     }
                 } else {
                     mRootSplitScreenPrimaryTask = stack;
-                    mDisplayContent.onSplitScreenModeActivated();
-                    mDividerControllerLocked.notifyDockedStackExistsChanged(true);
                 }
             }
         }
@@ -4337,11 +4299,6 @@
                 mRootPinnedTask = null;
             } else if (stack == mRootSplitScreenPrimaryTask) {
                 mRootSplitScreenPrimaryTask = null;
-                mDisplayContent.onSplitScreenModeDismissed();
-                // Re-set the split-screen create mode whenever the split-screen stack is removed.
-                mWmService.setDockedStackCreateStateLocked(
-                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
-                mDividerControllerLocked.notifyDockedStackExistsChanged(false);
             }
         }
 
@@ -5757,6 +5714,33 @@
         return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent);
     }
 
+    /** @return the tile to create the next stack in. */
+    private TaskTile updateLaunchTile(int windowingMode) {
+        if (!isSplitScreenWindowingMode(windowingMode)) {
+            // Only split-screen windowing modes interact with tiles.
+            return null;
+        }
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final TaskTile t = getStackAt(i).asTile();
+            if (t == null || t.getRequestedOverrideWindowingMode() != windowingMode) {
+                continue;
+            }
+            // If not already set, pick a launch tile which is not the one we are launching
+            // into.
+            if (mLaunchTile == null) {
+                for (int j = 0, n = getStackCount(); j < n; ++j) {
+                    TaskTile tt = getStackAt(j).asTile();
+                    if (tt != t) {
+                        mLaunchTile = tt;
+                        break;
+                    }
+                }
+            }
+            return t;
+        }
+        return mLaunchTile;
+    }
+
     @VisibleForTesting
     ActivityStack createStackUnchecked(int windowingMode, int activityType,
             int stackId, boolean onTop, ActivityInfo info, Intent intent) {
@@ -5769,13 +5753,20 @@
             info.applicationInfo = new ApplicationInfo();
         }
 
+        TaskTile tile = updateLaunchTile(windowingMode);
+        if (tile != null) {
+            // Since this stack will be put into a tile, its windowingMode will be inherited.
+            windowingMode = WINDOWING_MODE_UNDEFINED;
+        }
         final ActivityStack stack = new ActivityStack(this, stackId,
                 mRootWindowContainer.mStackSupervisor, activityType, info, intent);
         addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
         stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
                 true /* creating */);
-
+        if (tile != null) {
+            tile.addChild(stack, 0 /* index */);
+        }
         return stack;
     }
 
@@ -5995,20 +5986,19 @@
     void onSplitScreenModeDismissed() {
         mAtmService.deferWindowLayout();
         try {
-            // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
+            mLaunchTile = null;
             for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack otherStack = getStackAt(i);
-                if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
-                    continue;
+                final TaskTile t = getStackAt(i).asTile();
+                if (t != null) {
+                    t.removeAllChildren();
                 }
-                otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */,
-                        false /* showRecents */, false /* enteringSplitScreenMode */,
-                        true /* deferEnsuringVisibility */, false /* creating */);
             }
+            mDividerControllerLocked.setMinimizedDockedStack(false /* minimized */,
+                    false /* animate */);
         } finally {
             final ActivityStack topFullscreenStack =
                     getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-            final ActivityStack homeStack = getRootHomeTask();
+            final ActivityStack homeStack = getOrCreateRootHomeTask();
             if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
                 // Whenever split-screen is dismissed we want the home stack directly behind the
                 // current top fullscreen stack so it shows up when the top stack is finished.
@@ -6022,27 +6012,6 @@
         }
     }
 
-    void onSplitScreenModeActivated() {
-        mAtmService.deferWindowLayout();
-        try {
-            // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
-            final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask();
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack otherStack = getStackAt(i);
-                if (otherStack == splitScreenPrimaryStack
-                        || !otherStack.affectedBySplitScreenResize()) {
-                    continue;
-                }
-                otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
-                        false /* animate */, false /* showRecents */,
-                        true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
-                        false /* creating */);
-            }
-        } finally {
-            mAtmService.continueWindowLayout();
-        }
-    }
-
     /**
      * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
      * @param windowingMode The windowing mode we are checking support for.
@@ -6574,7 +6543,7 @@
     }
 
     void moveHomeStackToFront(String reason) {
-        final ActivityStack homeStack = getRootHomeTask();
+        final ActivityStack homeStack = getOrCreateRootHomeTask();
         if (homeStack != null) {
             homeStack.moveToFront(reason);
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ca6bd2d..d719dbe3 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -24,7 +24,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
-import static android.view.Display.TYPE_BUILT_IN;
+import static android.view.Display.TYPE_INTERNAL;
 import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
@@ -145,7 +145,6 @@
 import android.util.Slog;
 import android.view.DisplayCutout;
 import android.view.Gravity;
-import android.view.IApplicationToken;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -333,8 +332,6 @@
     private WindowState mFocusedWindow;
     private WindowState mLastFocusedWindow;
 
-    IApplicationToken mFocusedApp;
-
     // The states of decor windows from the last layout. These are used to generate another display
     // layout in different bounds but with the same states.
     private boolean mLastNavVisible;
@@ -2986,11 +2983,12 @@
 
     /**
      * Return corner radius in pixels that should be used on windows in order to cover the display.
-     * The radius is only valid for built-in displays since the one who configures window corner
-     * radius cannot know the corner radius of non-built-in display.
+     *
+     * The radius is only valid for internal displays, since the corner radius of external displays
+     * is not known at build time when window corners are configured.
      */
     float getWindowCornerRadius() {
-        return mDisplayContent.getDisplay().getType() == TYPE_BUILT_IN
+        return mDisplayContent.getDisplay().getType() == TYPE_INTERNAL
                 ? ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()) : 0f;
     }
 
@@ -3287,7 +3285,6 @@
                 && mLastBehavior == behavior
                 && mLastFocusIsFullscreen == isFullscreen
                 && mLastFocusIsImmersive == isImmersive
-                && mFocusedApp == win.getAppToken()
                 && mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
                 && mLastDockedStackBounds.equals(mDockedStackBounds)) {
             return 0;
@@ -3304,7 +3301,6 @@
         mLastBehavior = behavior;
         mLastFocusIsFullscreen = isFullscreen;
         mLastFocusIsImmersive = isImmersive;
-        mFocusedApp = win.getAppToken();
         mLastNonDockedStackBounds.set(mNonDockedStackBounds);
         mLastDockedStackBounds.set(mDockedStackBounds);
         final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
@@ -3803,9 +3799,6 @@
         if (mFocusedWindow != null) {
             pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
         }
-        if (mFocusedApp != null) {
-            pw.print(prefix); pw.print("mFocusedApp="); pw.println(mFocusedApp);
-        }
         if (mTopFullscreenOpaqueWindowState != null) {
             pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
             pw.println(mTopFullscreenOpaqueWindowState);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index e71371a..539853b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -525,9 +525,15 @@
             }
             mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
             mIsWaitingForRemoteRotation = false;
-            mDisplayContent.sendNewConfiguration();
-            if (t != null) {
-                mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
+            mService.mAtmService.deferWindowLayout();
+            try {
+                mDisplayContent.sendNewConfiguration();
+                if (t != null) {
+                    mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t,
+                            null /* organizer */);
+                }
+            } finally {
+                mService.mAtmService.continueWindowLayout();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 872379e..6431e11 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -745,7 +745,7 @@
      * @param minimizedDock Whether the docked stack is currently minimized.
      * @param animate Whether to animate the change.
      */
-    private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
+    void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
         final boolean wasMinimized = mMinimizedDock;
         mMinimizedDock = minimizedDock;
         if (minimizedDock == wasMinimized) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 3ff1b95..370d125 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -39,13 +39,6 @@
     }
 
     /**
-     * Called when Insets have been dispatched to client. This gets called just after onPostLayout.
-     */
-    void onPostInsetsDispatched() {
-        checkShowImePostLayout();
-    }
-
-    /**
      * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
      * requests to show IME on {@param imeTarget}.
      *
@@ -80,7 +73,7 @@
         };
     }
 
-    private void checkShowImePostLayout() {
+    void checkShowImePostLayout() {
         // check if IME is drawn
         if (mIsImeLayoutDrawn
                 || (mImeTargetFromIme != null
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 2d7d3f1..a8fe349 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -168,7 +168,6 @@
             mLastState.set(mState, true /* copySources */);
             notifyInsetsChanged();
         }
-        getImeSourceProvider().onPostInsetsDispatched();
     }
 
     void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 00947d7..44034ed 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -31,14 +31,14 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_OCCLUDED_STATES;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING;
 import static com.android.server.wm.KeyguardOccludedProto.DISPLAY_ID;
 import static com.android.server.wm.KeyguardOccludedProto.KEYGUARD_OCCLUDED;
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -411,8 +411,7 @@
             if (stack == null) {
                 return;
             }
-            mStackSupervisor.moveTasksToFullscreenStackLocked(stack,
-                    stack.isFocusedStackOnDisplay());
+            mRootWindowContainer.getDefaultDisplay().onSplitScreenModeDismissed();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 944e0ae..634d7e9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
@@ -414,12 +413,7 @@
         }
 
         // Save the minimized home height
-        final ActivityStack dockedStack =
-                mDisplayContent.getRootSplitScreenPrimaryTask();
-        mDisplayContent.getDockedDividerController().getHomeStackBoundsInDockedMode(
-                mDisplayContent.getConfiguration(),
-                dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide(),
-                mMinimizedHomeBounds);
+        mMinimizedHomeBounds = mDisplayContent.getRootHomeTask().getBounds();
 
         mService.mWindowPlacerLocked.performSurfacePlacement();
 
@@ -748,8 +742,10 @@
     }
 
     boolean isWallpaperVisible(WindowState w) {
-        return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
-                && mTargetActivityRecord == w.mActivityRecord && isTargetOverWallpaper();
+        return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION &&
+                ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord)
+                        || isAnimatingTask(w.getTask()))
+                && isTargetOverWallpaper();
     }
 
     /**
@@ -842,8 +838,8 @@
             mTask = task;
             mIsRecentTaskInvisible = isRecentTaskInvisible;
             final WindowContainer container = mTask.getParent();
-            container.getRelativeDisplayedPosition(mPosition);
             mBounds.set(container.getDisplayedBounds());
+            mPosition.set(mBounds.left, mBounds.top);
         }
 
         RemoteAnimationTarget createRemoteAnimationTarget() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2196d89..2596452 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1998,8 +1998,6 @@
         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
         mUserStackInFront.put(mCurrentUser, focusStackId);
-        final int restoreStackId =
-                mUserStackInFront.get(userId, getDefaultDisplay().getRootHomeTask().getRootTaskId());
         mCurrentUser = userId;
 
         mStackSupervisor.mStartingUsers.add(uss);
@@ -2008,16 +2006,13 @@
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.switchUser(userId);
-                Task task = stack.getTopMostTask();
-                if (task != null && task != stack) {
-                    stack.positionChildAtTop(task);
-                }
             }
         }
 
+        final int restoreStackId = mUserStackInFront.get(userId);
         ActivityStack stack = getStack(restoreStackId);
         if (stack == null) {
-            stack = getDefaultDisplay().getRootHomeTask();
+            stack = getDefaultDisplay().getOrCreateRootHomeTask();
         }
         final boolean homeInFront = stack.isActivityTypeHome();
         if (stack.isOnHomeDisplay()) {
@@ -2039,8 +2034,11 @@
      */
     void updateUserStack(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
-            mUserStackInFront.put(userId, stack != null ? stack.getRootTaskId()
-                    : getDefaultDisplay().getRootHomeTask().getRootTaskId());
+            if (stack == null) {
+                stack = getDefaultDisplay().getOrCreateRootHomeTask();
+            }
+
+            mUserStackInFront.put(userId, stack.getRootTaskId());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0aed691..c7f2cc7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,6 +199,7 @@
     static final int INVALID_MIN_SIZE = -1;
     private float mShadowRadius = 0;
     private final Rect mLastSurfaceCrop = new Rect();
+    private static final boolean ENABLE_FREEFORM_COMPOSITOR_SHADOWS = false;
 
     /**
      * The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -2144,12 +2145,6 @@
                     // For floating tasks, calculate the smallest width from the bounds of the task
                     inOutConfig.smallestScreenWidthDp = (int) (
                             Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
-                } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
-                    // Iterating across all screen orientations, and return the minimum of the task
-                    // width taking into account that the bounds might change because the snap
-                    // algorithm snaps to a different value
-                    inOutConfig.smallestScreenWidthDp =
-                            getSmallestScreenWidthDpForDockedBounds(mTmpFullBounds);
                 }
                 // otherwise, it will just inherit
             }
@@ -2553,8 +2548,10 @@
     }
 
     private void updateSurfaceCrop() {
+        // TODO(b/149585281) remove when root task has the correct bounds for freeform
         // Only update the crop if we are drawing shadows on the task.
-        if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor) {
+        if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor
+                || !isRootTask() || !ENABLE_FREEFORM_COMPOSITOR_SHADOWS) {
             return;
         }
 
@@ -2978,8 +2975,14 @@
     }
 
     @Override
+    void onSurfaceShown(SurfaceControl.Transaction t) {
+        super.onSurfaceShown(t);
+        t.unsetColor(mSurfaceControl);
+    }
+
+    @Override
     SurfaceControl.Builder makeSurface() {
-        return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
+        return super.makeSurface().setColorLayer().setMetadata(METADATA_TASK_ID, mTaskId);
     }
 
     boolean isTaskAnimating() {
@@ -3248,6 +3251,10 @@
         return this;
     }
 
+    TaskTile asTile() {
+        return null;
+    }
+
     // TODO(task-merge): Figure-out how this should work with hierarchy tasks.
     boolean shouldBeVisible(ActivityRecord starting) {
         return true;
@@ -3934,7 +3941,8 @@
             return dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP,
                     mDisplayContent.getDisplayMetrics());
         }
-        if (inFreeformWindowingMode()) {
+        // TODO(b/149585281) remove when root task has the correct bounds for freeform
+        if (ENABLE_FREEFORM_COMPOSITOR_SHADOWS && inFreeformWindowingMode()) {
             final int elevation = taskIsFocused
                     ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
             return dipToPixel(elevation, mDisplayContent.getDisplayMetrics());
@@ -3949,7 +3957,7 @@
      */
     private void updateShadowsRadius(boolean taskIsFocused,
             SurfaceControl.Transaction pendingTransaction) {
-        if (!mWmService.mRenderShadowsInCompositor) return;
+        if (!mWmService.mRenderShadowsInCompositor || !isRootTask()) return;
 
         final float newShadowRadius = getShadowRadius(taskIsFocused);
         if (mShadowRadius != newShadowRadius) {
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 2dde0ba..f715d8f 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -59,6 +59,7 @@
     private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 24;
     private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 25;
     private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 26;
+    private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 27;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -179,6 +180,10 @@
         l.onRecentTaskListFrozenChanged(m.arg1 != 0);
     };
 
+    private final TaskStackConsumer mNotifyTaskFocusChanged = (l, m) -> {
+        l.onTaskFocusChanged(m.arg1, m.arg2 != 0);
+    };
+
     @FunctionalInterface
     public interface TaskStackConsumer {
         void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -273,6 +278,9 @@
                 case NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG:
                     forAllRemoteListeners(mNotifyTaskListFrozen, msg);
                     break;
+                case NOTIFY_TASK_FOCUS_CHANGED_MSG:
+                    forAllRemoteListeners(mNotifyTaskFocusChanged, msg);
+                    break;
             }
         }
     }
@@ -565,4 +573,12 @@
         forAllLocalListeners(mNotifyTaskListFrozen, msg);
         msg.sendToTarget();
     }
+
+    /** @see ITaskStackListener#onTaskFocusChanged(int, boolean) */
+    void notifyTaskFocusChanged(int taskId, boolean focused) {
+        final Message msg = mHandler.obtainMessage(NOTIFY_TASK_FOCUS_CHANGED_MSG,
+                taskId, focused ? 1 : 0);
+        forAllLocalListeners(mNotifyTaskFocusChanged, msg);
+        msg.sendToTarget();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 4b13a0c..4d5621c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -492,6 +492,10 @@
         if (!(container instanceof Task)) {
             throw new IllegalArgumentException("Invalid container in hierarchy op");
         }
+        if (container.getDisplayContent() == null) {
+            Slog.w(TAG, "Container is no longer attached: " + container);
+            return 0;
+        }
         if (hop.isReparent()) {
             // special case for tiles since they are "virtual" parents
             if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java
index 369db05..74d5c33 100644
--- a/services/core/java/com/android/server/wm/TaskTile.java
+++ b/services/core/java/com/android/server/wm/TaskTile.java
@@ -31,7 +31,6 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.Slog;
-import android.view.SurfaceControl;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -78,30 +77,9 @@
         // Virtual parent, so don't notify children.
     }
 
-    /**
-     * If there is a disconnection, this will clean up any vestigial surfaces left on the tile
-     * leash by moving known children to a new surfacecontrol and then removing the old one.
-     */
-    void cleanupSurfaces() {
-        if (mSurfaceControl == null) {
-            return;
-        }
-        SurfaceControl oldSurface = mSurfaceControl;
-        WindowContainer parentWin = getParent();
-        if (parentWin == null) {
-            return;
-        }
-        mSurfaceControl = parentWin.makeChildSurface(null).setName("TaskTile " + mTaskId + " - "
-                    + getRequestedOverrideWindowingMode()).setContainerLayer().build();
-        SurfaceControl.Transaction t = parentWin.getPendingTransaction();
-        t.show(mSurfaceControl);
-        for (int i = 0; i < mChildren.size(); ++i) {
-            if (mChildren.get(i).getSurfaceControl() == null) {
-                continue;
-            }
-            mChildren.get(i).reparentSurfaceControl(t, mSurfaceControl);
-        }
-        t.remove(oldSurface);
+    @Override
+    TaskTile asTile() {
+        return this;
     }
 
     @Override
@@ -215,6 +193,12 @@
         super.removeImmediately();
     }
 
+    @Override
+    void taskOrganizerDied() {
+        super.taskOrganizerDied();
+        removeImmediately();
+    }
+
     static TaskTile forToken(IBinder token) {
         try {
             return (TaskTile) ((TaskToken) token).getContainer();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0bb4e03..294c36a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -365,6 +365,7 @@
             // build a surface.
             setSurfaceControl(makeSurface().build());
             getPendingTransaction().show(mSurfaceControl);
+            onSurfaceShown(getPendingTransaction());
             updateSurfacePosition();
         } else {
             // If we have a surface but a new parent, we just need to perform a reparent. Go through
@@ -383,6 +384,13 @@
         scheduleAnimation();
     }
 
+    /**
+     * Called when the surface is shown for the first time.
+     */
+    void onSurfaceShown(Transaction t) {
+        // do nothing
+    }
+
     // Temp. holders for a chain of containers we are currently processing.
     private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
     private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
@@ -1908,7 +1916,7 @@
 
     @Override
     public Builder makeAnimationLeash() {
-        return makeSurface();
+        return makeSurface().setContainerLayer();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8f9caea..e85261d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1336,7 +1336,10 @@
             DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
             InsetsState outInsetsState) {
         int[] appOp = new int[1];
-        int res = mPolicy.checkAddPermission(attrs, appOp);
+        final boolean isRoundedCornerOverlay = (attrs.privateFlags
+                & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
+        int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
+                appOp);
         if (res != WindowManagerGlobal.ADD_OKAY) {
             return res;
         }
@@ -1410,8 +1413,6 @@
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
                 final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
-                final boolean isRoundedCornerOverlay =
-                        (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
                 token = new WindowToken(this, binder, type, false, displayContent,
                         session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
             } else if (rootType >= FIRST_APPLICATION_WINDOW
@@ -2560,10 +2561,8 @@
         final boolean callerCanManageAppTokens =
                 checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
         if (!callerCanManageAppTokens) {
-            // TODO(window-context): refactor checkAddPermission to not take attrs.
-            LayoutParams attrs = new LayoutParams(type);
-            attrs.packageName = packageName;
-            final int res = mPolicy.checkAddPermission(attrs, new int[1]);
+            final int res = mPolicy.checkAddPermission(type, false /* isRoundedCornerOverlay */,
+                    packageName, new int[1]);
             if (res != ADD_OKAY) {
                 return res;
             }
@@ -7317,9 +7316,13 @@
                 Slog.w(TAG_WM, "updateInputMethodTargetWindow: imeToken=" + imeToken
                         + " imeTargetWindowToken=" + imeTargetWindowToken);
             }
-            final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
-            if (imeTarget != null) {
-                imeTarget.getDisplayContent().updateImeControlTarget(imeTarget);
+            synchronized (mGlobalLock) {
+                final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
+                if (imeTarget != null) {
+                    InsetsControlTarget controlTarget =
+                            imeTarget.getDisplayContent().computeImeControlTarget(imeTarget);
+                    imeTarget.getDisplayContent().updateImeControlTarget(controlTarget);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b336f8d..ed4e684 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1493,7 +1493,8 @@
         // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
         // associate them with some stack to enable dimming.
         final DisplayContent dc = getDisplayContent();
-        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null;
+        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null
+                ? dc.getOrCreateRootHomeTask() : null;
     }
 
     /**
@@ -2661,18 +2662,6 @@
                             mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
                         }
                         win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
-                        if (win.mAttrs.type == TYPE_DOCK_DIVIDER) {
-                            // The owner of the docked divider died :( We reset the docked stack,
-                            // just in case they have the divider at an unstable position. Better
-                            // also reset drag resizing state, because the owner can't do it
-                            // anymore.
-                            final ActivityStack stack =
-                                    dc.getRootSplitScreenPrimaryTask();
-                            if (stack != null) {
-                                stack.resetDockedStackToMiddle();
-                            }
-                            resetSplitScreenResizing = true;
-                        }
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
                         WindowState.this.removeIfPossible();
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index d0eaa48..e1615af 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -645,10 +645,12 @@
     Return<void> gnssSvStatusCb_2_0(const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList) override {
         return gnssSvStatusCbImpl(svInfoList);
     }
+
+    // New in 2.1
     Return<void> gnssSvStatusCb_2_1(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList) override {
         return gnssSvStatusCbImpl(svInfoList);
     }
-    Return<void> gnssSetCapabilitesCbImpl(uint32_t capabilities, bool hasSubHalCapabilityFlags);
+    Return<void> gnssSetCapabilitiesCb_2_1(uint32_t capabilities) override;
 
     // TODO: Reconsider allocation cost vs threadsafety on these statics
     static const char* sNmeaString;
@@ -851,6 +853,10 @@
     return GnssCallback::gnssSetCapabilitesCb(capabilities);
 }
 
+Return<void> GnssCallback::gnssSetCapabilitiesCb_2_1(uint32_t capabilities) {
+    return GnssCallback::gnssSetCapabilitesCb(capabilities);
+}
+
 Return<void> GnssCallback::gnssAcquireWakelockCb() {
     acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
     return Void();
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 7e6f79f..70a9c09 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -18,18 +18,27 @@
 #define LOG_TAG "PackageManagerShellCommandDataLoader-jni"
 #include <android-base/logging.h>
 
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
-#include <nativehelper/JNIHelp.h>
-#include "android-base/file.h"
+#include <cutils/trace.h>
+#include <sys/eventfd.h>
+#include <sys/poll.h>
 
-#include <endian.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <core_jni_helpers.h>
+#include <endian.h>
 
 #include "dataloader.h"
 
+#include <charconv>
 #include <chrono>
+#include <span>
+#include <string>
 #include <thread>
+#include <unordered_map>
+#include <unordered_set>
 
 namespace android {
 
@@ -39,9 +48,26 @@
 using android::base::ReadFully;
 using android::base::unique_fd;
 
+using namespace std::literals;
+
+using BlockSize = int16_t;
+using FileIdx = int16_t;
+using BlockIdx = int32_t;
+using NumBlocks = int32_t;
+using CompressionType = int16_t;
+using RequestType = int16_t;
+using MagicType = uint32_t;
+
 static constexpr int BUFFER_SIZE = 256 * 1024;
 static constexpr int BLOCKS_COUNT = BUFFER_SIZE / INCFS_DATA_FILE_BLOCK_SIZE;
 
+static constexpr int COMMAND_SIZE = 4 + 2 + 2 + 4; // bytes
+static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2;  // bytes
+static constexpr std::string_view OKAY = "OKAY"sv;
+static constexpr MagicType INCR = 0x52434e49; // BE INCR
+
+static constexpr auto PollTimeoutMs = 5000;
+
 struct JniIds {
     jclass packageManagerShellCommandDataLoader;
     jmethodID pmscdLookupShellCommand;
@@ -85,6 +111,70 @@
     return ids;
 }
 
+struct BlockHeader {
+    FileIdx fileIdx = -1;
+    CompressionType compressionType = -1;
+    BlockIdx blockIdx = -1;
+    BlockSize blockSize = -1;
+} __attribute__((packed));
+
+static_assert(sizeof(BlockHeader) == HEADER_SIZE);
+
+static constexpr RequestType EXIT = 0;
+static constexpr RequestType BLOCK_MISSING = 1;
+static constexpr RequestType PREFETCH = 2;
+
+struct RequestCommand {
+    MagicType magic;
+    RequestType requestType;
+    FileIdx fileIdx;
+    BlockIdx blockIdx;
+} __attribute__((packed));
+
+static_assert(COMMAND_SIZE == sizeof(RequestCommand));
+
+static bool sendRequest(int fd, RequestType requestType, FileIdx fileIdx = -1,
+                        BlockIdx blockIdx = -1) {
+    const RequestCommand command{.magic = INCR,
+                                 .requestType = static_cast<int16_t>(be16toh(requestType)),
+                                 .fileIdx = static_cast<int16_t>(be16toh(fileIdx)),
+                                 .blockIdx = static_cast<int32_t>(be32toh(blockIdx))};
+    return android::base::WriteFully(fd, &command, sizeof(command));
+}
+
+static int waitForDataOrSignal(int fd, int event_fd) {
+    struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}};
+    // Wait indefinitely until either data is ready or stop signal is received
+    int res = poll(pfds, 2, PollTimeoutMs);
+    if (res <= 0) {
+        return res;
+    }
+    // First check if there is a stop signal
+    if (pfds[1].revents == POLLIN) {
+        return event_fd;
+    }
+    // Otherwise check if incoming data is ready
+    if (pfds[0].revents == POLLIN) {
+        return fd;
+    }
+    return -1;
+}
+
+static bool readChunk(int fd, std::vector<uint8_t>& data) {
+    int32_t size;
+    if (!android::base::ReadFully(fd, &size, sizeof(size))) {
+        return false;
+    }
+    size = int32_t(be32toh(size));
+    if (size <= 0) {
+        return false;
+    }
+    data.resize(size);
+    return android::base::ReadFully(fd, data.data(), data.size());
+}
+
+BlockHeader readHeader(std::span<uint8_t>& data);
+
 static inline int32_t readBEInt32(borrowed_fd fd) {
     int32_t result;
     ReadFully(fd, &result, sizeof(result));
@@ -106,6 +196,22 @@
     return readBEInt32(fd); // size of the verity tree
 }
 
+static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
+    constexpr int SHA256_DIGEST_SIZE = 32;
+    constexpr int digest_size = SHA256_DIGEST_SIZE;
+    constexpr int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size;
+
+    IncFsSize total_tree_block_count = 0;
+
+    auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
+    auto hash_block_count = block_count;
+    for (auto i = 0; hash_block_count > 1; i++) {
+        hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
+        total_tree_block_count += hash_block_count;
+    }
+    return total_tree_block_count * INCFS_DATA_FILE_BLOCK_SIZE;
+}
+
 static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, jobject pfd) {
     if (!pfd) {
         ALOGE("Missing In ParcelFileDescriptor.");
@@ -125,8 +231,9 @@
 struct InputDesc {
     unique_fd fd;
     IncFsSize size;
-    IncFsBlockKind kind;
-    bool waitOnEof;
+    IncFsBlockKind kind = INCFS_BLOCK_KIND_DATA;
+    bool waitOnEof = false;
+    bool streaming = false;
 };
 using InputDescs = std::vector<InputDesc>;
 
@@ -135,8 +242,7 @@
     InputDescs result;
     result.reserve(2);
 
-    const bool fromStdin = (metadata.size == 0 || *metadata.data == '-');
-    if (fromStdin) {
+    if (metadata.size == 0 || *metadata.data == '-') {
         // stdin
         auto fd = convertPfdToFdAndDup(
                 env, jni,
@@ -146,12 +252,29 @@
             result.push_back(InputDesc{
                     .fd = std::move(fd),
                     .size = size,
-                    .kind = INCFS_BLOCK_KIND_DATA,
                     .waitOnEof = true,
             });
         }
         return result;
     }
+    if (*metadata.data == '+') {
+        // verity tree from stdin, rest is streaming
+        auto fd = convertPfdToFdAndDup(
+                env, jni,
+                env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
+                                            jni.pmscdGetStdInPFD, shellCommand));
+        if (fd.ok()) {
+            auto treeSize = verityTreeSizeForFile(size);
+            result.push_back(InputDesc{
+                    .fd = std::move(fd),
+                    .size = treeSize,
+                    .kind = INCFS_BLOCK_KIND_HASH,
+                    .waitOnEof = true,
+                    .streaming = true,
+            });
+        }
+        return result;
+    }
 
     // local file and possibly signature
     const std::string filePath(metadata.data, metadata.size);
@@ -163,13 +286,17 @@
                                         jni.pmscdGetLocalFile, shellCommand,
                                         env->NewStringUTF(idsigPath.c_str())));
     if (idsigFd.ok()) {
-        ALOGE("idsig found, skipping to the tree");
-        auto treeSize = skipIdSigHeaders(idsigFd);
+        auto treeSize = verityTreeSizeForFile(size);
+        auto actualTreeSize = skipIdSigHeaders(idsigFd);
+        if (treeSize != actualTreeSize) {
+            ALOGE("Verity tree size mismatch: %d vs .idsig: %d.", int(treeSize),
+                  int(actualTreeSize));
+            return {};
+        }
         result.push_back(InputDesc{
                 .fd = std::move(idsigFd),
                 .size = treeSize,
                 .kind = INCFS_BLOCK_KIND_HASH,
-                .waitOnEof = false,
         });
     }
 
@@ -182,8 +309,6 @@
         result.push_back(InputDesc{
                 .fd = std::move(fileFd),
                 .size = size,
-                .kind = INCFS_BLOCK_KIND_DATA,
-                .waitOnEof = false,
         });
     }
 
@@ -226,19 +351,32 @@
                   android::dataloader::StatusListenerPtr statusListener,
                   android::dataloader::ServiceConnectorPtr,
                   android::dataloader::ServiceParamsPtr) final {
+        CHECK(ifs) << "ifs can't be null";
+        CHECK(statusListener) << "statusListener can't be null";
         mArgs = params.arguments();
         mIfs = ifs;
+        mStatusListener = statusListener;
         return true;
     }
     bool onStart() final { return true; }
-    void onStop() final {}
-    void onDestroy() final {}
+    void onStop() final {
+        mStopReceiving = true;
+        eventfd_write(mEventFd, 1);
+        if (mReceiverThread.joinable()) {
+            mReceiverThread.join();
+        }
+    }
+    void onDestroy() final {
+        ALOGE("Sending EXIT to server.");
+        sendRequest(mOutFd, EXIT);
+        // Make sure the receiver thread stopped.
+        CHECK(!mReceiverThread.joinable());
 
-    // IFS callbacks.
-    void onPendingReads(const dataloader::PendingReads& pendingReads) final {}
-    void onPageReads(const dataloader::PageReads& pageReads) final {}
+        mInFd.reset();
+        mOutFd.reset();
+    }
 
-    // FS callbacks.
+    // Installation.
     bool onPrepareImage(const dataloader::DataLoaderInstallationFiles& addedFiles) final {
         JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
         const auto& jni = jniIds(env);
@@ -257,6 +395,7 @@
         std::vector<IncFsDataBlock> blocks;
         blocks.reserve(BLOCKS_COUNT);
 
+        unique_fd streamingFd;
         for (auto&& file : addedFiles) {
             auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata);
             if (inputs.empty()) {
@@ -267,7 +406,6 @@
             }
 
             const auto fileId = IncFs_FileIdFromMetadata(file.metadata);
-
             const auto incfsFd(mIfs->openWrite(fileId));
             if (incfsFd < 0) {
                 ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. "
@@ -277,6 +415,9 @@
             }
 
             for (auto&& input : inputs) {
+                if (input.streaming && !streamingFd.ok()) {
+                    streamingFd.reset(dup(input.fd));
+                }
                 if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof,
                                  &buffer, &blocks)) {
                     ALOGE("Failed to copy data to IncFS file for metadata: %.*s, final file name "
@@ -288,7 +429,12 @@
             }
         }
 
-        ALOGE("All done.");
+        if (streamingFd.ok()) {
+            ALOGE("onPrepareImage: done, proceeding to streaming.");
+            return initStreaming(std::move(streamingFd));
+        }
+
+        ALOGE("onPrepareImage: done.");
         return true;
     }
 
@@ -378,11 +524,253 @@
         return true;
     }
 
+    // Read tracing.
+    struct TracedRead {
+        uint64_t timestampUs;
+        android::dataloader::FileId fileId;
+        uint32_t firstBlockIdx;
+        uint32_t count;
+    };
+
+    void onPageReads(const android::dataloader::PageReads& pageReads) final {
+        auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+        if (CC_LIKELY(!trace)) {
+            return;
+        }
+
+        TracedRead last = {};
+        for (auto&& read : pageReads) {
+            if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) {
+                traceRead(last);
+                last = TracedRead{
+                        .timestampUs = read.bootClockTsUs,
+                        .fileId = read.id,
+                        .firstBlockIdx = (uint32_t)read.block,
+                        .count = 1,
+                };
+            } else {
+                ++last.count;
+            }
+        }
+        traceRead(last);
+    }
+
+    void traceRead(const TracedRead& read) {
+        if (!read.count) {
+            return;
+        }
+
+        FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
+        auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
+                                               static_cast<long long>(read.firstBlockIdx),
+                                               static_cast<long long>(read.count),
+                                               static_cast<int>(fileIdx));
+        ATRACE_BEGIN(str.c_str());
+        ATRACE_END();
+    }
+
+    // Streaming.
+    bool initStreaming(unique_fd inout) {
+        mInFd.reset(dup(inout));
+        mOutFd.reset(dup(inout));
+        if (mInFd < 0 || mOutFd < 0) {
+            ALOGE("Failed to dup FDs.");
+            return false;
+        }
+
+        mEventFd.reset(eventfd(0, EFD_CLOEXEC));
+        if (mEventFd < 0) {
+            ALOGE("Failed to create eventfd.");
+            return false;
+        }
+
+        // Awaiting adb handshake.
+        char okay_buf[OKAY.size()];
+        if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) {
+            ALOGE("Failed to receive OKAY. Abort.");
+            return false;
+        }
+        if (std::string_view(okay_buf, OKAY.size()) != OKAY) {
+            ALOGE("Received '%.*s', expecting '%.*s'", (int)OKAY.size(), okay_buf, (int)OKAY.size(),
+                  OKAY.data());
+            return false;
+        }
+
+        mReceiverThread = std::thread([this]() { receiver(); });
+        ALOGI("Started streaming...");
+        return true;
+    }
+
+    // IFS callbacks.
+    void onPendingReads(const dataloader::PendingReads& pendingReads) final {
+        CHECK(mIfs);
+        for (auto&& pendingRead : pendingReads) {
+            const android::dataloader::FileId& fileId = pendingRead.id;
+            const auto blockIdx = static_cast<BlockIdx>(pendingRead.block);
+            /*
+            ALOGI("Missing: %d", (int) blockIdx);
+            */
+            FileIdx fileIdx = convertFileIdToFileIndex(fileId);
+            if (fileIdx < 0) {
+                ALOGE("Failed to handle event for fileid=%s. Ignore.",
+                      android::incfs::toString(fileId).c_str());
+                continue;
+            }
+            if (mRequestedFiles.insert(fileIdx).second) {
+                if (!sendRequest(mOutFd, PREFETCH, fileIdx, blockIdx)) {
+                    ALOGE("Failed to request prefetch for fileid=%s. Ignore.",
+                          android::incfs::toString(fileId).c_str());
+                    mRequestedFiles.erase(fileIdx);
+                    mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+                }
+            }
+            sendRequest(mOutFd, BLOCK_MISSING, fileIdx, blockIdx);
+        }
+    }
+
+    void receiver() {
+        std::vector<uint8_t> data;
+        std::vector<IncFsDataBlock> instructions;
+        std::unordered_map<FileIdx, unique_fd> writeFds;
+        while (!mStopReceiving) {
+            const int res = waitForDataOrSignal(mInFd, mEventFd);
+            if (res == 0) {
+                continue;
+            }
+            if (res < 0) {
+                ALOGE("Failed to poll. Abort.");
+                mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+                break;
+            }
+            if (res == mEventFd) {
+                ALOGE("Received stop signal. Exit.");
+                break;
+            }
+            if (!readChunk(mInFd, data)) {
+                ALOGE("Failed to read a message. Abort.");
+                mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+                break;
+            }
+            auto remainingData = std::span(data);
+            while (!remainingData.empty()) {
+                auto header = readHeader(remainingData);
+                if (header.fileIdx == -1 && header.compressionType == 0 && header.blockIdx == 0 &&
+                    header.blockSize == 0) {
+                    ALOGI("Stop signal received. Sending exit command (remaining bytes: %d).",
+                          int(remainingData.size()));
+
+                    sendRequest(mOutFd, EXIT);
+                    mStopReceiving = true;
+                    break;
+                }
+                if (header.fileIdx < 0 || header.blockSize <= 0 || header.compressionType < 0 ||
+                    header.blockIdx < 0) {
+                    ALOGE("invalid header received. Abort.");
+                    mStopReceiving = true;
+                    break;
+                }
+                const FileIdx fileIdx = header.fileIdx;
+                const android::dataloader::FileId fileId = convertFileIndexToFileId(fileIdx);
+                if (!android::incfs::isValidFileId(fileId)) {
+                    ALOGE("Unknown data destination for file ID %d. "
+                          "Ignore.",
+                          header.fileIdx);
+                    continue;
+                }
+
+                auto& writeFd = writeFds[fileIdx];
+                if (writeFd < 0) {
+                    writeFd = this->mIfs->openWrite(fileId);
+                    if (writeFd < 0) {
+                        ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileIdx,
+                              -writeFd);
+                        break;
+                    }
+                }
+
+                const auto inst = IncFsDataBlock{
+                        .fileFd = writeFd,
+                        .pageIndex = static_cast<IncFsBlockIndex>(header.blockIdx),
+                        .compression = static_cast<IncFsCompressionKind>(header.compressionType),
+                        .kind = INCFS_BLOCK_KIND_DATA,
+                        .dataSize = static_cast<uint16_t>(header.blockSize),
+                        .data = (const char*)remainingData.data(),
+                };
+                instructions.push_back(inst);
+                remainingData = remainingData.subspan(header.blockSize);
+            }
+            writeInstructions(instructions);
+        }
+        writeInstructions(instructions);
+    }
+
+    void writeInstructions(std::vector<IncFsDataBlock>& instructions) {
+        auto res = this->mIfs->writeBlocks(instructions);
+        if (res != instructions.size()) {
+            ALOGE("Dailed to write data to Incfs (res=%d when expecting %d)", res,
+                  int(instructions.size()));
+        }
+        instructions.clear();
+    }
+
+    FileIdx convertFileIdToFileIndex(android::dataloader::FileId fileId) {
+        // FileId is a string in format '+FileIdx\0'.
+        const char* meta = (const char*)&fileId;
+        if (*meta != '+') {
+            return -1;
+        }
+
+        int fileIdx;
+        auto res = std::from_chars(meta + 1, meta + sizeof(fileId), fileIdx);
+        if (res.ec != std::errc{} || fileIdx < std::numeric_limits<FileIdx>::min() ||
+            fileIdx > std::numeric_limits<FileIdx>::max()) {
+            return -1;
+        }
+
+        return FileIdx(fileIdx);
+    }
+
+    android::dataloader::FileId convertFileIndexToFileId(FileIdx fileIdx) {
+        IncFsFileId fileId = {};
+        char* meta = (char*)&fileId;
+        *meta = '+';
+        if (auto [p, ec] = std::to_chars(meta + 1, meta + sizeof(fileId), fileIdx);
+            ec != std::errc()) {
+            return {};
+        }
+        return fileId;
+    }
+
     JavaVM* const mJvm;
     std::string mArgs;
-    android::dataloader::FilesystemConnectorPtr mIfs;
+    android::dataloader::FilesystemConnectorPtr mIfs = nullptr;
+    android::dataloader::StatusListenerPtr mStatusListener = nullptr;
+    android::base::unique_fd mInFd;
+    android::base::unique_fd mOutFd;
+    android::base::unique_fd mEventFd;
+    std::thread mReceiverThread;
+    std::atomic<bool> mStopReceiving = false;
+    /** Tracks which files have been requested */
+    std::unordered_set<FileIdx> mRequestedFiles;
 };
 
+BlockHeader readHeader(std::span<uint8_t>& data) {
+    BlockHeader header;
+    if (data.size() < sizeof(header)) {
+        return header;
+    }
+
+    header.fileIdx = static_cast<FileIdx>(be16toh(*reinterpret_cast<const uint16_t*>(&data[0])));
+    header.compressionType =
+            static_cast<CompressionType>(be16toh(*reinterpret_cast<const uint16_t*>(&data[2])));
+    header.blockIdx = static_cast<BlockIdx>(be32toh(*reinterpret_cast<const uint32_t*>(&data[4])));
+    header.blockSize =
+            static_cast<BlockSize>(be16toh(*reinterpret_cast<const uint16_t*>(&data[8])));
+    data = data.subspan(sizeof(header));
+
+    return header;
+}
+
 static void nativeInitialize(JNIEnv* env, jclass klass) {
     jniIds(env);
 }
diff --git a/services/core/xsd/vts/Android.bp b/services/core/xsd/vts/Android.bp
index 5545656..636d110 100644
--- a/services/core/xsd/vts/Android.bp
+++ b/services/core/xsd/vts/Android.bp
@@ -31,4 +31,12 @@
         "-Wall",
         "-Werror",
     ],
+    data: [
+        ":default-permissions",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts-core"
+    ],
+    test_config: "vts_defaultPermissions_validate_test.xml",
 }
diff --git a/services/core/xsd/vts/vts_defaultPermissions_validate_test.xml b/services/core/xsd/vts/vts_defaultPermissions_validate_test.xml
new file mode 100644
index 0000000..1ccacae
--- /dev/null
+++ b/services/core/xsd/vts/vts_defaultPermissions_validate_test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs vts_defaultPermissions_validate_test.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="default-permissions.xsd->/data/local/tmp/default-permissions.xsd" />
+        <option name="push" value="vts_defaultPermissions_validate_test->/data/local/tmp/vts_defaultPermissions_validate_test" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="vts_defaultPermissions_validate_test" />
+    </test>
+</configuration>
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d2ec436..063ad0f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -57,6 +57,7 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
@@ -522,7 +523,8 @@
 
     /** Keyguard features that are allowed to be set on a managed profile */
     private static final int PROFILE_KEYGUARD_FEATURES =
-            PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
+            NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+                    | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
 
     private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000;
 
@@ -2542,7 +2544,7 @@
      * corporate owned device.
      */
     @GuardedBy("getLockObject()")
-    private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+    private void migrateToProfileOnOrganizationOwnedDeviceIfCompLocked() {
         logIfVerbose("Checking whether we need to migrate COMP ");
         final int doUserId = mOwners.getDeviceOwnerUserId();
         if (doUserId == UserHandle.USER_NULL) {
@@ -2605,6 +2607,11 @@
 
         // Note: KeyChain keys are not removed and will remain accessible for the apps that have
         // been given grants to use them.
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.COMP_TO_ORG_OWNED_PO_MIGRATED)
+                .setAdmin(poAdminComponent)
+                .write();
     }
 
     private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
@@ -3865,7 +3872,7 @@
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 synchronized (getLockObject()) {
-                    maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+                    migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
                 }
                 final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
                 if (userId >= 0) {
@@ -8163,16 +8170,20 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
-        if (isManagedProfile(userHandle)) {
-            if (parent) {
-                which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-            } else {
-                which = which & PROFILE_KEYGUARD_FEATURES;
-            }
-        }
         synchronized (getLockObject()) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(
                     who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
+            if (isManagedProfile(userHandle)) {
+                if (parent) {
+                    if (isProfileOwnerOfOrganizationOwnedDevice(ap)) {
+                        which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                    } else {
+                        which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                    }
+                } else {
+                    which = which & PROFILE_KEYGUARD_FEATURES;
+                }
+            }
             if (ap.disabledKeyguardFeatures != which) {
                 ap.disabledKeyguardFeatures = which;
                 saveSettingsLocked(userHandle);
@@ -11553,7 +11564,7 @@
     }
 
     @Override
-    public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) {
+    public void setConfiguredNetworksLockdownState(ComponentName who, boolean lockdown) {
         if (!mHasFeature) {
             return;
         }
@@ -11572,7 +11583,7 @@
     }
 
     @Override
-    public boolean isLockdownAdminConfiguredNetworks(ComponentName who) {
+    public boolean hasLockdownAdminConfiguredNetworks(ComponentName who) {
         if (!mHasFeature) {
             return false;
         }
@@ -15219,15 +15230,27 @@
             final ActiveAdmin admin =
                     getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             previousCrossProfilePackages = admin.mCrossProfilePackages;
+            if (packageNames.equals(previousCrossProfilePackages)) {
+                return;
+            }
             admin.mCrossProfilePackages = packageNames;
             saveSettingsLocked(mInjector.userHandleGetCallingUserId());
         }
+        logSetCrossProfilePackages(who, packageNames);
         final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
         mInjector.binderWithCleanCallingIdentity(
                 () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
                         previousCrossProfilePackages, new HashSet<>(packageNames)));
     }
 
+    private void logSetCrossProfilePackages(ComponentName who, List<String> packageNames) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_PACKAGES)
+                .setAdmin(who)
+                .setStrings(packageNames.toArray(new String[packageNames.size()]))
+                .write();
+    }
+
     @Override
     public List<String> getCrossProfilePackages(ComponentName who) {
         if (!mHasFeature) {
@@ -15551,6 +15574,12 @@
 
         mInjector.binderWithCleanCallingIdentity(
                 () -> applyPersonalAppsSuspension(callingUserId, suspended));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
+                .setAdmin(who)
+                .setBoolean(suspended)
+                .write();
     }
 
     /**
@@ -15722,9 +15751,16 @@
 
         mInjector.binderWithCleanCallingIdentity(
                 () -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
+                .setAdmin(who)
+                .setTimePeriod(timeoutMs)
+                .write();
     }
 
-    void enforceHandlesCheckPolicyComplianceIntent(@UserIdInt int userId, String packageName) {
+    private void enforceHandlesCheckPolicyComplianceIntent(
+            @UserIdInt int userId, String packageName) {
         mInjector.binderWithCleanCallingIdentity(() -> {
             final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
             intent.setPackage(packageName);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 93662c9..1936f13 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1741,7 +1741,7 @@
             mSystemServiceManager.startService(SensorNotificationService.class);
             t.traceEnd();
 
-            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXTHUB)) {
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXT_HUB)) {
                 t.traceBegin("StartContextHubSystemService");
                 mSystemServiceManager.startService(ContextHubSystemService.class);
                 t.traceEnd();
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 2d18a29..663bf4f 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -17,6 +17,7 @@
 package com.android.server.people;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.AppTarget;
@@ -24,6 +25,7 @@
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -138,6 +140,21 @@
             });
         }
 
+        @Override
+        public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
+            mDataManager.pruneDataForUser(userId, signal);
+        }
+
+        @Override
+        public byte[] backupConversationInfos(@UserIdInt int userId) {
+            return new byte[0];
+        }
+
+        @Override
+        public void restoreConversationInfos(@UserIdInt int userId, @NonNull String key,
+                @NonNull byte[] payload) {
+        }
+
         @VisibleForTesting
         SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
             return mSessions.get(sessionId);
@@ -160,14 +177,5 @@
                 Slog.e(TAG, "Failed to calling callback" + e);
             }
         }
-
-        @Override
-        public byte[] backupConversationInfos(int userId) {
-            return new byte[0];
-        }
-
-        @Override
-        public void restoreConversationInfos(int userId, String key, byte[] payload) {
-        }
     }
 }
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index ce35366..859cdf2 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -137,6 +137,11 @@
         return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
     }
 
+    /** Whether the shortcut for this conversation is cached in Shortcut Service. */
+    public boolean isShortcutCached() {
+        return hasShortcutFlags(ShortcutInfo.FLAG_CACHED);
+    }
+
     /** Whether this conversation is marked as important by the user. */
     public boolean isImportant() {
         return hasConversationFlags(FLAG_IMPORTANT);
@@ -213,6 +218,9 @@
         if (isShortcutLongLived()) {
             sb.append("Liv");
         }
+        if (isShortcutCached()) {
+            sb.append("Cac");
+        }
         sb.append("]");
         sb.append(", conversationFlags=0x").append(Integer.toHexString(mConversationFlags));
         sb.append(" [");
diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java
index ea36d38..3afb209 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -133,10 +133,11 @@
     }
 
     @MainThread
-    synchronized void deleteConversation(@NonNull String shortcutId) {
+    @Nullable
+    synchronized ConversationInfo deleteConversation(@NonNull String shortcutId) {
         ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId);
         if (conversationInfo == null) {
-            return;
+            return null;
         }
 
         LocusId locusId = conversationInfo.getLocusId();
@@ -159,6 +160,7 @@
             mNotifChannelIdToShortcutIdMap.remove(notifChannelId);
         }
         scheduleUpdateConversationsOnDisk();
+        return conversationInfo;
     }
 
     synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
diff --git a/services/people/java/com/android/server/people/data/DataMaintenanceService.java b/services/people/java/com/android/server/people/data/DataMaintenanceService.java
new file mode 100644
index 0000000..58f0654
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/DataMaintenanceService.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import android.annotation.UserIdInt;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This service runs periodically to ensure the data consistency and the retention policy for a
+ * specific user's data.
+ */
+public class DataMaintenanceService extends JobService {
+
+    private static final long JOB_RUN_INTERVAL = TimeUnit.HOURS.toMillis(24);
+
+    /** This job ID must be unique within the system server. */
+    private static final int BASE_JOB_ID = 0xC315BD7;  // 204561367
+
+    static void scheduleJob(Context context, @UserIdInt int userId) {
+        int jobId = getJobId(userId);
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        if (jobScheduler.getPendingJob(jobId) == null) {
+            ComponentName component = new ComponentName(context, DataMaintenanceService.class);
+            JobInfo newJob = new JobInfo.Builder(jobId, component)
+                    .setRequiresDeviceIdle(true)
+                    .setPeriodic(JOB_RUN_INTERVAL)
+                    .build();
+            jobScheduler.schedule(newJob);
+        }
+    }
+
+    static void cancelJob(Context context, @UserIdInt int userId) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        jobScheduler.cancel(getJobId(userId));
+    }
+
+    private CancellationSignal mSignal;
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        int userId = getUserId(params.getJobId());
+        mSignal = new CancellationSignal();
+        new Thread(() -> {
+            PeopleServiceInternal peopleServiceInternal =
+                    LocalServices.getService(PeopleServiceInternal.class);
+            peopleServiceInternal.pruneDataForUser(userId, mSignal);
+            jobFinished(params, mSignal.isCanceled());
+        }).start();
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        if (mSignal != null) {
+            mSignal.cancel();
+        }
+        return false;
+    }
+
+    private static int getJobId(@UserIdInt int userId) {
+        return BASE_JOB_ID + userId;
+    }
+
+    private static @UserIdInt int getUserId(int jobId) {
+        return jobId - BASE_JOB_ID;
+    }
+}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index c8673f8..dd9cbd0 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -32,6 +32,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.content.pm.ShortcutManager.ShareShortcutInfo;
@@ -40,6 +41,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Process;
 import android.os.RemoteException;
@@ -53,16 +55,20 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
+import android.util.ArraySet;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ChooserActivity;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.SmsApplication;
 import com.android.server.LocalServices;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -94,10 +100,12 @@
     private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
     private final SparseArray<NotificationListenerService> mNotificationListeners =
             new SparseArray<>();
-    private final ContentObserver mCallLogContentObserver;
-    private final ContentObserver mMmsSmsContentObserver;
+    private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+    private ContentObserver mCallLogContentObserver;
+    private ContentObserver mMmsSmsContentObserver;
 
     private ShortcutServiceInternal mShortcutServiceInternal;
+    private PackageManagerInternal mPackageManagerInternal;
     private ShortcutManager mShortcutManager;
     private UserManager mUserManager;
 
@@ -110,16 +118,13 @@
         mContext = context;
         mInjector = injector;
         mUsageStatsQueryExecutor = mInjector.createScheduledExecutor();
-        mCallLogContentObserver = new CallLogContentObserver(
-                BackgroundThread.getHandler());
-        mMmsSmsContentObserver = new MmsSmsContentObserver(
-                BackgroundThread.getHandler());
         mDiskReadWriterExecutor = mInjector.createScheduledExecutor();
     }
 
     /** Initialization. Called when the system services are up running. */
     public void initialize() {
         mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mShortcutManager = mContext.getSystemService(ShortcutManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
 
@@ -172,17 +177,26 @@
             // Should never occur for local calls.
         }
 
+        PackageMonitor packageMonitor = new PerUserPackageMonitor();
+        packageMonitor.register(mContext, null, UserHandle.of(userId), true);
+        mPackageMonitors.put(userId, packageMonitor);
+
         if (userId == UserHandle.USER_SYSTEM) {
             // The call log and MMS/SMS messages are shared across user profiles. So only need to
             // register the content observers once for the primary user.
             // TODO: Register observers after the conversations and events being loaded from disk.
+            mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler());
             mContext.getContentResolver().registerContentObserver(
                     CallLog.CONTENT_URI, /* notifyForDescendants= */ true,
                     mCallLogContentObserver, UserHandle.USER_SYSTEM);
+
+            mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler());
             mContext.getContentResolver().registerContentObserver(
                     MmsSms.CONTENT_URI, /* notifyForDescendants= */ false,
                     mMmsSmsContentObserver, UserHandle.USER_SYSTEM);
         }
+
+        DataMaintenanceService.scheduleJob(mContext, userId);
     }
 
     /** This method is called when a user is stopped. */
@@ -207,10 +221,21 @@
                 // Should never occur for local calls.
             }
         }
-        if (userId == UserHandle.USER_SYSTEM) {
-            mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver);
-            mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver);
+        if (mPackageMonitors.indexOfKey(userId) >= 0) {
+            mPackageMonitors.get(userId).unregister();
         }
+        if (userId == UserHandle.USER_SYSTEM) {
+            if (mCallLogContentObserver != null) {
+                mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver);
+                mCallLogContentObserver = null;
+            }
+            if (mMmsSmsContentObserver != null) {
+                mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver);
+                mCallLogContentObserver = null;
+            }
+        }
+
+        DataMaintenanceService.cancelJob(mContext, userId);
     }
 
     /**
@@ -274,9 +299,8 @@
                     || TextUtils.isEmpty(mimeType)) {
                 return;
             }
-            EventHistoryImpl eventHistory =
-                    packageData.getEventStore().getOrCreateShortcutEventHistory(
-                            shortcutInfo.getId());
+            EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+                    EventStore.CATEGORY_SHORTCUT_BASED, shortcutInfo.getId());
             @Event.EventType int eventType;
             if (mimeType.startsWith("text/")) {
                 eventType = Event.TYPE_SHARE_TEXT;
@@ -291,6 +315,45 @@
         }
     }
 
+    /** Prunes the data for the specified user. */
+    public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
+        UserData userData = getUnlockedUserData(userId);
+        if (userData == null || signal.isCanceled()) {
+            return;
+        }
+        pruneUninstalledPackageData(userData);
+
+        long currentTimeMillis = System.currentTimeMillis();
+        userData.forAllPackages(packageData -> {
+            if (signal.isCanceled()) {
+                return;
+            }
+            packageData.getEventStore().pruneOldEvents(currentTimeMillis);
+            if (!packageData.isDefaultDialer()) {
+                packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_CALL);
+            }
+            if (!packageData.isDefaultSmsApp()) {
+                packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
+            }
+            packageData.pruneOrphanEvents();
+        });
+    }
+
+    private void pruneUninstalledPackageData(@NonNull UserData userData) {
+        Set<String> installApps = new ArraySet<>();
+        mPackageManagerInternal.forEachInstalledPackage(
+                pkg -> installApps.add(pkg.getPackageName()), userData.getUserId());
+        List<String> packagesToDelete = new ArrayList<>();
+        userData.forAllPackages(packageData -> {
+            if (!installApps.contains(packageData.getPackageName())) {
+                packagesToDelete.add(packageData.getPackageName());
+            }
+        });
+        for (String packageName : packagesToDelete) {
+            userData.deletePackageData(packageName);
+        }
+    }
+
     /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */
     private List<ShortcutInfo> getShortcuts(
             @NonNull String packageName, @UserIdInt int userId,
@@ -322,7 +385,8 @@
     private void updateDefaultDialer(@NonNull UserData userData) {
         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         String defaultDialer = telecomManager != null
-                ? telecomManager.getDefaultDialerPackage(userData.getUserId()) : null;
+                ? telecomManager.getDefaultDialerPackage(
+                        new UserHandle(userData.getUserId())) : null;
         userData.setDefaultDialer(defaultDialer);
     }
 
@@ -346,7 +410,8 @@
                 || packageData.getConversationStore().getConversation(shortcutId) == null) {
             return null;
         }
-        return packageData.getEventStore().getOrCreateShortcutEventHistory(shortcutId);
+        return packageData.getEventStore().getOrCreateEventHistory(
+                EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
     }
 
     @VisibleForTesting
@@ -412,6 +477,11 @@
     }
 
     @VisibleForTesting
+    PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) {
+        return mPackageMonitors.get(userId);
+    }
+
+    @VisibleForTesting
     UserData getUserDataForTesting(@UserIdInt int userId) {
         return mUserDataArray.get(userId);
     }
@@ -498,7 +568,8 @@
                     return;
                 }
                 EventStore eventStore = defaultDialer.getEventStore();
-                eventStore.getOrCreateCallEventHistory(phoneNumber).addEvent(event);
+                eventStore.getOrCreateEventHistory(
+                        EventStore.CATEGORY_CALL, phoneNumber).addEvent(event);
             });
         }
     }
@@ -543,7 +614,8 @@
                     return;
                 }
                 EventStore eventStore = defaultSmsApp.getEventStore();
-                eventStore.getOrCreateSmsEventHistory(phoneNumber).addEvent(event);
+                eventStore.getOrCreateEventHistory(
+                        EventStore.CATEGORY_SMS, phoneNumber).addEvent(event);
             });
         }
     }
@@ -568,6 +640,14 @@
     private class NotificationListener extends NotificationListenerService {
 
         @Override
+        public void onNotificationPosted(StatusBarNotification sbn) {
+            EventHistoryImpl eventHistory = getEventHistoryIfEligible(sbn);
+            if (eventHistory != null) {
+                eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
+            }
+        }
+
+        @Override
         public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
                 int reason) {
             if (reason != REASON_CLICK) {
@@ -616,7 +696,15 @@
                     break;
             }
             conversationStore.addOrUpdate(builder.build());
-            // TODO: Cache the shortcut when a conversation's notification setting is changed.
+
+            if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED
+                    && conversationInfo.isShortcutLongLived()
+                    && !conversationInfo.isShortcutCached()) {
+                mShortcutServiceInternal.cacheShortcuts(user.getIdentifier(),
+                        mContext.getPackageName(), pkg,
+                        Collections.singletonList(conversationInfo.getShortcutId()),
+                        user.getIdentifier());
+            }
         }
     }
 
@@ -669,6 +757,20 @@
         }
     }
 
+    private class PerUserPackageMonitor extends PackageMonitor {
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            super.onPackageRemoved(packageName, uid);
+
+            int userId = getChangingUserId();
+            UserData userData = getUnlockedUserData(userId);
+            if (userData != null) {
+                userData.deletePackageData(packageName);
+            }
+        }
+    }
+
     private class ShutdownBroadcastReceiver extends BroadcastReceiver {
 
         @Override
diff --git a/services/people/java/com/android/server/people/data/EventHistoryImpl.java b/services/people/java/com/android/server/people/data/EventHistoryImpl.java
index 6b6bd7e..6bef1db 100644
--- a/services/people/java/com/android/server/people/data/EventHistoryImpl.java
+++ b/services/people/java/com/android/server/people/data/EventHistoryImpl.java
@@ -78,6 +78,17 @@
         mRecentEvents.add(event);
     }
 
+    void onDestroy() {
+        mEventIndexArray.clear();
+        mRecentEvents.clear();
+        // TODO: STOPSHIP: Delete the data files.
+    }
+
+    /** Deletes the events data that exceeds the retention period. */
+    void pruneOldEvents(long currentTimeMillis) {
+        // TODO: STOPSHIP: Delete the old events data files.
+    }
+
     @VisibleForTesting
     static class Injector {
 
diff --git a/services/people/java/com/android/server/people/data/EventList.java b/services/people/java/com/android/server/people/data/EventList.java
index b267d66..d770f91 100644
--- a/services/people/java/com/android/server/people/data/EventList.java
+++ b/services/people/java/com/android/server/people/data/EventList.java
@@ -69,6 +69,10 @@
         return result;
     }
 
+    void clear() {
+        mEvents.clear();
+    }
+
     /** Returns the first index whose timestamp is greater or equal to the provided timestamp. */
     private int firstIndexOnOrAfter(long timestamp) {
         int result = mEvents.size();
diff --git a/services/people/java/com/android/server/people/data/EventStore.java b/services/people/java/com/android/server/people/data/EventStore.java
index d6b7a86..c8d44ac 100644
--- a/services/people/java/com/android/server/people/data/EventStore.java
+++ b/services/people/java/com/android/server/people/data/EventStore.java
@@ -16,99 +16,129 @@
 
 package com.android.server.people.data;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.LocusId;
 import android.util.ArrayMap;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
 
 /** The store that stores and accesses the events data for a package. */
 class EventStore {
 
-    private final EventHistoryImpl mPackageEventHistory = new EventHistoryImpl();
+    /** The events that are queryable with a shortcut ID. */
+    static final int CATEGORY_SHORTCUT_BASED = 0;
 
-    // Shortcut ID -> Event History
-    private final Map<String, EventHistoryImpl> mShortcutEventHistoryMap = new ArrayMap<>();
+    /** The events that are queryable with a {@link android.content.LocusId}. */
+    static final int CATEGORY_LOCUS_ID_BASED = 1;
 
-    // Locus ID -> Event History
-    private final Map<LocusId, EventHistoryImpl> mLocusEventHistoryMap = new ArrayMap<>();
+    /** The phone call events that are queryable with a phone number. */
+    static final int CATEGORY_CALL = 2;
 
-    // Phone Number -> Event History
-    private final Map<String, EventHistoryImpl> mCallEventHistoryMap = new ArrayMap<>();
+    /** The SMS or MMS events that are queryable with a phone number. */
+    static final int CATEGORY_SMS = 3;
 
-    // Phone Number -> Event History
-    private final Map<String, EventHistoryImpl> mSmsEventHistoryMap = new ArrayMap<>();
+    /** The events that are queryable with an {@link android.app.Activity} class name. */
+    static final int CATEGORY_CLASS_BASED = 4;
 
-    /** Gets the package level {@link EventHistory}. */
-    @NonNull
-    EventHistory getPackageEventHistory() {
-        return mPackageEventHistory;
-    }
+    @IntDef(prefix = { "CATEGORY_" }, value = {
+            CATEGORY_SHORTCUT_BASED,
+            CATEGORY_LOCUS_ID_BASED,
+            CATEGORY_CALL,
+            CATEGORY_SMS,
+            CATEGORY_CLASS_BASED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface EventCategory {}
 
-    /** Gets the {@link EventHistory} for the specified {@code shortcutId} if exists. */
-    @Nullable
-    EventHistory getShortcutEventHistory(String shortcutId) {
-        return mShortcutEventHistoryMap.get(shortcutId);
-    }
+    private final List<Map<String, EventHistoryImpl>> mEventHistoryMaps = new ArrayList<>();
 
-    /** Gets the {@link EventHistory} for the specified {@code locusId} if exists. */
-    @Nullable
-    EventHistory getLocusEventHistory(LocusId locusId) {
-        return mLocusEventHistoryMap.get(locusId);
-    }
-
-    /** Gets the phone call {@link EventHistory} for the specified {@code phoneNumber} if exists. */
-    @Nullable
-    EventHistory getCallEventHistory(String phoneNumber) {
-        return mCallEventHistoryMap.get(phoneNumber);
-    }
-
-    /** Gets the SMS {@link EventHistory} for the specified {@code phoneNumber} if exists. */
-    @Nullable
-    EventHistory getSmsEventHistory(String phoneNumber) {
-        return mSmsEventHistoryMap.get(phoneNumber);
+    EventStore() {
+        mEventHistoryMaps.add(CATEGORY_SHORTCUT_BASED, new ArrayMap<>());
+        mEventHistoryMaps.add(CATEGORY_LOCUS_ID_BASED, new ArrayMap<>());
+        mEventHistoryMaps.add(CATEGORY_CALL, new ArrayMap<>());
+        mEventHistoryMaps.add(CATEGORY_SMS, new ArrayMap<>());
+        mEventHistoryMaps.add(CATEGORY_CLASS_BASED, new ArrayMap<>());
     }
 
     /**
-     * Gets the {@link EventHistoryImpl} for the specified {@code shortcutId} or creates a new
-     * instance and put it into the store if not exists. The caller needs to verify if a
-     * conversation with this shortcut ID exists before calling this method.
+     * Gets the {@link EventHistory} for the specified key if exists.
+     *
+     * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class
+     *            name.
      */
-    @NonNull
-    EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) {
-        return mShortcutEventHistoryMap.computeIfAbsent(shortcutId, key -> new EventHistoryImpl());
+    @Nullable
+    EventHistory getEventHistory(@EventCategory int category, String key) {
+        return mEventHistoryMaps.get(category).get(key);
     }
 
     /**
-     * Gets the {@link EventHistoryImpl} for the specified {@code locusId} or creates a new
-     * instance and put it into the store if not exists. The caller needs to ensure a conversation
-     * with this locus ID exists before calling this method.
+     * Gets the {@link EventHistoryImpl} for the specified ID or creates a new instance and put it
+     * into the store if not exists. The caller needs to verify if the associated conversation
+     * exists before calling this method.
+     *
+     * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class
+     *            name.
      */
     @NonNull
-    EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) {
-        return mLocusEventHistoryMap.computeIfAbsent(locusId, key -> new EventHistoryImpl());
+    EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) {
+        return mEventHistoryMaps.get(category).computeIfAbsent(key, k -> new EventHistoryImpl());
     }
 
     /**
-     * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for call events
-     * or creates a new instance and put it into the store if not exists. The caller needs to ensure
-     * a conversation with this phone number exists and this package is the default dialer
-     * before calling this method.
+     * Deletes the events and index data for the specified key.
+     *
+     * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class
+     *            name.
      */
-    @NonNull
-    EventHistoryImpl getOrCreateCallEventHistory(String phoneNumber) {
-        return mCallEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl());
+    void deleteEventHistory(@EventCategory int category, String key) {
+        EventHistoryImpl eventHistory = mEventHistoryMaps.get(category).remove(key);
+        if (eventHistory != null) {
+            eventHistory.onDestroy();
+        }
+    }
+
+    /** Deletes all the events and index data for the specified category from disk. */
+    void deleteEventHistories(@EventCategory int category) {
+        mEventHistoryMaps.get(category).clear();
+        // TODO: Implement this method to delete the data from disk.
+    }
+
+    /** Deletes the events data that exceeds the retention period. */
+    void pruneOldEvents(long currentTimeMillis) {
+        for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) {
+            for (EventHistoryImpl eventHistory : map.values()) {
+                eventHistory.pruneOldEvents(currentTimeMillis);
+            }
+        }
     }
 
     /**
-     * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for SMS events
-     * or creates a new instance and put it into the store if not exists. The caller needs to ensure
-     * a conversation with this phone number exists and this package is the default SMS app
-     * before calling this method.
+     * Prunes the event histories whose key (shortcut ID, locus ID or phone number) does not match
+     * any conversations.
+     *
+     * @param keyChecker Check whether there exists a conversation contains this key.
      */
-    @NonNull
-    EventHistoryImpl getOrCreateSmsEventHistory(String phoneNumber) {
-        return mSmsEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl());
+    void pruneOrphanEventHistories(@EventCategory int category, Predicate<String> keyChecker) {
+        Set<String> keys = mEventHistoryMaps.get(category).keySet();
+        List<String> keysToDelete = new ArrayList<>();
+        for (String key : keys) {
+            if (!keyChecker.test(key)) {
+                keysToDelete.add(key);
+            }
+        }
+        Map<String, EventHistoryImpl> eventHistoryMap = mEventHistoryMaps.get(category);
+        for (String key : keysToDelete) {
+            EventHistoryImpl eventHistory = eventHistoryMap.remove(key);
+            if (eventHistory != null) {
+                eventHistory.onDestroy();
+            }
+        }
     }
 }
diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java
index f67699c..c55f972 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -16,6 +16,12 @@
 
 package com.android.server.people.data;
 
+import static com.android.server.people.data.EventStore.CATEGORY_CALL;
+import static com.android.server.people.data.EventStore.CATEGORY_CLASS_BASED;
+import static com.android.server.people.data.EventStore.CATEGORY_LOCUS_ID_BASED;
+import static com.android.server.people.data.EventStore.CATEGORY_SHORTCUT_BASED;
+import static com.android.server.people.data.EventStore.CATEGORY_SMS;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -89,11 +95,6 @@
         mConversationStore.forAllConversations(consumer);
     }
 
-    @NonNull
-    public EventHistory getPackageLevelEventHistory() {
-        return getEventStore().getPackageEventHistory();
-    }
-
     /**
      * Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link
      * ConversationInfo} does not exist.
@@ -117,14 +118,16 @@
             return result;
         }
 
-        EventHistory shortcutEventHistory = getEventStore().getShortcutEventHistory(shortcutId);
+        EventHistory shortcutEventHistory = getEventStore().getEventHistory(
+                CATEGORY_SHORTCUT_BASED, shortcutId);
         if (shortcutEventHistory != null) {
             result.addEventHistory(shortcutEventHistory);
         }
 
         LocusId locusId = conversationInfo.getLocusId();
         if (locusId != null) {
-            EventHistory locusEventHistory = getEventStore().getLocusEventHistory(locusId);
+            EventHistory locusEventHistory = getEventStore().getEventHistory(
+                    CATEGORY_LOCUS_ID_BASED, locusId.getId());
             if (locusEventHistory != null) {
                 result.addEventHistory(locusEventHistory);
             }
@@ -135,13 +138,15 @@
             return result;
         }
         if (isDefaultDialer()) {
-            EventHistory callEventHistory = getEventStore().getCallEventHistory(phoneNumber);
+            EventHistory callEventHistory = getEventStore().getEventHistory(
+                    CATEGORY_CALL, phoneNumber);
             if (callEventHistory != null) {
                 result.addEventHistory(callEventHistory);
             }
         }
         if (isDefaultSmsApp()) {
-            EventHistory smsEventHistory = getEventStore().getSmsEventHistory(phoneNumber);
+            EventHistory smsEventHistory = getEventStore().getEventHistory(
+                    CATEGORY_SMS, phoneNumber);
             if (smsEventHistory != null) {
                 result.addEventHistory(smsEventHistory);
             }
@@ -149,6 +154,14 @@
         return result;
     }
 
+    /** Gets the {@link EventHistory} for a given Activity class. */
+    @NonNull
+    public EventHistory getClassLevelEventHistory(String className) {
+        EventHistory eventHistory = getEventStore().getEventHistory(
+                CATEGORY_CLASS_BASED, className);
+        return eventHistory != null ? eventHistory : new AggregateEventHistoryImpl();
+    }
+
     public boolean isDefaultDialer() {
         return mIsDefaultDialerPredicate.test(mPackageName);
     }
@@ -167,6 +180,47 @@
         return mEventStore;
     }
 
+    /**
+     * Deletes all the data (including conversation, events and index) for the specified
+     * conversation shortcut ID.
+     */
+    void deleteDataForConversation(String shortcutId) {
+        ConversationInfo conversationInfo = mConversationStore.deleteConversation(shortcutId);
+        if (conversationInfo == null) {
+            return;
+        }
+        mEventStore.deleteEventHistory(CATEGORY_SHORTCUT_BASED, shortcutId);
+        if (conversationInfo.getLocusId() != null) {
+            mEventStore.deleteEventHistory(
+                    CATEGORY_LOCUS_ID_BASED, conversationInfo.getLocusId().getId());
+        }
+        String phoneNumber = conversationInfo.getContactPhoneNumber();
+        if (!TextUtils.isEmpty(phoneNumber)) {
+            if (isDefaultDialer()) {
+                mEventStore.deleteEventHistory(CATEGORY_CALL, phoneNumber);
+            }
+            if (isDefaultSmsApp()) {
+                mEventStore.deleteEventHistory(CATEGORY_SMS, phoneNumber);
+            }
+        }
+    }
+
+    /** Prunes the events and index data that don't have a associated conversation. */
+    void pruneOrphanEvents() {
+        mEventStore.pruneOrphanEventHistories(CATEGORY_SHORTCUT_BASED,
+                key -> mConversationStore.getConversation(key) != null);
+        mEventStore.pruneOrphanEventHistories(CATEGORY_LOCUS_ID_BASED,
+                key -> mConversationStore.getConversationByLocusId(new LocusId(key)) != null);
+        if (isDefaultDialer()) {
+            mEventStore.pruneOrphanEventHistories(CATEGORY_CALL,
+                    key -> mConversationStore.getConversationByPhoneNumber(key) != null);
+        }
+        if (isDefaultSmsApp()) {
+            mEventStore.pruneOrphanEventHistories(CATEGORY_SMS,
+                    key -> mConversationStore.getConversationByPhoneNumber(key) != null);
+        }
+    }
+
     void onDestroy() {
         // TODO: STOPSHIP: Implements this method for the case of package being uninstalled.
     }
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 644c155..72f1abb 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -59,7 +59,7 @@
      */
     boolean querySince(long sinceTime) {
         UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
-                mUserId, sinceTime, System.currentTimeMillis(), false, false, false);
+                mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA);
         if (usageEvents == null) {
             return false;
         }
@@ -80,10 +80,6 @@
                     addEventByShortcutId(packageData, e.getShortcutId(),
                             new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION));
                     break;
-                case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
-                    addEventByNotificationChannelId(packageData, e.getNotificationChannelId(),
-                            new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED));
-                    break;
                 case UsageEvents.Event.LOCUS_ID_SET:
                     onInAppConversationEnded(packageData, e);
                     LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null;
@@ -129,8 +125,8 @@
         if (packageData.getConversationStore().getConversation(shortcutId) == null) {
             return;
         }
-        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory(
-                shortcutId);
+        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+                EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
         eventHistory.addEvent(event);
     }
 
@@ -138,21 +134,8 @@
         if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) {
             return;
         }
-        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory(
-                locusId);
-        eventHistory.addEvent(event);
-    }
-
-    private void addEventByNotificationChannelId(PackageData packageData,
-            String notificationChannelId, Event event) {
-        ConversationInfo conversationInfo =
-                packageData.getConversationStore().getConversationByNotificationChannelId(
-                        notificationChannelId);
-        if (conversationInfo == null) {
-            return;
-        }
-        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory(
-                conversationInfo.getShortcutId());
+        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+                EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
         eventHistory.addEvent(event);
     }
 }
diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java
index aaa5db8..7ca4b6c 100644
--- a/services/people/java/com/android/server/people/data/UserData.java
+++ b/services/people/java/com/android/server/people/data/UserData.java
@@ -104,6 +104,14 @@
         return mPackageDataMap.get(packageName);
     }
 
+    /** Deletes the specified package data. */
+    void deletePackageData(@NonNull String packageName) {
+        PackageData packageData = mPackageDataMap.remove(packageName);
+        if (packageData != null) {
+            packageData.onDestroy();
+        }
+    }
+
     void setDefaultDialer(@Nullable String packageName) {
         mDefaultDialer = packageName;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 64da6f6..d7a3cfd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -19,6 +19,12 @@
 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.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.RARE_INDEX;
+import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -34,13 +40,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
 
 import android.app.job.JobInfo;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.pm.PackageManagerInternal;
+import android.net.Uri;
 import android.os.SystemClock;
 import android.provider.MediaStore;
+import android.util.SparseIntArray;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -52,6 +61,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
@@ -61,7 +71,12 @@
 @RunWith(AndroidJUnit4.class)
 public class JobStatusTest {
     private static final double DELTA = 0.00001;
+    private static final String TEST_PACKAGE = "job.test.package";
+    private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(TEST_PACKAGE, "test");
+    private static final Uri TEST_MEDIA_URI = Uri.parse("content://media/path/to/media");
 
+    @Mock
+    private JobSchedulerInternal mJobSchedulerInternal;
     private MockitoSession mMockingSession;
 
     @Before
@@ -71,7 +86,7 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(LocalServices.class)
                 .startMocking();
-        doReturn(mock(JobSchedulerInternal.class))
+        doReturn(mJobSchedulerInternal)
                 .when(() -> LocalServices.getService(JobSchedulerInternal.class));
         doReturn(mock(PackageManagerInternal.class))
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -94,6 +109,82 @@
         }
     }
 
+    private static void assertEffectiveBucketForMediaExemption(JobStatus jobStatus,
+            boolean exemptionGranted) {
+        final SparseIntArray effectiveBucket = new SparseIntArray();
+        effectiveBucket.put(ACTIVE_INDEX, ACTIVE_INDEX);
+        effectiveBucket.put(WORKING_INDEX, WORKING_INDEX);
+        effectiveBucket.put(FREQUENT_INDEX, exemptionGranted ? WORKING_INDEX : FREQUENT_INDEX);
+        effectiveBucket.put(RARE_INDEX, exemptionGranted ? WORKING_INDEX : RARE_INDEX);
+        effectiveBucket.put(NEVER_INDEX, NEVER_INDEX);
+        effectiveBucket.put(RESTRICTED_INDEX, RESTRICTED_INDEX);
+        for (int i = 0; i < effectiveBucket.size(); i++) {
+            jobStatus.setStandbyBucket(effectiveBucket.keyAt(i));
+            assertEquals(effectiveBucket.valueAt(i), jobStatus.getEffectiveStandbyBucket());
+        }
+    }
+
+    @Test
+    public void testMediaBackupExemption_lateConstraint() {
+        final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+                .setOverrideDeadline(12)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+        assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false);
+    }
+
+    @Test
+    public void testMediaBackupExemption_noConnectivityConstraint() {
+        final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+                .build();
+        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+        assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false);
+    }
+
+    @Test
+    public void testMediaBackupExemption_noContentTriggerConstraint() {
+        final JobInfo networkJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+        assertEffectiveBucketForMediaExemption(createJobStatus(networkJob), false);
+    }
+
+    @Test
+    public void testMediaBackupExemption_wrongSourcePackage() {
+        final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn("not.test.package");
+        assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false);
+    }
+
+    @Test
+    public void testMediaBackupExemption_nonMediaUri() {
+        final Uri nonMediaUri = Uri.parse("content://not-media/any/path");
+        final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(nonMediaUri, 0))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+        assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false);
+    }
+
+    @Test
+    public void testMediaBackupExemptionGranted() {
+        final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+        assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), true);
+    }
+
     @Test
     public void testFraction() throws Exception {
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index bbc6bdb..d34f783 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -26,11 +26,6 @@
         <option name="test-file-name" value="SimpleServiceTestApp.apk" />
     </target_preparer>
 
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="AppIntegrityManagerServiceTestApp.apk->/data/local/tmp/AppIntegrityManagerServiceTestApp.apk" />
-    </target_preparer>
-
     <option name="test-tag" value="FrameworksServicesTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.frameworks.servicestests" />
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk
new file mode 100644
index 0000000..cc1f68c
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk
Binary files differ
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
new file mode 100644
index 0000000..9161d86
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 3f74681..7b2b30b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2087,7 +2087,7 @@
 
         FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
                 .setFactoryResetProtectionAccounts(new ArrayList<>())
-                .setFactoryResetProtectionDisabled(true)
+                .setFactoryResetProtectionEnabled(false)
                 .build();
         dpm.setFactoryResetProtectionPolicy(admin1, policy);
 
@@ -2105,7 +2105,7 @@
         setupProfileOwner();
 
         FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
-                .setFactoryResetProtectionDisabled(true)
+                .setFactoryResetProtectionEnabled(false)
                 .build();
 
         assertExpectException(SecurityException.class, null,
@@ -2157,7 +2157,7 @@
 
         FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
                 .setFactoryResetProtectionAccounts(new ArrayList<>())
-                .setFactoryResetProtectionDisabled(true)
+                .setFactoryResetProtectionEnabled(false)
                 .build();
 
         dpm.setFactoryResetProtectionPolicy(admin1, policy);
@@ -2177,8 +2177,8 @@
 
     private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
             FactoryResetProtectionPolicy actualPolicy) {
-        assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
-                expectedPolicy.isFactoryResetProtectionDisabled());
+        assertThat(actualPolicy.isFactoryResetProtectionEnabled()).isEqualTo(
+                expectedPolicy.isFactoryResetProtectionEnabled());
         assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
                 actualPolicy.getFactoryResetProtectionAccounts());
     }
@@ -2188,6 +2188,42 @@
         assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
     }
 
+    public void testSetKeyguardDisabledFeaturesWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+
+        dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+        assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+                DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+    }
+
+    public void testSetKeyguardDisabledFeaturesWithPO() throws Exception {
+        setupProfileOwner();
+
+        dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+
+        assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+                DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+    }
+
+    public void testSetKeyguardDisabledFeaturesWithPOOfOrganizationOwnedDevice()
+            throws Exception {
+        final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        parentDpm.setKeyguardDisabledFeatures(admin1,
+                DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+        assertThat(parentDpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+                DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+    }
+
     public void testSetApplicationHiddenWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -3781,35 +3817,35 @@
         assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
     }
 
-    public void testSetLockdownAdminConfiguredNetworksWithDO() throws Exception {
+    public void testSetConfiguredNetworksLockdownStateWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
-        dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+        dpm.setConfiguredNetworksLockdownState(admin1, true);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
 
-        dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+        dpm.setConfiguredNetworksLockdownState(admin1, false);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
     }
 
-    public void testSetLockdownAdminConfiguredNetworksWithPO() throws Exception {
+    public void testSetConfiguredNetworksLockdownStateWithPO() throws Exception {
         setupProfileOwner();
         assertExpectException(SecurityException.class, null,
-                () -> dpm.setLockdownAdminConfiguredNetworks(admin1, false));
+                () -> dpm.setConfiguredNetworksLockdownState(admin1, false));
         verify(getServices().settings, never()).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
     }
 
-    public void testSetLockdownAdminConfiguredNetworksWithPOOfOrganizationOwnedDevice()
+    public void testSetConfiguredNetworksLockdownStateWithPOOfOrganizationOwnedDevice()
             throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
-        dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+        dpm.setConfiguredNetworksLockdownState(admin1, true);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
 
-        dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+        dpm.setConfiguredNetworksLockdownState(admin1, false);
         verify(getServices().settings).settingsGlobalPutInt(
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
index bc853c6..e8818a3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -62,7 +62,7 @@
 
         FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
                 .setFactoryResetProtectionAccounts(accounts)
-                .setFactoryResetProtectionDisabled(true)
+                .setFactoryResetProtectionEnabled(false)
                 .build();
 
         testParcelAndUnparcel(policy);
@@ -77,7 +77,7 @@
 
         FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
                 .setFactoryResetProtectionAccounts(accounts)
-                .setFactoryResetProtectionDisabled(true)
+                .setFactoryResetProtectionEnabled(false)
                 .build();
 
         testParcelAndUnparcel(policy);
@@ -133,8 +133,8 @@
 
     private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
             FactoryResetProtectionPolicy actualPolicy) {
-        assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
-                actualPolicy.isFactoryResetProtectionDisabled());
+        assertEquals(expectedPolicy.isFactoryResetProtectionEnabled(),
+                actualPolicy.isFactoryResetProtectionEnabled());
         assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
                 actualPolicy.getFactoryResetProtectionAccounts());
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 8d5939a..7027185 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -202,7 +202,7 @@
         assertEquals(1, displayIds.length);
         final int displayId = displayIds[0];
         DisplayInfo info = bs.getDisplayInfo(displayId);
-        assertEquals(info.type, Display.TYPE_BUILT_IN);
+        assertEquals(info.type, Display.TYPE_INTERNAL);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java
new file mode 100644
index 0000000..607cd81
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Locale;
+
+/**
+ * Tests for static methods of {@link HdmiControlService} class.
+ */
+@SmallTest
+@RunWith(JUnit4.class)
+public class HdmiControlServiceStaticTest {
+
+    @Test
+    public void localToMenuLanguage_english() {
+        assertThat(HdmiControlService.localeToMenuLanguage(Locale.ENGLISH)).isEqualTo("eng");
+    }
+
+    @Test
+    public void localToMenuLanguage_german() {
+        assertThat(HdmiControlService.localeToMenuLanguage(Locale.GERMAN)).isEqualTo("ger");
+    }
+
+    @Test
+    public void localToMenuLanguage_taiwan() {
+        assertThat(HdmiControlService.localeToMenuLanguage(Locale.TAIWAN)).isEqualTo("chi");
+    }
+
+    @Test
+    public void localToMenuLanguage_macau() {
+        assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "MO"))).isEqualTo(
+                "chi");
+    }
+
+    @Test
+    public void localToMenuLanguage_hongkong() {
+        assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "HK"))).isEqualTo(
+                "chi");
+    }
+
+    @Test
+    public void localToMenuLanguage_chinese() {
+        assertThat(HdmiControlService.localeToMenuLanguage(Locale.CHINESE)).isEqualTo("zho");
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 8dae48c..be873bd 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -19,6 +19,7 @@
 import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
 import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
 import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
@@ -39,6 +40,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -65,6 +68,7 @@
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.testutils.TestUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,8 +80,9 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -85,7 +90,10 @@
 @RunWith(JUnit4.class)
 public class AppIntegrityManagerServiceImplTest {
     private static final String TEST_APP_PATH =
-            "/data/local/tmp/AppIntegrityManagerServiceTestApp.apk";
+            "AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk";
+
+    private static final String TEST_APP_TWO_CERT_PATH =
+            "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk";
 
     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
     private static final String VERSION = "version";
@@ -98,13 +106,16 @@
 
     // These are obtained by running the test and checking logcat.
     private static final String APP_CERT =
-            "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
-    private static final String INSTALLER_CERT =
-            "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
+            "C8A2E9BCCF597C2FB6DC66BEE293FC13F2FC47EC77BC6B2B0D52C11F51192AB8";
     // We use SHA256 for package names longer than 32 characters.
     private static final String INSTALLER_SHA256 =
             "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
 
+    private static final String DUMMY_APP_TWO_CERTS_CERT_1 =
+            "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94";
+    private static final String DUMMY_APP_TWO_CERTS_CERT_2 =
+            "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147";
+
     private static final String PLAY_STORE_PKG = "com.android.vending";
     private static final String ADB_INSTALLER = "adb";
     private static final String PLAY_STORE_CERT = "play_store_cert";
@@ -128,6 +139,7 @@
 
     private PackageManager mSpyPackageManager;
     private File mTestApk;
+    private File mTestApkTwoCerts;
 
     private final Context mRealContext = InstrumentationRegistry.getTargetContext();
     // under test
@@ -135,7 +147,15 @@
 
     @Before
     public void setup() throws Exception {
-        mTestApk = new File(TEST_APP_PATH);
+        mTestApk = File.createTempFile("AppIntegrity", ".apk");
+        try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_PATH)) {
+            Files.copy(inputStream, mTestApk.toPath(), REPLACE_EXISTING);
+        }
+
+        mTestApkTwoCerts = File.createTempFile("AppIntegrityTwoCerts", ".apk");
+        try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) {
+            Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING);
+        }
 
         mService =
                 new AppIntegrityManagerServiceImpl(
@@ -154,6 +174,12 @@
         when(mIntegrityFileManager.initialized()).thenReturn(true);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mTestApk.delete();
+        mTestApkTwoCerts.delete();
+    }
+
     @Test
     public void updateRuleSet_notAuthorized() throws Exception {
         makeUsSystemApp();
@@ -268,30 +294,51 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
 
         ArgumentCaptor<AppInstallMetadata> metadataCaptor =
                 ArgumentCaptor.forClass(AppInstallMetadata.class);
-        Map<String, String> allowedInstallers = new HashMap<>();
-        ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
-                ArgumentCaptor.forClass(allowedInstallers.getClass());
         verify(mRuleEvaluationEngine)
-                .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
+                .evaluate(metadataCaptor.capture());
         AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
-        allowedInstallers = allowedInstallersCaptor.getValue();
         assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
         assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
         assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
-        assertThat(appInstallMetadata.getInstallerCertificates()).containsExactly(INSTALLER_CERT);
+        // we cannot check installer cert because it seems to be device specific.
         assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
         assertFalse(appInstallMetadata.isPreInstalled());
         // These are hardcoded in the test apk android manifest
+        Map<String, String> allowedInstallers =
+                appInstallMetadata.getAllowedInstallersAndCertificates();
         assertEquals(2, allowedInstallers.size());
         assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG));
-        assertEquals(ADB_CERT, allowedInstallers.get(ADB_INSTALLER));
+        assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER));
+    }
+
+    @Test
+    public void handleBroadcast_correctArgs_multipleCerts() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        Intent intent = makeVerificationIntent();
+        intent.setDataAndType(Uri.fromFile(mTestApkTwoCerts), PACKAGE_MIME_TYPE);
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+        runJobInHandler();
+
+        ArgumentCaptor<AppInstallMetadata> metadataCaptor =
+                ArgumentCaptor.forClass(AppInstallMetadata.class);
+        verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
+        AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
+        assertThat(appInstallMetadata.getAppCertificates()).containsExactly(
+                DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2);
     }
 
     @Test
@@ -303,7 +350,7 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
@@ -321,7 +368,7 @@
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        when(mRuleEvaluationEngine.evaluate(any(), any()))
+        when(mRuleEvaluationEngine.evaluate(any()))
                 .thenReturn(
                         IntegrityCheckResult.deny(
                                 Arrays.asList(
@@ -349,7 +396,7 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
@@ -377,7 +424,7 @@
         verify(mMockContext, atLeastOnce())
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
-        when(mRuleEvaluationEngine.evaluate(any(), any()))
+        when(mRuleEvaluationEngine.evaluate(any()))
                 .thenReturn(IntegrityCheckResult.deny(/* rule= */ null));
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index b0b9596..0488745 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -22,6 +22,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.IntegrityFormula;
+import android.content.integrity.Rule;
 
 import com.android.server.integrity.IntegrityFileManager;
 import com.android.server.integrity.model.IntegrityCheckResult;
@@ -33,7 +35,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -60,13 +61,14 @@
 
         mEngine = new RuleEvaluationEngine(mIntegrityFileManager);
 
-        when(mIntegrityFileManager.readRules(any())).thenReturn(new ArrayList<>());
+        when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule(
+                IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY)));
+
+        when(mIntegrityFileManager.initialized()).thenReturn(true);
     }
 
     @Test
     public void testAllowedInstallers_empty() {
-        Map<String, String> allowedInstallers = Collections.emptyMap();
-
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
@@ -83,11 +85,11 @@
                         .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
 
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
     }
 
@@ -100,32 +102,36 @@
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
 
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(RANDOM_INSTALLER)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
 
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
 
         AppInstallMetadata appInstallMetadata4 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
     }
 
@@ -138,57 +144,37 @@
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
 
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
 
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
 
         AppInstallMetadata appInstallMetadata4 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
+                        .setAllowedInstallersAndCert(allowedInstallers)
                         .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
-        assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.DENY);
-    }
-
-    @Test
-    public void manifestBasedRuleEvaluationWorksEvenWhenIntegrityFilesAreUnavailable() {
-        when(mIntegrityFileManager.initialized()).thenReturn(false);
-
-        Map<String, String> allowedInstallers =
-                Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);
-
-        AppInstallMetadata appInstallMetadata1 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata2 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(RANDOM_INSTALLER)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+        assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index c0e7927..70d6cf8 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -46,7 +46,7 @@
                 .setContactUri(CONTACT_URI)
                 .setContactPhoneNumber(PHONE_NUMBER)
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
-                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED | ShortcutInfo.FLAG_CACHED)
                 .setImportant(true)
                 .setNotificationSilenced(true)
                 .setBubbled(true)
@@ -62,6 +62,7 @@
         assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber());
         assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
         assertTrue(conversationInfo.isShortcutLongLived());
+        assertTrue(conversationInfo.isShortcutCached());
         assertTrue(conversationInfo.isImportant());
         assertTrue(conversationInfo.isNotificationSilenced());
         assertTrue(conversationInfo.isBubbled());
@@ -83,6 +84,7 @@
         assertNull(conversationInfo.getContactPhoneNumber());
         assertNull(conversationInfo.getNotificationChannelId());
         assertFalse(conversationInfo.isShortcutLongLived());
+        assertFalse(conversationInfo.isShortcutCached());
         assertFalse(conversationInfo.isImportant());
         assertFalse(conversationInfo.isNotificationSilenced());
         assertFalse(conversationInfo.isBubbled());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 498d888..3ecd319 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -24,12 +24,14 @@
 
 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.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -41,6 +43,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.Person;
+import android.app.job.JobScheduler;
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
@@ -49,12 +52,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
+import android.content.pm.parsing.AndroidPackage;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.CancellationSignal;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -66,6 +72,7 @@
 import android.util.Range;
 
 import com.android.internal.app.ChooserActivity;
+import com.android.internal.content.PackageMonitor;
 import com.android.server.LocalServices;
 
 import org.junit.After;
@@ -84,6 +91,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 @RunWith(JUnit4.class)
 public final class DataManagerTest {
@@ -101,12 +109,14 @@
     @Mock private Context mContext;
     @Mock private ShortcutServiceInternal mShortcutServiceInternal;
     @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal;
+    @Mock private PackageManagerInternal mPackageManagerInternal;
     @Mock private ShortcutManager mShortcutManager;
     @Mock private UserManager mUserManager;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private TelecomManager mTelecomManager;
     @Mock private ContentResolver mContentResolver;
     @Mock private ScheduledExecutorService mExecutorService;
+    @Mock private JobScheduler mJobScheduler;
     @Mock private ScheduledFuture mScheduledFuture;
     @Mock private StatusBarNotification mStatusBarNotification;
     @Mock private Notification mNotification;
@@ -114,6 +124,7 @@
     private NotificationChannel mNotificationChannel;
     private DataManager mDataManager;
     private int mCallingUserId;
+    private CancellationSignal mCancellationSignal;
     private TestInjector mInjector;
 
     @Before
@@ -124,6 +135,15 @@
 
         addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
 
+        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternal);
+        AndroidPackage androidPackage = mock(AndroidPackage.class);
+        when(androidPackage.getPackageName()).thenReturn(TEST_PKG_NAME);
+        doAnswer(ans -> {
+            Consumer<AndroidPackage> callback = (Consumer<AndroidPackage>) ans.getArguments()[0];
+            callback.accept(androidPackage);
+            return null;
+        }).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt());
+
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
 
         Context originalContext = getInstrumentation().getTargetContext();
@@ -142,7 +162,12 @@
         when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
         when(mContext.getSystemServiceName(TelecomManager.class)).thenReturn(
                 Context.TELECOM_SERVICE);
-        when(mTelecomManager.getDefaultDialerPackage(anyInt())).thenReturn(TEST_PKG_NAME);
+        when(mTelecomManager.getDefaultDialerPackage(any(UserHandle.class)))
+                .thenReturn(TEST_PKG_NAME);
+
+        when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler);
+        when(mContext.getSystemServiceName(JobScheduler.class)).thenReturn(
+                Context.JOB_SCHEDULER_SERVICE);
 
         when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(
                 TimeUnit.class))).thenReturn(mScheduledFuture);
@@ -159,6 +184,7 @@
         when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
         when(mStatusBarNotification.getPackageName()).thenReturn(TEST_PKG_NAME);
         when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
+        when(mStatusBarNotification.getPostTime()).thenReturn(System.currentTimeMillis());
         when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
 
         mNotificationChannel = new NotificationChannel(
@@ -167,6 +193,8 @@
 
         mCallingUserId = USER_ID_PRIMARY;
 
+        mCancellationSignal = new CancellationSignal();
+
         mInjector = new TestInjector();
         mDataManager = new DataManager(mContext, mInjector);
         mDataManager.initialize();
@@ -176,6 +204,7 @@
     public void tearDown() {
         LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
     }
 
     @Test
@@ -297,6 +326,28 @@
     }
 
     @Test
+    public void testNotificationPosted() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+        listenerService.onNotificationPosted(mStatusBarNotification);
+
+        List<Range<Long>> activeNotificationOpenTimeSlots = new ArrayList<>();
+        mDataManager.forAllPackages(packageData ->
+                activeNotificationOpenTimeSlots.addAll(
+                        packageData.getEventHistory(TEST_SHORTCUT_ID)
+                                .getEventIndex(Event.TYPE_NOTIFICATION_POSTED)
+                                .getActiveTimeSlots()));
+        assertEquals(1, activeNotificationOpenTimeSlots.size());
+    }
+
+    @Test
     public void testNotificationOpened() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
 
@@ -365,6 +416,9 @@
         assertTrue(conversationInfo.isImportant());
         assertFalse(conversationInfo.isNotificationSilenced());
         assertFalse(conversationInfo.isDemoted());
+        verify(mShortcutServiceInternal).cacheShortcuts(
+                anyInt(), any(), eq(TEST_PKG_NAME),
+                eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
     }
 
     @Test
@@ -453,6 +507,101 @@
         assertEquals(2, activeTimeSlots.size());
     }
 
+    @Test
+    public void testDeleteUninstalledPackageDataOnPackageRemoved() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+        assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+
+        PackageMonitor packageMonitor = mDataManager.getPackageMonitorForTesting(USER_ID_PRIMARY);
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_PRIMARY);
+        intent.setData(Uri.parse("package:" + TEST_PKG_NAME));
+        packageMonitor.onReceive(mContext, intent);
+        assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+    }
+
+    @Test
+    public void testPruneUninstalledPackageData() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+        assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+
+        doAnswer(ans -> null).when(mPackageManagerInternal)
+                .forEachInstalledPackage(any(Consumer.class), anyInt());
+        mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+        assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+    }
+
+    @Test
+    public void testPruneCallEventsFromNonDialer() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+
+        long currentTimestamp = System.currentTimeMillis();
+        mInjector.mCallLogQueryHelper.mEventConsumer.accept(PHONE_NUMBER,
+                new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_CALL_OUTGOING));
+
+        List<Range<Long>> activeTimeSlots = new ArrayList<>();
+        mDataManager.forAllPackages(packageData ->
+                activeTimeSlots.addAll(
+                        packageData.getEventHistory(TEST_SHORTCUT_ID)
+                                .getEventIndex(Event.CALL_EVENT_TYPES)
+                                .getActiveTimeSlots()));
+        assertEquals(1, activeTimeSlots.size());
+
+        mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultDialer(null);
+        mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+        activeTimeSlots.clear();
+        mDataManager.forAllPackages(packageData ->
+                activeTimeSlots.addAll(
+                        packageData.getEventHistory(TEST_SHORTCUT_ID)
+                                .getEventIndex(Event.CALL_EVENT_TYPES)
+                                .getActiveTimeSlots()));
+        assertTrue(activeTimeSlots.isEmpty());
+    }
+
+    @Test
+    public void testPruneSmsEventsFromNonDefaultSmsApp() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+        mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME);
+
+        long currentTimestamp = System.currentTimeMillis();
+        mInjector.mMmsQueryHelper.mEventConsumer.accept(PHONE_NUMBER,
+                new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_SMS_OUTGOING));
+
+        List<Range<Long>> activeTimeSlots = new ArrayList<>();
+        mDataManager.forAllPackages(packageData ->
+                activeTimeSlots.addAll(
+                        packageData.getEventHistory(TEST_SHORTCUT_ID)
+                                .getEventIndex(Event.SMS_EVENT_TYPES)
+                                .getActiveTimeSlots()));
+        assertEquals(1, activeTimeSlots.size());
+
+        mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(null);
+        mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+        activeTimeSlots.clear();
+        mDataManager.forAllPackages(packageData ->
+                activeTimeSlots.addAll(
+                        packageData.getEventHistory(TEST_SHORTCUT_ID)
+                                .getEventIndex(Event.SMS_EVENT_TYPES)
+                                .getActiveTimeSlots()));
+        assertTrue(activeTimeSlots.isEmpty());
+    }
+
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
         LocalServices.removeServiceForTest(clazz);
         LocalServices.addService(clazz, mock);
@@ -466,6 +615,7 @@
         when(mockContext.getUser()).thenReturn(UserHandle.of(userId));
         ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, id)
                 .setShortLabel(id)
+                .setLongLived(true)
                 .setIntent(new Intent("TestIntent"));
         if (person != null) {
             builder.setPersons(new Person[] {person});
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
index 1ddc21e..e52cdf5 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
@@ -81,8 +81,10 @@
     @Test
     public void testGetEventHistory() {
         EventStore eventStore = mPackageData.getEventStore();
-        eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1);
-        eventStore.getOrCreateLocusEventHistory(LOCUS_ID).addEvent(mE2);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+                .addEvent(mE1);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId())
+                .addEvent(mE2);
 
         EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
         List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
@@ -96,9 +98,10 @@
         mIsDefaultDialer = true;
         mIsDefaultSmsApp = true;
         EventStore eventStore = mPackageData.getEventStore();
-        eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1);
-        eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3);
-        eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+                .addEvent(mE1);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
 
         assertTrue(mPackageData.isDefaultDialer());
         assertTrue(mPackageData.isDefaultSmsApp());
@@ -113,9 +116,10 @@
     @Test
     public void testGetEventHistoryNotDefaultDialerOrSmsApp() {
         EventStore eventStore = mPackageData.getEventStore();
-        eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1);
-        eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3);
-        eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+                .addEvent(mE1);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
 
         assertFalse(mPackageData.isDefaultDialer());
         assertFalse(mPackageData.isDefaultSmsApp());
@@ -125,6 +129,61 @@
         assertEventEquals(mE1, events.get(0));
     }
 
+    @Test
+    public void testDeleteConversationData() {
+        mIsDefaultDialer = true;
+        mIsDefaultSmsApp = true;
+        EventStore eventStore = mPackageData.getEventStore();
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+                .addEvent(mE1);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId())
+                .addEvent(mE2);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
+
+        EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+        List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(4, events.size());
+
+        mPackageData.deleteDataForConversation(SHORTCUT_ID);
+
+        eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+        events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertTrue(events.isEmpty());
+    }
+
+    @Test
+    public void testPruneOrphanEvents() {
+        mIsDefaultDialer = true;
+        mIsDefaultSmsApp = true;
+        EventStore eventStore = mPackageData.getEventStore();
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+                .addEvent(mE1);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId())
+                .addEvent(mE2);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+        eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
+
+        EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+        List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(4, events.size());
+
+        ConversationInfo conversationInfo = new ConversationInfo.Builder()
+                .setShortcutId(SHORTCUT_ID)
+                .setLocusId(null)
+                .setContactUri(null)
+                .setContactPhoneNumber(null)
+                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+                .build();
+        mPackageData.getConversationStore().addOrUpdate(conversationInfo);
+        mPackageData.pruneOrphanEvents();
+        eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+        events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(1, events.size());
+        // Only the shortcut based event is kept. All the other events are deleted.
+        assertEventEquals(mE1, events.get(0));
+    }
+
     private void assertEventEquals(Event expected, Event actual) {
         assertEquals(expected.getTimestamp(), actual.getTimestamp());
         assertEquals(expected.getType(), actual.getType());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index 01d9dc0..d444466 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -19,9 +19,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
@@ -59,7 +59,6 @@
     private static final String PKG_NAME = "pkg";
     private static final String ACTIVITY_NAME = "TestActivity";
     private static final String SHORTCUT_ID = "abc";
-    private static final String NOTIFICATION_CHANNEL_ID = "test : abc";
     private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
     private static final LocusId LOCUS_ID_2 = new LocusId("locus_2");
 
@@ -83,7 +82,6 @@
                 scheduledExecutorService, testDir, helper);
         mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
                 .setShortcutId(SHORTCUT_ID)
-                .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setLocusId(LOCUS_ID_1)
                 .build();
 
@@ -114,19 +112,6 @@
     }
 
     @Test
-    public void testQueryNotificationInterruptionEvent() {
-        addUsageEvents(createNotificationInterruptionEvent(100L));
-
-        assertTrue(mHelper.querySince(50L));
-        assertEquals(100L, mHelper.getLastEventTimestamp());
-        Event expectedEvent = new Event(100L, Event.TYPE_NOTIFICATION_POSTED);
-        List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents(
-                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
-        assertEquals(1, events.size());
-        assertEquals(expectedEvent, events.get(0));
-    }
-
-    @Test
     public void testInAppConversationSwitch() {
         addUsageEvents(
                 createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
@@ -189,7 +174,7 @@
     private void addUsageEvents(UsageEvents.Event... events) {
         UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
         when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
-                anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents);
+                eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents);
     }
 
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
@@ -203,13 +188,6 @@
         return e;
     }
 
-    private static UsageEvents.Event createNotificationInterruptionEvent(long timestamp) {
-        UsageEvents.Event e = createUsageEvent(UsageEvents.Event.NOTIFICATION_INTERRUPTION,
-                timestamp);
-        e.mNotificationChannelId = NOTIFICATION_CHANNEL_ID;
-        return e;
-    }
-
     private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) {
         UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp);
         e.mClass = ACTIVITY_NAME;
@@ -288,14 +266,13 @@
 
         @Override
         @NonNull
-        EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) {
-            return mShortcutEventHistory;
-        }
-
-        @Override
-        @NonNull
-        EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) {
-            return mLocusEventHistory;
+        EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) {
+            if (category == EventStore.CATEGORY_SHORTCUT_BASED) {
+                return mShortcutEventHistory;
+            } else if (category == EventStore.CATEGORY_LOCUS_ID_BASED) {
+                return mLocusEventHistory;
+            }
+            throw new UnsupportedOperationException();
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index e28d13a..9670658 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -37,6 +37,7 @@
 import android.content.pm.parsing.ParsingPackage;
 import android.os.Build;
 import android.os.Process;
+import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
@@ -57,6 +58,7 @@
 import java.util.Map;
 import java.util.Set;
 
+@Presubmit
 @RunWith(JUnit4.class)
 public class AppsFilterTest {
 
@@ -314,7 +316,8 @@
                 b -> b.setSigningDetails(frameworkSigningDetails));
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
                 DUMMY_TARGET_UID,
-                b -> b.setSigningDetails(frameworkSigningDetails));
+                b -> b.setSigningDetails(frameworkSigningDetails)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_UID,
                 b -> b.setSigningDetails(otherSigningDetails));
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 4d0481be..eef9012 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -48,8 +49,9 @@
  * Build/Install/Run:
  *  atest FrameworksServicesTests:SystemConfigTest
  */
-@RunWith(AndroidJUnit4.class)
 @SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
 public class SystemConfigTest {
     private static final String LOG_TAG = "SystemConfigTest";
 
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e768205..9e57763 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -145,6 +145,7 @@
     static class MyInjector extends AppStandbyController.Injector {
         long mElapsedRealtime;
         boolean mIsAppIdleEnabled = true;
+        boolean mIsCharging;
         List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
         boolean mDisplayOn;
         DisplayManager.DisplayListener mDisplayListener;
@@ -179,6 +180,11 @@
         }
 
         @Override
+        boolean isCharging() {
+            return mIsCharging;
+        }
+
+        @Override
         boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
             return mPowerSaveWhitelistExceptIdle.contains(packageName);
         }
@@ -281,6 +287,13 @@
         } catch (PackageManager.NameNotFoundException nnfe) {}
     }
 
+    private void setChargingState(AppStandbyController controller, boolean charging) {
+        mInjector.mIsCharging = charging;
+        if (controller != null) {
+            controller.setChargingState(charging);
+        }
+    }
+
     private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
         mInjector.mIsAppIdleEnabled = enabled;
         if (controller != null) {
@@ -297,6 +310,7 @@
         controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         mInjector.setDisplayOn(false);
         mInjector.setDisplayOn(true);
+        setChargingState(controller, false);
         controller.checkIdleStates(USER_ID);
         assertNotEquals(STANDBY_BUCKET_EXEMPTED,
                 controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -324,6 +338,46 @@
                         mInjector.mElapsedRealtime, false));
     }
 
+    @Test
+    public void testIsAppIdle_Charging() throws Exception {
+        setChargingState(mController, false);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setChargingState(mController, true);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setChargingState(mController, false);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+    }
+
+    @Test
+    public void testIsAppIdle_Enabled() throws Exception {
+        setChargingState(mController, false);
+        setAppIdleEnabled(mController, true);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setAppIdleEnabled(mController, false);
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+        setAppIdleEnabled(mController, true);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+    }
+
     private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
         mInjector.mElapsedRealtime = elapsedTime;
         controller.checkIdleStates(USER_ID);
diff --git a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/Android.bp b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/Android.bp
deleted file mode 100644
index 9aaa37d..0000000
--- a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/Android.bp
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_test_helper_app {
-    name: "AppIntegrityManagerServiceTestApp",
-
-    test_suites: ["device-tests"],
-
-    certificate: "platform",
-}
diff --git a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
deleted file mode 100644
index f5dbf43..0000000
--- a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2019 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.appintegritymanager.test.app"
-    android:versionCode="5000">
-
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" />
-
-    <application android:hasCode="false">
-        <meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb|"/>
-    </application>
-</manifest>
-
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
index 458117d..6eaf546 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
@@ -21,6 +21,7 @@
 import android.app.NotificationHistory.HistoricalNotification;
 import android.graphics.drawable.Icon;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +58,10 @@
         String expectedText = "text" + index;
         Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
                 index);
+        String conversationId = null;
+        if (index % 2 == 0) {
+            conversationId = "convo" + index;
+        }
 
         return new HistoricalNotification.Builder()
                 .setPackage(packageName)
@@ -68,6 +73,7 @@
                 .setTitle(expectedTitle)
                 .setText(expectedText)
                 .setIcon(expectedIcon)
+                .setConversationId(conversationId)
                 .build();
     }
 
@@ -139,6 +145,9 @@
                 expectedStrings.add(n.getPackage());
                 expectedStrings.add(n.getChannelName());
                 expectedStrings.add(n.getChannelId());
+                if (!TextUtils.isEmpty(n.getConversationId())) {
+                    expectedStrings.add(n.getConversationId());
+                }
                 expectedEntries.add(n);
             }
             history.addNotificationToWrite(n);
@@ -178,6 +187,9 @@
                 expectedStrings.add(n.getPackage());
                 expectedStrings.add(n.getChannelName());
                 expectedStrings.add(n.getChannelId());
+                if (!TextUtils.isEmpty(n.getConversationId())) {
+                    expectedStrings.add(n.getConversationId());
+                }
                 expectedEntries.add(n);
             }
             history.addNotificationToWrite(n);
@@ -227,6 +239,9 @@
                 expectedStrings.add(n.getPackage());
                 expectedStrings.add(n.getChannelName());
                 expectedStrings.add(n.getChannelId());
+                if (n.getConversationId() != null) {
+                    expectedStrings.add(n.getConversationId());
+                }
                 expectedEntries.add(n);
             }
             history.addNotificationToWrite(n);
@@ -264,6 +279,9 @@
             expectedStrings.add(n.getPackage());
             expectedStrings.add(n.getChannelName());
             expectedStrings.add(n.getChannelId());
+            if (n.getConversationId() != null) {
+                expectedStrings.add(n.getConversationId());
+            }
             history.addNotificationToWrite(n);
         }
         history.poolStringsFromNotifications();
@@ -279,6 +297,9 @@
             expectedStrings.add(n.getPackage());
             expectedStrings.add(n.getChannelName());
             expectedStrings.add(n.getChannelId());
+            if (n.getConversationId() != null) {
+                expectedStrings.add(n.getConversationId());
+            }
             actualHistory.addNotificationToWrite(n);
         }
         actualHistory.poolStringsFromNotifications();
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 a0f7f5b..e5ffb4d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -51,8 +51,6 @@
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 
@@ -1144,14 +1142,15 @@
     }
 
     @Test
-    public void testEnqueueNotificationWithTag_WritesExpectedLog() throws Exception {
+    public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception {
         final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog";
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         assertEquals(1, mNotificationRecordLogger.getCalls().size());
+
         NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0);
-        assertTrue(call.shouldLog);
+        assertTrue(call.shouldLogReported);
         assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
                 call.event);
         assertNotNull(call.r);
@@ -1161,7 +1160,6 @@
         assertEquals(PKG, call.r.getSbn().getPackageName());
         assertEquals(0, call.r.getSbn().getId());
         assertEquals(tag, call.r.getSbn().getTag());
-        assertNotNull(call.r.getSbn().getInstanceId());
         assertEquals(0, call.getInstanceId());  // Fake instance IDs are assigned in order
     }
 
@@ -1180,13 +1178,13 @@
         waitForIdle();
         assertEquals(2, mNotificationRecordLogger.getCalls().size());
 
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+        assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
                 mNotificationRecordLogger.get(0).event);
         assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
 
-        assertTrue(mNotificationRecordLogger.get(1).shouldLog);
+        assertTrue(mNotificationRecordLogger.get(1).shouldLogReported);
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED,
                 mNotificationRecordLogger.get(1).event);
@@ -1195,16 +1193,37 @@
     }
 
     @Test
-    public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates() throws Exception {
-        final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates";
+    public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception {
+        final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate";
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
                 generateNotificationRecord(null).getNotification(), 0);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         assertEquals(2, mNotificationRecordLogger.getCalls().size());
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
-        assertFalse(mNotificationRecordLogger.get(1).shouldLog);
+        assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+        assertEquals(
+                NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+                mNotificationRecordLogger.get(0).event);
+        assertFalse(mNotificationRecordLogger.get(1).shouldLogReported);
+        assertNull(mNotificationRecordLogger.get(1).event);
+    }
+
+    @Test
+    public void testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate() throws Exception {
+        final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate";
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
+                generateNotificationRecord(null).getNotification(),
+                0);
+        final Notification notif = generateNotificationRecord(null).getNotification();
+        notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
+        waitForIdle();
+        assertEquals(2, mNotificationRecordLogger.getCalls().size());
+        assertEquals(
+                NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+                mNotificationRecordLogger.get(0).event);
+        assertNull(mNotificationRecordLogger.get(1).event);
     }
 
     @Test
@@ -1224,20 +1243,18 @@
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
                 mNotificationRecordLogger.get(0).event);
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+        assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
         assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
 
-        assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason);
         assertEquals(
                 NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL,
                 mNotificationRecordLogger.get(1).event);
-        assertTrue(mNotificationRecordLogger.get(1).shouldLog);
         assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
 
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
                 mNotificationRecordLogger.get(2).event);
-        assertTrue(mNotificationRecordLogger.get(2).shouldLog);
+        assertTrue(mNotificationRecordLogger.get(2).shouldLogReported);
         // New instance ID because notification was canceled before re-post
         assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId());
     }
@@ -1267,6 +1284,27 @@
     }
 
     @Test
+    public void testPostCancelPostNotifiesListeners() throws Exception {
+        // WHEN a notification is posted
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
+                sbn.getNotification(), sbn.getUserId());
+        // THEN it is canceled
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        // THEN it is posted again (before the cancel has a chance to finish)
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
+                sbn.getNotification(), sbn.getUserId());
+        // THEN the later enqueue isn't swallowed by the cancel. I.e., ordering is respected
+        waitForIdle();
+
+        // The final enqueue made it to the listener instead of being canceled
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelNotificationWhilePostedAndEnqueued", 0,
@@ -2605,6 +2643,33 @@
     }
 
     @Test
+    public void testSystemNotificationListenerCanUnsnooze() throws Exception {
+        final NotificationRecord nr = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testSystemNotificationListenerCanUnsnooze",
+                nr.getSbn().getId(), nr.getSbn().getNotification(),
+                nr.getSbn().getUserId());
+        waitForIdle();
+        NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+                mService.new SnoozeNotificationRunnable(
+                        nr.getKey(), 100, null);
+        snoozeNotificationRunnable.run();
+
+        ManagedServices.ManagedServiceInfo listener = mListeners.new ManagedServiceInfo(
+                null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
+        listener.isSystem = true;
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(listener);
+
+        mBinderService.unsnoozeNotificationFromSystemListener(null, nr.getKey());
+        waitForIdle();
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertNotNull(notifs[0].getKey());//mService.getNotificationRecord(nr.getSbn().getKey()));
+    }
+
+    @Test
     public void testSetListenerAccessForUser() throws Exception {
         UserHandle user = UserHandle.of(10);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
@@ -3396,11 +3461,9 @@
         // so we only get the cancel notification.
         assertEquals(1, mNotificationRecordLogger.getCalls().size());
 
-        assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason);
         assertEquals(
                 NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD,
                 mNotificationRecordLogger.get(0).event);
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
         assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
     }
 
@@ -4326,6 +4389,34 @@
     }
 
     @Test
+    public void testOnNotificationVisibilityChanged_triggersVisibilityLog() {
+        final NotificationRecord r = generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true);
+        r.setTextChanged(true);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationVisibilityChanged(new NotificationVisibility[]
+                {NotificationVisibility.obtain(r.getKey(), 1, 1, true)},
+                new NotificationVisibility[]{});
+
+        assertEquals(1, mNotificationRecordLogger.getCalls().size());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN,
+                mNotificationRecordLogger.get(0).event);
+        assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
+
+        mService.mNotificationDelegate.onNotificationVisibilityChanged(
+                new NotificationVisibility[]{},
+                new NotificationVisibility[]
+                        {NotificationVisibility.obtain(r.getKey(), 1, 1, true)}
+        );
+
+        assertEquals(2, mNotificationRecordLogger.getCalls().size());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE,
+                mNotificationRecordLogger.get(1).event);
+        assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
+    }
+
+    @Test
     public void testSetNotificationsShownFromListener_triggersInterruptionUsageStat()
             throws RemoteException {
         final NotificationRecord r = generateNotificationRecord(
@@ -5373,21 +5464,7 @@
     }
 
     @Test
-    public void testCancelAllNotifications_cancelsBubble() throws Exception {
-        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
-        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
-        mService.addNotification(nr);
-
-        mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
-        waitForIdle();
-
-        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
-        assertEquals(0, notifs.length);
-        assertEquals(0, mService.getNotificationRecordCount());
-    }
-
-    @Test
-    public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+    public void testCancelNotificationsFromApp_cancelsBubbles() throws Exception {
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
         nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
 
@@ -5413,6 +5490,20 @@
     }
 
     @Test
+    public void testCancelAllNotificationsFromApp_cancelsBubble() throws Exception {
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+        mService.addNotification(nr);
+
+        mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
     public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
         final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
@@ -5448,6 +5539,25 @@
     }
 
     @Test
+    public void testCancelAllNotificationsFromStatusBar_ignoresBubble() throws Exception {
+        // GIVEN a notification bubble
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+        mService.addNotification(nr);
+
+        // WHEN the status bar clears all notifications
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+                nr.getSbn().getUserId());
+        waitForIdle();
+
+        // THEN the bubble notification does not get removed
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+
+    @Test
     public void testGetAllowedAssistantAdjustments() throws Exception {
         List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
         assertNotNull(capabilities);
@@ -6105,6 +6215,26 @@
     }
 
     @Test
+    public void testNotificationBubbles_bubbleStays_whenClicked()
+            throws Exception {
+        // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
+        mService.addNotification(nr);
+
+        // WHEN we click the notification
+        final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true);
+        mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+                nr.getKey(), nv);
+        waitForIdle();
+
+        // THEN the bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
+
+    @Test
     public void testLoadDefaultApprovedServices_emptyResources() {
         TestableResources tr = mContext.getOrCreateTestableResources();
         tr.addOverride(com.android.internal.R.string.config_defaultListenerAccessPackages, "");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index b120dbe..2a17bae 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -26,24 +26,26 @@
  */
 class NotificationRecordLoggerFake implements NotificationRecordLogger {
     static class CallRecord extends NotificationRecordPair {
-        static final int INVALID = -1;
-        public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID;
-        public boolean shouldLog;
         public UiEventLogger.UiEventEnum event;
+
+        // The following fields are only relevant to maybeLogNotificationPosted() calls.
+        static final int INVALID = -1;
+        public int position = INVALID, buzzBeepBlink = INVALID;
+        public boolean shouldLogReported;
+
         CallRecord(NotificationRecord r, NotificationRecord old, int position,
                 int buzzBeepBlink) {
             super(r, old);
-
             this.position = position;
             this.buzzBeepBlink = buzzBeepBlink;
-            shouldLog = shouldLog(buzzBeepBlink);
-            event = NotificationReportedEvent.fromRecordPair(this);
+            shouldLogReported = shouldLogReported(buzzBeepBlink);
+            event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null;
         }
-        CallRecord(NotificationRecord r, int reason, int dismissalSurface) {
+
+        CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
             super(r, null);
-            this.reason = reason;
-            shouldLog = true;
-            event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface);
+            shouldLogReported = false;
+            this.event = event;
         }
     }
     private List<CallRecord> mCalls = new ArrayList<>();
@@ -57,14 +59,19 @@
     }
 
     @Override
-    public void logNotificationReported(NotificationRecord r, NotificationRecord old,
+    public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
             int position, int buzzBeepBlink) {
         mCalls.add(new CallRecord(r, old, position, buzzBeepBlink));
     }
 
     @Override
     public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
-        mCalls.add(new CallRecord(r, reason, dismissalSurface));
+        mCalls.add(new CallRecord(r,
+                NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface)));
     }
 
+    @Override
+    public void logNotificationVisibility(NotificationRecord r, boolean visible) {
+        mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible)));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 3186d53..1dd0b1a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -15,15 +15,18 @@
  */
 package com.android.server.notification;
 
+import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -322,8 +325,12 @@
         mSnoozeHelper.snooze(r, 1000);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
         mSnoozeHelper.snooze(r2, 1000);
+        reset(mAm);
         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM);
         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).cancel(captor.capture());
+        assertEquals(r.getKey(), captor.getValue().getIntent().getStringExtra(EXTRA_KEY));
     }
 
     @Test
@@ -332,8 +339,10 @@
         mSnoozeHelper.snooze(r, 1000);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
         mSnoozeHelper.snooze(r2, 1000);
+        reset(mAm);
         mSnoozeHelper.repost(r.getKey());
         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
+        verify(mAm).cancel(any(PendingIntent.class));
     }
 
     @Test
@@ -370,31 +379,7 @@
     }
 
     @Test
-    public void testGetSnoozedByUser() throws Exception {
-        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
-        NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
-        NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
-        NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
-        mSnoozeHelper.snooze(r, 1000);
-        mSnoozeHelper.snooze(r2, 1000);
-        mSnoozeHelper.snooze(r3, 1000);
-        mSnoozeHelper.snooze(r4, 1000);
-        IntArray profileIds = new IntArray();
-        profileIds.add(UserHandle.USER_SYSTEM);
-        when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
-        assertEquals(3, mSnoozeHelper.getSnoozed().size());
-        profileIds = new IntArray();
-        profileIds.add(UserHandle.USER_CURRENT);
-        when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
-        assertEquals(1, mSnoozeHelper.getSnoozed().size());
-    }
-
-    @Test
-    public void testGetSnoozedByUser_managedProfiles() throws Exception {
-        IntArray profileIds = new IntArray();
-        profileIds.add(UserHandle.USER_CURRENT);
-        profileIds.add(UserHandle.USER_SYSTEM);
-        when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+    public void testGetSnoozedBy() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index 9647178..e8c0362 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -144,4 +144,33 @@
                 anyInt() /* reason */, anyString() /* packageName */);
         verify(taskChangeNotifier, never()).notifyActivityDismissingDockedStack();
     }
+
+    /**
+     * Ensures that notify focus task changes.
+     */
+    @Test
+    public void testNotifyTaskFocusChanged() {
+        final ActivityRecord fullScreenActivityA = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        final Task taskA = fullScreenActivityA.getTask();
+
+        final TaskChangeNotificationController taskChangeNotifier =
+                mService.getTaskChangeNotificationController();
+        spyOn(taskChangeNotifier);
+
+        mService.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA");
+        verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
+                eq(true) /* focused */);
+        reset(taskChangeNotifier);
+
+        final ActivityRecord fullScreenActivityB = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        final Task taskB = fullScreenActivityB.getTask();
+
+        mService.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
+        verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
+                eq(false) /* focused */);
+        verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */,
+                eq(true) /* focused */);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index fa182d6..d46975c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -29,6 +29,7 @@
 import static android.app.ActivityManager.START_SWITCHES_CANCELED;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -64,8 +65,10 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -80,6 +83,9 @@
 import android.platform.test.annotations.Presubmit;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.Gravity;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
 
 import androidx.test.filters.SmallTest;
 
@@ -999,7 +1005,8 @@
         assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
 
         // Move activity to split-screen-primary stack and make sure it has the focus.
-        top.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        TestSplitOrganizer splitOrg = new TestSplitOrganizer(mService, top.getDisplayId());
+        splitOrg.mPrimary.addChild(top.getRootTask(), 0 /* index */);
         top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
 
         // Activity must landed on split-screen-secondary when launch adjacent.
@@ -1022,4 +1029,58 @@
 
         verify(recentTasks, times(1)).add(any());
     }
+
+    static class TestSplitOrganizer extends ITaskOrganizer.Stub {
+        final ActivityTaskManagerService mService;
+        TaskTile mPrimary;
+        TaskTile mSecondary;
+        boolean mInSplit = false;
+        int mDisplayId;
+        TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
+            mService = service;
+            mDisplayId = displayId;
+            mService.mTaskOrganizerController.registerTaskOrganizer(this,
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+            mService.mTaskOrganizerController.registerTaskOrganizer(this,
+                    WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+            IWindowContainer primary = mService.mTaskOrganizerController.createRootTask(
+                    displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
+            mPrimary = TaskTile.forToken(primary.asBinder());
+            IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask(
+                    displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
+            mSecondary = TaskTile.forToken(secondary.asBinder());
+        }
+        @Override
+        public void taskAppeared(ActivityManager.RunningTaskInfo info) {
+        }
+        @Override
+        public void taskVanished(IWindowContainer wc) {
+        }
+        @Override
+        public void transactionReady(int id, SurfaceControl.Transaction t) {
+        }
+        @Override
+        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
+            if (mInSplit) {
+                return;
+            }
+            if (info.topActivityType != ACTIVITY_TYPE_UNDEFINED) {
+                if (info.configuration.windowConfiguration.getWindowingMode()
+                        == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                    mInSplit = true;
+                    mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
+                            mSecondary.mRemoteToken);
+                    // move everything to secondary because test expects this but usually sysui
+                    // does it.
+                    DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
+                    for (int i = dc.getStackCount() - 1; i >= 0; --i) {
+                        if (!WindowConfiguration.isSplitScreenWindowingMode(
+                                dc.getStackAt(i).getWindowingMode())) {
+                            mSecondary.addChild(dc.getStackAt(i), 0);
+                        }
+                    }
+                }
+            }
+        }
+    };
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
new file mode 100644
index 0000000..8ac1d24
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Presubmit
+public class DisplayAreaPolicyBuilderTest {
+
+    @Rule
+    public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+
+    private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null);
+
+    @Test
+    public void testBuilder() {
+        WindowManagerService wms = mSystemServices.getWindowManagerService();
+        DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
+        DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
+        DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks");
+
+        final Feature foo;
+        final Feature bar;
+
+        DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .addFeature(foo = new Feature.Builder(mPolicy, "Foo")
+                        .upTo(TYPE_STATUS_BAR)
+                        .and(TYPE_NAVIGATION_BAR)
+                        .build())
+                .addFeature(bar = new Feature.Builder(mPolicy, "Bar")
+                        .all()
+                        .except(TYPE_STATUS_BAR)
+                        .build())
+                .build(wms, mock(DisplayContent.class), root, ime, tasks);
+
+        policy.attachDisplayAreas();
+
+        assertThat(policy.getDisplayAreas(foo), is(not(empty())));
+        assertThat(policy.getDisplayAreas(bar), is(not(empty())));
+
+        assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
+                is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+        assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
+                is(not(decendantOfOneOf(policy.getDisplayAreas(bar)))));
+
+        assertThat(tasks,
+                is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+        assertThat(tasks,
+                is(decendantOfOneOf(policy.getDisplayAreas(bar))));
+
+        assertThat(ime,
+                is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+        assertThat(ime,
+                is(decendantOfOneOf(policy.getDisplayAreas(bar))));
+
+        List<DisplayArea<?>> actualOrder = collectLeafAreas(root);
+        Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks);
+        actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
+
+        Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
+                v -> v.stream().min(Integer::compareTo).get());
+        Map<DisplayArea<?>, Integer> expectedByMaxLayer = mapValues(zSets,
+                v -> v.stream().max(Integer::compareTo).get());
+
+        assertThat(expectedByMinLayer, is(equalTo(expectedByMaxLayer)));
+        assertThat(actualOrder, is(equalTo(expectedByMaxLayer)));
+    }
+
+    private <K, V, R> Map<K, R> mapValues(Map<K, V> zSets, Function<V, R> f) {
+        return zSets.entrySet().stream().collect(Collectors.toMap(
+                Map.Entry::getKey,
+                e -> f.apply(e.getValue())));
+    }
+
+    private List<DisplayArea<?>> collectLeafAreas(DisplayArea<?> root) {
+        ArrayList<DisplayArea<?>> leafs = new ArrayList<>();
+        traverseLeafAreas(root, leafs::add);
+        return leafs;
+    }
+
+    private Map<DisplayArea<?>, Set<Integer>> calculateZSets(
+            DisplayAreaPolicyBuilder.Result policy, DisplayArea.Root root,
+            DisplayArea<WindowContainer> ime,
+            DisplayArea<ActivityStack> tasks) {
+        Map<DisplayArea<?>, Set<Integer>> zSets = new HashMap<>();
+        int[] types = {TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_PRESENTATION,
+                TYPE_APPLICATION_OVERLAY};
+        for (int type : types) {
+            WindowToken token = tokenOfType(type);
+            recordLayer(policy.findAreaForToken(token), token.getWindowLayerFromType(), zSets);
+        }
+        recordLayer(tasks, APPLICATION_LAYER, zSets);
+        recordLayer(ime, mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD), zSets);
+        return zSets;
+    }
+
+    private void recordLayer(DisplayArea<?> area, int layer,
+            Map<DisplayArea<?>,  Set<Integer>> zSets) {
+        zSets.computeIfAbsent(area, k -> new HashSet<>()).add(layer);
+    }
+
+    private Matcher<WindowContainer> decendantOfOneOf(List<? extends WindowContainer> expected) {
+        return new CustomTypeSafeMatcher<WindowContainer>("descendant of one of " + expected) {
+            @Override
+            protected boolean matchesSafely(WindowContainer actual) {
+                for (WindowContainer expected : expected) {
+                    WindowContainer candidate = actual;
+                    while (candidate != null && candidate.getParent() != candidate) {
+                        if (candidate.getParent() == expected) {
+                            return true;
+                        }
+                        candidate = candidate.getParent();
+                    }
+                }
+                return false;
+            }
+
+            @Override
+            protected void describeMismatchSafely(WindowContainer item,
+                    Description description) {
+                description.appendText("was ").appendValue(item);
+                while (item != null && item.getParent() != item) {
+                    item = item.getParent();
+                    description.appendText(", child of ").appendValue(item);
+                }
+            }
+        };
+    }
+
+    private WindowToken tokenOfType(int type) {
+        WindowToken m = mock(WindowToken.class);
+        when(m.getWindowLayerFromType()).thenReturn(mPolicy.getWindowLayerFromTypeLw(type));
+        return m;
+    }
+
+    private static void traverseLeafAreas(DisplayArea<?> root, Consumer<DisplayArea<?>> consumer) {
+        boolean leaf = true;
+        for (int i = 0; i < root.getChildCount(); i++) {
+            WindowContainer child = root.getChildAt(i);
+            if (child instanceof DisplayArea<?>) {
+                traverseLeafAreas((DisplayArea<?>) child, consumer);
+                leaf = false;
+            }
+        }
+        if (leaf) {
+            consumer.accept(root);
+        }
+    }
+
+    private static class SurfacelessDisplayAreaRoot extends DisplayArea.Root {
+
+        SurfacelessDisplayAreaRoot(WindowManagerService wms) {
+            super(wms);
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceControlBuilder();
+        }
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index c1a1d5e..3120631 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -34,13 +34,13 @@
     @Test
     public void testFromResources_emptyProvider() {
         Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")),
-                Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+                Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class));
     }
 
     @Test
     public void testFromResources_nullProvider() {
         Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)),
-                Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+                Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2ea00ce..3b6816a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1033,6 +1033,54 @@
         assertTrue(continued[0]);
     }
 
+    @Test
+    public void testGetOrCreateRootHomeTask_defaultDisplay() {
+        DisplayContent defaultDisplay = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
+
+        // Remove the current home stack if it exists so a new one can be created below.
+        ActivityStack homeTask = defaultDisplay.getRootHomeTask();
+        if (homeTask != null) {
+            defaultDisplay.removeStack(homeTask);
+        }
+        assertNull(defaultDisplay.getRootHomeTask());
+
+        assertNotNull(defaultDisplay.getOrCreateRootHomeTask());
+    }
+
+    @Test
+    public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
+        DisplayContent display = createNewDisplay();
+        doReturn(true).when(display).supportsSystemDecorations();
+        doReturn(false).when(display).isUntrustedVirtualDisplay();
+
+        // Remove the current home stack if it exists so a new one can be created below.
+        ActivityStack homeTask = display.getRootHomeTask();
+        if (homeTask != null) {
+            display.removeStack(homeTask);
+        }
+        assertNull(display.getRootHomeTask());
+
+        assertNotNull(display.getOrCreateRootHomeTask());
+    }
+
+    @Test
+    public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
+        DisplayContent display = createNewDisplay();
+        doReturn(false).when(display).supportsSystemDecorations();
+
+        assertNull(display.getRootHomeTask());
+        assertNull(display.getOrCreateRootHomeTask());
+    }
+
+    @Test
+    public void testGetOrCreateRootHomeTask_untrustedVirtualDisplay() {
+        DisplayContent display = createNewDisplay();
+        doReturn(true).when(display).isUntrustedVirtualDisplay();
+
+        assertNull(display.getRootHomeTask());
+        assertNull(display.getOrCreateRootHomeTask());
+    }
+
     private boolean isOptionsPanelAtRight(int displayId) {
         return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index a672a95..4449069 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1098,7 +1098,6 @@
         assertSecurityException(expectCallable,
                 () -> mService.setTaskWindowingModeSplitScreenPrimary(0,
                         SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true));
-        assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
         assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
         assertSecurityException(expectCallable,
                 () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 79db927..dd46673 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -860,6 +860,26 @@
                 secondaryDisplay.mDisplayId, result.getDisplayId());
     }
 
+    @Test
+    public void testSwitchUser_missingHomeRootTask() {
+        doReturn(mFullscreenStack).when(mRootWindowContainer).getTopDisplayFocusedStack();
+
+        DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay();
+        ActivityStack homeStack = defaultDisplay.getRootHomeTask();
+        if (homeStack != null) {
+            homeStack.removeImmediately();
+        }
+        assertNull(defaultDisplay.getRootHomeTask());
+
+        int currentUser = mRootWindowContainer.mCurrentUser;
+        int otherUser = currentUser + 1;
+
+        mRootWindowContainer.switchUser(otherUser, null);
+
+        assertNotNull(defaultDisplay.getRootHomeTask());
+        assertEquals(defaultDisplay.getTopStack(), defaultDisplay.getRootHomeTask());
+    }
+
     /**
      * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
      * info for test cases (the original implementation will resolve from the real package manager).
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index f517881..8ad7505 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
 import android.os.PowerSaveState;
 import android.os.StrictMode;
 import android.os.UserHandle;
+import android.util.Log;
 import android.view.InputChannel;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -120,11 +121,22 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
+                Throwable throwable = null;
                 try {
                     runWithDexmakerShareClassLoader(SystemServicesTestRule.this::setUp);
                     base.evaluate();
+                } catch (Throwable t) {
+                    throwable = t;
                 } finally {
-                    tearDown();
+                    try {
+                        tearDown();
+                    } catch (Throwable t) {
+                        if (throwable != null) {
+                            Log.e("SystemServicesTestRule", "Suppressed: ", throwable);
+                            t.addSuppressed(throwable);
+                        }
+                        throw t;
+                    }
                 }
             }
         };
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 0312df6..cd53ece 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -28,19 +28,17 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+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.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -49,6 +47,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.PictureInPictureParams;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -56,7 +55,6 @@
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
-import android.content.pm.ActivityInfo;
 import android.util.Rational;
 import android.view.Display;
 import android.view.ITaskOrganizer;
@@ -458,9 +456,9 @@
     private List<TaskTile> getTaskTiles(DisplayContent dc) {
         ArrayList<TaskTile> out = new ArrayList<>();
         for (int i = dc.getStackCount() - 1; i >= 0; --i) {
-            final Task t = dc.getStackAt(i);
-            if (t instanceof TaskTile) {
-                out.add((TaskTile) t);
+            final TaskTile t = dc.getStackAt(i).asTile();
+            if (t != null) {
+                out.add(t);
             }
         }
         return out;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 3a68924..7a075a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -75,7 +75,8 @@
     }
 
     @Override
-    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
+    public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
+            int[] outAppOp) {
         return 0;
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 420695d..df5b311 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -500,6 +500,19 @@
                 == PackageManager.PERMISSION_GRANTED);
     }
 
+    /**
+     * Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and
+     * {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does
+     * not hold the {@link android.Manifest.permission.MANAGE_NOTIFICATIONS} permission.
+     */
+    private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) {
+        if (callingUid == Process.SYSTEM_UID) {
+            return false;
+        }
+        return !(getContext().checkPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS,
+                callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
+    }
+
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -1038,9 +1051,7 @@
     /**
      * Called by the Binder stub.
      */
-    UsageEvents queryEvents(int userId, long beginTime, long endTime,
-            boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
-            boolean shouldHideLocusIdEvents) {
+    UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
         synchronized (mLock) {
             if (!mUserUnlockedStates.get(userId)) {
                 Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1051,8 +1062,7 @@
             if (service == null) {
                 return null; // user was stopped or removed
             }
-            return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps,
-                    shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
+            return service.queryEvents(beginTime, endTime, flags);
         }
     }
 
@@ -1475,10 +1485,15 @@
             try {
                 final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
                         userId, callingPackage, callingPid, callingUid);
-                boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
-                return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
-                        obfuscateInstantApps, hideShortcutInvocationEvents,
-                        shouldHideLocusIdEvents);
+                final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+                final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+                        callingPid, callingUid);
+                int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+                if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+                if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+                if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+                if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+                return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1525,10 +1540,15 @@
             try {
                 final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
                         userId, callingPackage, callingPid, callingUid);
-                boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
-                return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
-                        obfuscateInstantApps, hideShortcutInvocationEvents,
-                        shouldHideLocusIdEvents);
+                final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+                        callingPid, callingUid);
+                boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+                int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+                if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+                if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+                if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+                if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+                return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2144,12 +2164,8 @@
         }
 
         @Override
-        public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
-                boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
-                boolean shouldHideLocusIdEvents) {
-            return UsageStatsService.this.queryEvents(
-                    userId, beginTime, endTime, obfuscateInstantApps,
-                    shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
+        public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, int flags) {
+            return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
         }
 
         @Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index d9317ac..db26d88 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -18,6 +18,10 @@
 
 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
 import static android.app.usage.UsageEvents.Event.DEVICE_STARTUP;
+import static android.app.usage.UsageEvents.HIDE_LOCUS_EVENTS;
+import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS;
+import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS;
+import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
 import static android.app.usage.UsageStatsManager.INTERVAL_BEST;
 import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
 import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
@@ -481,8 +485,7 @@
         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
     }
 
-    UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps,
-            boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) {
+    UsageEvents queryEvents(final long beginTime, final long endTime, int flags) {
         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
             return null;
         }
@@ -500,15 +503,22 @@
                             }
 
                             Event event = stats.events.get(i);
-                            if (hideShortcutInvocationEvents
-                                    && event.mEventType == Event.SHORTCUT_INVOCATION) {
+                            final int eventType = event.mEventType;
+                            if (eventType == Event.SHORTCUT_INVOCATION
+                                    && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
                                 continue;
                             }
-                            if (hideLocusIdEvents
-                                    && event.mEventType == Event.LOCUS_ID_SET) {
+                            if (eventType == Event.LOCUS_ID_SET
+                                    && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) {
                                 continue;
                             }
-                            if (obfuscateInstantApps) {
+                            if ((eventType == Event.NOTIFICATION_SEEN
+                                    || eventType == Event.NOTIFICATION_INTERRUPTION)
+                                    && (flags & OBFUSCATE_NOTIFICATION_EVENTS)
+                                    == OBFUSCATE_NOTIFICATION_EVENTS) {
+                                event = event.getObfuscatedNotificationEvent();
+                            }
+                            if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) {
                                 event = event.getObfuscatedIfInstantApp();
                             }
                             if (event.mPackage != null) {
diff --git a/startop/iorap/functional_tests/AndroidTest.xml b/startop/iorap/functional_tests/AndroidTest.xml
index 41109b4..ef56fc8 100644
--- a/startop/iorap/functional_tests/AndroidTest.xml
+++ b/startop/iorap/functional_tests/AndroidTest.xml
@@ -34,6 +34,11 @@
         <option name="run-command" value="rm -r /data/misc/iorapd/*" />
         <option name="run-command" value="sleep 1" />
 
+        <!-- Set system properties to enable perfetto tracing, readahead and detailed logging. -->
+        <option name="run-command" value="setprop iorapd.perfetto.enable true" />
+        <option name="run-command" value="setprop iorapd.readahead.enable true" />
+        <option name="run-command" value="setprop iorapd.log.verbose true" />
+
         <option name="run-command" value="start iorapd" />
 
         <!-- give it some time to restart the service; otherwise the first unit test might fail -->
@@ -45,9 +50,5 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
     </test>
 
-    <!-- using DeviceSetup again does not work. we simply leave the device in a semi-bad
-         state. there is no way to clean this up as far as I know.
-         -->
-
 </configuration>
 
diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
index bd8a45c..4002387 100644
--- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
+++ b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
@@ -67,7 +67,7 @@
   private static final String TEST_ACTIVITY_NAME = "com.android.settings.Settings";
 
   private static final String DB_PATH = "/data/misc/iorapd/sqlite.db";
-  private static final Duration TIMEOUT = Duration.ofSeconds(20L);
+  private static final Duration TIMEOUT = Duration.ofSeconds(300L);
 
   private static final String READAHEAD_INDICATOR =
       "Description = /data/misc/iorapd/com.android.settings/none/com.android.settings.Settings/compiled_traces/compiled_trace.pb";
@@ -88,7 +88,7 @@
     mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds());
   }
 
-  @Test
+  @Test (timeout = 300000)
   public void testApp() throws Exception {
     assertThat(mDevice, notNullValue());
 
@@ -247,7 +247,7 @@
       if (supplier.getAsBoolean()) {
         return true;
       }
-      TimeUnit.SECONDS.sleep(totalSleepTimeSeconds);
+      TimeUnit.SECONDS.sleep(sleepIntervalSeconds);
       totalSleepTimeSeconds += sleepIntervalSeconds;
       if (totalSleepTimeSeconds > timeout.getSeconds()) {
         return false;
@@ -367,7 +367,7 @@
    *
    * <p> This should be run as root.</p>
    */
-  private String executeShellCommand(String cmd) throws Exception {
+  private static String executeShellCommand(String cmd) throws Exception {
     Log.i(TAG, "Execute: " + cmd);
     return UiDevice.getInstance(
         InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ec99f36..52213d8 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -458,8 +459,14 @@
         /** Call supports the deflect feature. */
         public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
 
+        /**
+         * Call supports adding participants to the call via
+         * {@link #addConferenceParticipants(List)}.
+         * @hide
+         */
+        public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
         //******************************************************************************************
-        // Next CAPABILITY value: 0x02000000
+        // Next CAPABILITY value: 0x04000000
         //******************************************************************************************
 
         /**
@@ -539,7 +546,7 @@
          *
          * @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
          */
-        public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200;
+        public static final int PROPERTY_ASSISTED_DIALING = 0x00000200;
 
         /**
          * Indicates that the call is an RTT call. Use {@link #getRttCall()} to get the
@@ -689,6 +696,9 @@
             if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
                 builder.append(" CAPABILITY_SUPPORT_DEFLECT");
             }
+            if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
+                builder.append(" CAPABILITY_ADD_PARTICIPANT");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -744,7 +754,7 @@
             if (hasProperty(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
                 builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
             }
-            if (hasProperty(properties, PROPERTY_ASSISTED_DIALING_USED)) {
+            if (hasProperty(properties, PROPERTY_ASSISTED_DIALING)) {
                 builder.append(" PROPERTY_ASSISTED_DIALING_USED");
             }
             if (hasProperty(properties, PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL)) {
@@ -1703,6 +1713,17 @@
     }
 
     /**
+     * Pulls participants to existing call by forming a conference call.
+     * See {@link Details#CAPABILITY_ADD_PARTICIPANT}.
+     *
+     * @param participants participants to be pulled to existing call.
+     * @hide
+     */
+    public void addConferenceParticipants(@NonNull List<Uri> participants) {
+        mInCallAdapter.addConferenceParticipants(mTelecomCallId, participants);
+    }
+
+    /**
      * Initiates a request to the {@link ConnectionService} to pull an external call to the local
      * device.
      * <p>
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 56acdff..f019a9d 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -16,8 +16,13 @@
 
 package android.telecom;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.net.Uri;
@@ -319,6 +324,13 @@
     public void onConnectionAdded(Connection connection) {}
 
     /**
+     * Notifies the {@link Conference} of a request to add a new participants to the conference call
+     * @param participants that will be added to this conference call
+     * @hide
+     */
+    public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+    /**
      * Notifies this Conference, which is in {@code STATE_RINGING}, of
      * a request to accept.
      * For managed {@link ConnectionService}s, this will be called when the user answers a call via
@@ -625,12 +637,12 @@
      * Should be specified in wall-clock time returned by {@link System#currentTimeMillis()}.
      * <p>
      * When setting the connection time, you should always set the connection elapsed time via
-     * {@link #setConnectionStartElapsedRealTime(long)} to ensure the duration is reflected.
+     * {@link #setConnectionStartElapsedRealtimeMillis(long)} to ensure the duration is reflected.
      *
      * @param connectionTimeMillis The connection time, in milliseconds, as returned by
      *                             {@link System#currentTimeMillis()}.
      */
-    public final void setConnectionTime(long connectionTimeMillis) {
+    public final void setConnectionTime(@IntRange(from = 0) long connectionTimeMillis) {
         mConnectTimeMillis = connectionTimeMillis;
     }
 
@@ -646,8 +658,28 @@
      *
      * @param connectionStartElapsedRealTime The connection time, as measured by
      * {@link SystemClock#elapsedRealtime()}.
+     * @deprecated use {@link #setConnectionStartElapsedRealtimeMillis(long)} instead.
      */
+    @Deprecated
     public final void setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime) {
+        setConnectionStartElapsedRealtimeMillis(connectionStartElapsedRealTime);
+    }
+
+    /**
+     * Sets the start time of the {@link Conference} which is the basis for the determining the
+     * duration of the {@link Conference}.
+     * <p>
+     * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
+     * zone changes do not impact the conference duration.
+     * <p>
+     * When setting this, you should also set the connection time via
+     * {@link #setConnectionTime(long)}.
+     *
+     * @param connectionStartElapsedRealTime The connection time, as measured by
+     * {@link SystemClock#elapsedRealtime()}.
+     */
+    public final void setConnectionStartElapsedRealtimeMillis(
+            @ElapsedRealtimeLong long connectionStartElapsedRealTime) {
         mConnectionStartElapsedRealTime = connectionStartElapsedRealTime;
     }
 
@@ -668,7 +700,7 @@
      *
      * @return The time at which the {@code Conference} was connected.
      */
-    public final long getConnectionTime() {
+    public final @IntRange(from = 0) long getConnectionTime() {
         return mConnectTimeMillis;
     }
 
@@ -685,11 +717,8 @@
      * has no general use other than to the Telephony framework.
      *
      * @return The elapsed time at which the {@link Conference} was connected.
-     * @hide
      */
-    @SystemApi
-    @TestApi
-    public final long getConnectionStartElapsedRealTime() {
+    public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
         return mConnectionStartElapsedRealTime;
     }
 
@@ -987,6 +1016,7 @@
      */
     @SystemApi
     @TestApi
+    @RequiresPermission(MODIFY_PHONE_STATE)
     public void setConferenceState(boolean isConference) {
         for (Listener l : mListeners) {
             l.onConferenceStateChanged(this, isConference);
@@ -1007,6 +1037,7 @@
      */
     @SystemApi
     @TestApi
+    @RequiresPermission(MODIFY_PHONE_STATE)
     public final void setAddress(@NonNull Uri address,
             @TelecomManager.Presentation int presentation) {
         Log.d(this, "setAddress %s", address);
@@ -1113,12 +1144,52 @@
     }
 
     /**
-     * Sends an event associated with this {@code Conference} with associated event extras to the
-     * {@link InCallService} (note: this is identical in concept to
-     * {@link Connection#sendConnectionEvent(String, Bundle)}).
-     * @see Connection#sendConnectionEvent(String, Bundle)
+     * Sends an event associated with this {@link Conference} with associated event extras to the
+     * {@link InCallService}.
+     * <p>
+     * Connection events are used to communicate point in time information from a
+     * {@link ConnectionService} to an {@link InCallService} implementation.  An example of a
+     * custom connection event includes notifying the UI when a WIFI call has been handed over to
+     * LTE, which the InCall UI might use to inform the user that billing charges may apply.  The
+     * Android Telephony framework will send the {@link Connection#EVENT_MERGE_COMPLETE}
+     * connection event when a call to {@link Call#mergeConference()} has completed successfully.
+     * <p>
+     * Events are exposed to {@link InCallService} implementations via
+     * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these events.
+     * The {@link ConnectionService} must assume that the In-Call UI could even chose to ignore
+     * some events altogether.
+     * <p>
+     * Events should be fully qualified (e.g. {@code com.example.event.MY_EVENT}) to avoid
+     * conflicts between {@link ConnectionService} implementations.  Further, custom
+     * {@link ConnectionService} implementations shall not re-purpose events in the
+     * {@code android.*} namespace, nor shall they define new event types in this namespace.  When
+     * defining a custom event type, ensure the contents of the extras {@link Bundle} is clearly
+     * defined.  Extra keys for this bundle should be named similar to the event type (e.g.
+     * {@code com.example.extra.MY_EXTRA}).
+     * <p>
+     * When defining events and the associated extras, it is important to keep their behavior
+     * consistent when the associated {@link ConnectionService} is updated.  Support for deprecated
+     * events/extras should me maintained to ensure backwards compatibility with older
+     * {@link InCallService} implementations which were built to support the older behavior.
+     * <p>
+     * Expected connection events from the Telephony stack are:
+     * <p>
+     * <ul>
+     *      <li>{@link Connection#EVENT_CALL_HOLD_FAILED} with {@code null} {@code extras} when the
+     *      {@link Conference} could not be held.</li>
+     *      <li>{@link Connection#EVENT_MERGE_START} with {@code null} {@code extras} when a new
+     *      call is being merged into the conference.</li>
+     *      <li>{@link Connection#EVENT_MERGE_COMPLETE} with {@code null} {@code extras} a new call
+     *      has completed being merged into the conference.</li>
+     *      <li>{@link Connection#EVENT_CALL_MERGE_FAILED} with {@code null} {@code extras} a new
+     *      call has failed to merge into the conference (the dialer app can determine which call
+     *      failed to merge based on the fact that the call still exists outside of the conference
+     *      at the end of the merge process).</li>
+     * </ul>
      *
-     * @param event The connection event.
+     * @param event The conference event.
      * @param extras Optional bundle containing extra information associated with the event.
      */
     public void sendConferenceEvent(@NonNull String event, @Nullable Bundle extras) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8049459..3b0ba25 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -16,9 +16,14 @@
 
 package android.telecom;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Notification;
@@ -376,8 +381,14 @@
     /** Call supports the deflect feature. */
     public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
 
+    /**
+     * When set, indicates that this {@link Connection} supports initiation of a conference call
+     * by directly adding participants using {@link #onAddConferenceParticipants(List)}.
+     * @hide
+     */
+    public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x04000000
+    // Next CAPABILITY value: 0x08000000
     //**********************************************************************************************
 
     /**
@@ -474,7 +485,7 @@
      *
      * @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
      */
-    public static final int PROPERTY_ASSISTED_DIALING_USED = 1 << 9;
+    public static final int PROPERTY_ASSISTED_DIALING = 1 << 9;
 
     /**
      * Set by the framework to indicate that the network has identified a Connection as an emergency
@@ -953,7 +964,9 @@
         if ((capabilities & CAPABILITY_SUPPORT_DEFLECT) == CAPABILITY_SUPPORT_DEFLECT) {
             builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
         }
-
+        if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
+            builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
+        }
         builder.append("]");
         return builder.toString();
     }
@@ -2109,19 +2122,24 @@
      */
     @SystemApi
     @TestApi
-    public final long getConnectTimeMillis() {
+    public final @IntRange(from = 0) long getConnectTimeMillis() {
         return mConnectTimeMillis;
     }
 
     /**
      * Retrieves the connection start time of the {@link Connection}, if specified.  A value of
      * {@link Conference#CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the
-     * start time of the conference.
+     * start time of the connection.
      * <p>
      * Based on the value of {@link SystemClock#elapsedRealtime()}, which ensures that wall-clock
      * changes do not impact the call duration.
      * <p>
      * Used internally in Telephony when migrating conference participant data for IMS conferences.
+     * <p>
+     * The value returned is the same one set using
+     * {@link #setConnectionStartElapsedRealtimeMillis(long)}.  This value is never updated from
+     * the Telecom framework, so no permission enforcement occurs when retrieving the value with
+     * this method.
      *
      * @return The time at which the {@link Connection} was connected.
      *
@@ -2129,7 +2147,7 @@
      */
     @SystemApi
     @TestApi
-    public final long getConnectElapsedTimeMillis() {
+    public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
         return mConnectElapsedTimeMillis;
     }
 
@@ -2550,6 +2568,9 @@
      * Sets the time at which a call became active on this Connection. This is set only
      * when a conference call becomes active on this connection.
      * <p>
+     * This time corresponds to the date/time of connection and is stored in the call log in
+     * {@link android.provider.CallLog.Calls#DATE}.
+     * <p>
      * Used by telephony to maintain calls associated with an IMS Conference.
      *
      * @param connectTimeMillis The connection time, in milliseconds.  Should be set using a value
@@ -2559,7 +2580,8 @@
      */
     @SystemApi
     @TestApi
-    public final void setConnectTimeMillis(long connectTimeMillis) {
+    @RequiresPermission(MODIFY_PHONE_STATE)
+    public final void setConnectTimeMillis(@IntRange(from = 0) long connectTimeMillis) {
         mConnectTimeMillis = connectTimeMillis;
     }
 
@@ -2567,15 +2589,23 @@
      * Sets the time at which a call became active on this Connection. This is set only
      * when a conference call becomes active on this connection.
      * <p>
+     * This time is used to establish the duration of a call.  It uses
+     * {@link SystemClock#elapsedRealtime()} to ensure that the call duration is not impacted by
+     * time zone changes during a call.  The difference between the current
+     * {@link SystemClock#elapsedRealtime()} and the value set at the connection start time is used
+     * to populate {@link android.provider.CallLog.Calls#DURATION} in the call log.
+     * <p>
      * Used by telephony to maintain calls associated with an IMS Conference.
+     *
      * @param connectElapsedTimeMillis The connection time, in milliseconds.  Stored in the format
      *                              {@link SystemClock#elapsedRealtime()}.
-     *
      * @hide
      */
     @SystemApi
     @TestApi
-    public final void setConnectionStartElapsedRealTime(long connectElapsedTimeMillis) {
+    @RequiresPermission(MODIFY_PHONE_STATE)
+    public final void setConnectionStartElapsedRealtimeMillis(
+            @ElapsedRealtimeLong long connectElapsedTimeMillis) {
         mConnectElapsedTimeMillis = connectElapsedTimeMillis;
     }
 
@@ -2953,6 +2983,14 @@
     public void onSeparate() {}
 
     /**
+     * Supports initiation of a conference call by directly adding participants to an ongoing call.
+     *
+     * @param participants with which conference call will be formed.
+     * @hide
+     */
+    public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+    /**
      * Notifies this Connection of a request to abort.
      */
     public void onAbort() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 00c2918..2aea723 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
@@ -142,6 +141,7 @@
     private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
     private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
     private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
+    private static final String SESSION_ADD_PARTICIPANT = "CS.aP";
     private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
     private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
     private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
@@ -195,6 +195,7 @@
     private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
     private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
     private static final int MSG_REJECT_WITH_REASON = 38;
+    private static final int MSG_ADD_PARTICIPANT = 39;
 
     private static Connection sNullConnection;
 
@@ -627,6 +628,21 @@
         }
 
         @Override
+        public void addConferenceParticipants(String callId, List<Uri> participants,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_ADD_PARTICIPANT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = participants;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ADD_PARTICIPANT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
             try {
@@ -1224,6 +1240,19 @@
                     }
                     break;
                 }
+                case MSG_ADD_PARTICIPANT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg3,
+                                SESSION_HANDLER + SESSION_ADD_PARTICIPANT);
+                        addConferenceParticipants((String) args.arg1, (List<Uri>)args.arg2);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+
                 case MSG_ON_POST_DIAL_CONTINUE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -1778,7 +1807,7 @@
                         null : conference.getVideoProvider().getInterface(),
                 conference.getVideoState(),
                 conference.getConnectTimeMillis(),
-                conference.getConnectionStartElapsedRealTime(),
+                conference.getConnectionStartElapsedRealtimeMillis(),
                 conference.getStatusHints(),
                 conference.getExtras(),
                 conference.getAddress(),
@@ -1884,7 +1913,7 @@
                         connection.isRingbackRequested(),
                         connection.getAudioModeIsVoip(),
                         connection.getConnectTimeMillis(),
-                        connection.getConnectElapsedTimeMillis(),
+                        connection.getConnectionStartElapsedRealtimeMillis(),
                         connection.getStatusHints(),
                         connection.getDisconnectCause(),
                         createIdList(connection.getConferenceables()),
@@ -2152,6 +2181,17 @@
         }
     }
 
+    private void addConferenceParticipants(String callId, List<Uri> participants) {
+        Log.d(this, "addConferenceParticipants(%s)", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "addConferenceParticipants")
+                    .onAddConferenceParticipants(participants);
+        } else {
+            findConferenceForAction(callId, "addConferenceParticipants")
+                    .onAddConferenceParticipants(participants);
+        }
+    }
+
     /**
      * Notifies a {@link Connection} of a request to pull an external call.
      *
@@ -2374,7 +2414,7 @@
                             null : conference.getVideoProvider().getInterface(),
                     conference.getVideoState(),
                     conference.getConnectTimeMillis(),
-                    conference.getConnectionStartElapsedRealTime(),
+                    conference.getConnectionStartElapsedRealtimeMillis(),
                     conference.getStatusHints(),
                     conference.getExtras(),
                     conference.getAddress(),
@@ -2465,7 +2505,7 @@
                     connection.isRingbackRequested(),
                     connection.getAudioModeIsVoip(),
                     connection.getConnectTimeMillis(),
-                    connection.getConnectElapsedTimeMillis(),
+                    connection.getConnectionStartElapsedRealtimeMillis(),
                     connection.getStatusHints(),
                     connection.getDisconnectCause(),
                     emptyList,
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 594c1eb..9d291740 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -283,6 +283,20 @@
     }
 
     /**
+     * Instructs Telecom to pull participants to existing call
+     *
+     * @param callId The unique ID of the call.
+     * @param participants participants to be pulled to existing call.
+     */
+    public void addConferenceParticipants(String callId, List<Uri> participants) {
+        try {
+            mAdapter.addConferenceParticipants(callId, participants);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+
+    /**
      * Instructs Telecom to split the specified call from any conference call with which it may be
      * connected.
      *
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index ebfa3a1..982e5f3 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -20,6 +20,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
+import android.app.UiModeManager;
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
@@ -43,12 +44,32 @@
  * phone calls.
  * <h2>Becoming the Default Phone App</h2>
  * The default dialer/phone app is one which provides the in-call user interface while the device is
- * in a call.  A device is bundled with a system provided default dialer/phone app.  The user may
- * choose a single app to take over this role from the system app.  An app which wishes to fulfill
- * one this role uses the {@code android.app.role.RoleManager} to request that they fill the role.
+ * in a call.  It also provides the user with a means to initiate calls and see a history of calls
+ * on their device.  A device is bundled with a system provided default dialer/phone app.  The user
+ * may choose a single app to take over this role from the system app.  An app which wishes to
+ * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * {@link android.app.role.RoleManager#ROLE_DIALER} role.
  * <p>
- * An app filling the role of the default phone app provides a user interface while the device is in
- * a call, and the device is not in car mode.
+ * The default phone app provides a user interface while the device is in a call, and the device is
+ * not in car mode (i.e. {@link UiModeManager#getCurrentModeType()} is not
+ * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
+ * <p>
+ * In order to fill the {@link android.app.role.RoleManager#ROLE_DIALER} role, an app must meet a
+ * number of requirements:
+ * <ul>
+ *     <li>It must handle the {@link Intent#ACTION_DIAL} intent.  This means the app must provide
+ *     a dial pad UI for the user to initiate outgoing calls.</li>
+ *     <li>It must fully implement the {@link InCallService} API and provide both an incoming call
+ *     UI, as well as an ongoing call UI.</li>
+ * </ul>
+ * <p>
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
+ * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
+ * dialer app pre-loaded on the device.  The system will display a notification to the user to let
+ * them know that the app has crashed and that their call was continued using the pre-loaded dialer
+ * app.
+ * <p>
+ * Further, the pre-loaded dialer will ALWAYS be used when the user places an emergency call.
  * <p>
  * Below is an example manifest registration for an {@code InCallService}. The meta-data
  * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,6 +103,11 @@
  *           <action android:name="android.intent.action.DIAL" />
  *           <category android:name="android.intent.category.DEFAULT" />
  *      </intent-filter>
+ *      <intent-filter>
+ *           <action android:name="android.intent.action.DIAL" />
+ *           <category android:name="android.intent.category.DEFAULT" />
+ *           <data android:scheme="tel" />
+ *      </intent-filter>
  * </activity>
  * }
  * </pre>
@@ -111,6 +137,7 @@
  *         }
  *     }
  * }
+ * }
  * </pre>
  * <p id="incomingCallNotification">
  * <h3>Showing the Incoming Call Notification</h3>
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index f00432b..4e6e1a5 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -16,7 +16,10 @@
 
 package android.telecom;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Intent;
@@ -614,7 +617,8 @@
          * time. By default, there is no group Id for a {@link PhoneAccount} (an empty String). Only
          * grouped {@link PhoneAccount}s with the same {@link ConnectionService} can be replaced.
          * <p>
-         * Note: This is an API specific to the Telephony stack.
+         * Note: This is an API specific to the Telephony stack; the group Id will be ignored for
+         * callers not holding the correct permission.
          *
          * @param groupId The group Id of the {@link PhoneAccount} that will replace any other
          * registered {@link PhoneAccount} in Telecom with the same Group Id.
@@ -623,6 +627,7 @@
          */
         @SystemApi
         @TestApi
+        @RequiresPermission(MODIFY_PHONE_STATE)
         public @NonNull Builder setGroupId(@NonNull String groupId) {
             if (groupId != null) {
                 mGroupId = groupId;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a28cc4f..5d7d649 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -819,8 +819,8 @@
      * automatically add dialing prefixes when placing international calls.
      * <p>
      * Setting this extra on the outgoing call extras will cause the
-     * {@link Connection#PROPERTY_ASSISTED_DIALING_USED} property and
-     * {@link Call.Details#PROPERTY_ASSISTED_DIALING_USED} property to be set on the
+     * {@link Connection#PROPERTY_ASSISTED_DIALING} property and
+     * {@link Call.Details#PROPERTY_ASSISTED_DIALING} property to be set on the
      * {@link Connection}/{@link Call} in question.  When the call is logged to the call log, the
      * {@link android.provider.CallLog.Calls#FEATURES_ASSISTED_DIALING_USED} call feature is set to
      * indicate that assisted dialing was used for the call.
@@ -1412,7 +1412,7 @@
     /**
      * Used to determine the currently selected default dialer package for a specific user.
      *
-     * @param userId the user id to query the default dialer package for.
+     * @param userHandle the user id to query the default dialer package for.
      * @return package name for the default dialer package or null if no package has been
      *         selected as the default dialer.
      * @hide
@@ -1420,10 +1420,11 @@
     @SystemApi
     @TestApi
     @RequiresPermission(READ_PRIVILEGED_PHONE_STATE)
-    public @Nullable String getDefaultDialerPackage(int userId) {
+    public @Nullable String getDefaultDialerPackage(@NonNull UserHandle userHandle) {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getDefaultDialerPackageForUser(userId);
+                return getTelecomService().getDefaultDialerPackageForUser(
+                        userHandle.getIdentifier());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get the default dialer package name.", e);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 4249dff..a397d77 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -104,6 +104,9 @@
 
     void swapConference(String conferenceCallId, in Session.Info sessionInfo);
 
+    void addConferenceParticipants(String CallId, in List<Uri> participants,
+    in Session.Info sessionInfo);
+
     void onPostDialContinue(String callId, boolean proceed, in Session.Info sessionInfo);
 
     void pullExternalCall(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index eb2d714..9beff22 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -67,6 +67,8 @@
 
     void swapConference(String callId);
 
+    void addConferenceParticipants(String callId, in List<Uri> participants);
+
     void turnOnProximitySensor();
 
     void turnOffProximitySensor(boolean screenOnImmediately);
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index d2a5905..a27c480 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -661,4 +661,16 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Skip464XlatStatus {}
+
+    /**
+     * Override network type
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "OVERRIDE_NETWORK_TYPE_", value = {
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+            DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+    public @interface OverrideNetworkType {}
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ebb53c5..c51a852 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1090,6 +1090,14 @@
             "support_adhoc_conference_calls_bool";
 
     /**
+     * Determines whether conference participants can be added to existing call.  When {@code true},
+     * adding conference participants to existing call is supported, {@code false otherwise}.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL =
+            "support_add_conference_participants_bool";
+
+    /**
      * Determines whether conference calls are supported by a carrier.  When {@code true},
      * conference calling is supported, {@code false otherwise}.
      */
@@ -2369,7 +2377,7 @@
      *  {@link CellSignalStrengthLte#USE_RSRQ}, {@link CellSignalStrengthLte#USE_RSSNR}.
      *
      * For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1).
-     * If the key is invalid or not configured, a default value (RSRP | RSSNR = 1 << 0 | 1 << 2)
+     * If the key is invalid or not configured, a default value (RSRP = 1 << 0)
      * will apply.
      *
      * @hide
@@ -4004,6 +4012,7 @@
         sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false);
+        sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
@@ -4325,7 +4334,7 @@
         sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
         sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000);
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
-                CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR);
+                CellSignalStrengthLte.USE_RSRP);
         // Default wifi configurations.
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 9f2537c..203047f 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -22,11 +22,12 @@
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * CellIdentity to represent a unique GSM cell
@@ -50,7 +51,7 @@
     private final int mBsic;
 
     // a list of additional PLMN-IDs reported for this cell
-    private final List<String> mAdditionalPlmns;
+    private final ArraySet<String> mAdditionalPlmns;
 
     /**
      * @hide
@@ -62,7 +63,7 @@
         mCid = CellInfo.UNAVAILABLE;
         mArfcn = CellInfo.UNAVAILABLE;
         mBsic = CellInfo.UNAVAILABLE;
-        mAdditionalPlmns = Collections.emptyList();
+        mAdditionalPlmns = new ArraySet<>();
     }
 
     /**
@@ -81,13 +82,13 @@
      */
     public CellIdentityGsm(int lac, int cid, int arfcn, int bsic, @Nullable String mccStr,
             @Nullable String mncStr, @Nullable String alphal, @Nullable String alphas,
-            @NonNull List<String> additionalPlmns) {
+            @NonNull Collection<String> additionalPlmns) {
         super(TAG, CellInfo.TYPE_GSM, mccStr, mncStr, alphal, alphas);
         mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
         mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
         mArfcn = inRangeOrUnavailable(arfcn, 0, MAX_ARFCN);
         mBsic = inRangeOrUnavailable(bsic, 0, MAX_BSIC);
-        mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+        mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
         for (String plmn : additionalPlmns) {
             if (isValidPlmn(plmn)) {
                 mAdditionalPlmns.add(plmn);
@@ -99,7 +100,7 @@
     public CellIdentityGsm(@NonNull android.hardware.radio.V1_0.CellIdentityGsm cid) {
         this(cid.lac, cid.cid, cid.arfcn,
                 cid.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.bsic,
-                cid.mcc, cid.mnc, "", "", Collections.emptyList());
+                cid.mcc, cid.mnc, "", "", new ArraySet<>());
     }
 
     /** @hide */
@@ -107,7 +108,7 @@
         this(cid.base.lac, cid.base.cid, cid.base.arfcn,
                 cid.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.base.bsic, cid.base.mcc,
                 cid.base.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
-                Collections.emptyList());
+                new ArraySet<>());
     }
 
     /** @hide */
@@ -221,8 +222,8 @@
      * @return a list of additional PLMN IDs supported by this cell.
      */
     @NonNull
-    public List<String> getAdditionalPlmns() {
-        return mAdditionalPlmns;
+    public Set<String> getAdditionalPlmns() {
+        return Collections.unmodifiableSet(mAdditionalPlmns);
     }
 
     /**
@@ -296,7 +297,7 @@
         dest.writeInt(mCid);
         dest.writeInt(mArfcn);
         dest.writeInt(mBsic);
-        dest.writeList(mAdditionalPlmns);
+        dest.writeArraySet(mAdditionalPlmns);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -306,7 +307,7 @@
         mCid = in.readInt();
         mArfcn = in.readInt();
         mBsic = in.readInt();
-        mAdditionalPlmns = in.readArrayList(null);
+        mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
 
         if (DBG) log(toString());
     }
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index a194ae3..e4198d1 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -23,11 +23,13 @@
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * CellIdentity is to represent a unique LTE cell
@@ -54,7 +56,7 @@
     private final int mBandwidth;
 
     // a list of additional PLMN-IDs reported for this cell
-    private final List<String> mAdditionalPlmns;
+    private final ArraySet<String> mAdditionalPlmns;
 
     private ClosedSubscriberGroupInfo mCsgInfo;
 
@@ -69,7 +71,7 @@
         mTac = CellInfo.UNAVAILABLE;
         mEarfcn = CellInfo.UNAVAILABLE;
         mBandwidth = CellInfo.UNAVAILABLE;
-        mAdditionalPlmns = Collections.emptyList();
+        mAdditionalPlmns = new ArraySet<>();
         mCsgInfo = null;
     }
 
@@ -86,7 +88,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) {
         this(ci, pci, tac, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc),
-                String.valueOf(mnc), null, null, Collections.emptyList(), null);
+                String.valueOf(mnc), null, null, new ArraySet<>(), null);
     }
 
     /**
@@ -107,7 +109,7 @@
      */
     public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth,
             @Nullable String mccStr, @Nullable String mncStr, @Nullable String alphal,
-            @Nullable String alphas, @NonNull List<String> additionalPlmns,
+            @Nullable String alphas, @NonNull Collection<String> additionalPlmns,
             @Nullable ClosedSubscriberGroupInfo csgInfo) {
         super(TAG, CellInfo.TYPE_LTE, mccStr, mncStr, alphal, alphas);
         mCi = inRangeOrUnavailable(ci, 0, MAX_CI);
@@ -115,7 +117,7 @@
         mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
         mEarfcn = inRangeOrUnavailable(earfcn, 0, MAX_EARFCN);
         mBandwidth = inRangeOrUnavailable(bandwidth, 0, MAX_BANDWIDTH);
-        mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+        mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
         for (String plmn : additionalPlmns) {
             if (isValidPlmn(plmn)) {
                 mAdditionalPlmns.add(plmn);
@@ -127,14 +129,14 @@
     /** @hide */
     public CellIdentityLte(@NonNull android.hardware.radio.V1_0.CellIdentityLte cid) {
         this(cid.ci, cid.pci, cid.tac, cid.earfcn,
-                CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "", Collections.emptyList(), null);
+                CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "", new ArraySet<>(), null);
     }
 
     /** @hide */
     public CellIdentityLte(@NonNull android.hardware.radio.V1_2.CellIdentityLte cid) {
         this(cid.base.ci, cid.base.pci, cid.base.tac, cid.base.earfcn, cid.bandwidth,
                 cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong,
-                cid.operatorNames.alphaShort, Collections.emptyList(), null);
+                cid.operatorNames.alphaShort, new ArraySet<>(), null);
     }
 
     /** @hide */
@@ -270,8 +272,8 @@
      * @return a list of additional PLMN IDs supported by this cell.
      */
     @NonNull
-    public List<String> getAdditionalPlmns() {
-        return mAdditionalPlmns;
+    public Set<String> getAdditionalPlmns() {
+        return Collections.unmodifiableSet(mAdditionalPlmns);
     }
 
     /**
@@ -361,7 +363,7 @@
         dest.writeInt(mTac);
         dest.writeInt(mEarfcn);
         dest.writeInt(mBandwidth);
-        dest.writeList(mAdditionalPlmns);
+        dest.writeArraySet(mAdditionalPlmns);
         dest.writeParcelable(mCsgInfo, flags);
     }
 
@@ -373,7 +375,7 @@
         mTac = in.readInt();
         mEarfcn = in.readInt();
         mBandwidth = in.readInt();
-        mAdditionalPlmns = in.readArrayList(null);
+        mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
         mCsgInfo = in.readParcelable(null);
         if (DBG) log(toString());
     }
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index a0ef5aa..cba500a 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -22,11 +22,14 @@
 import android.os.Parcel;
 import android.telephony.AccessNetworkConstants.NgranBands.NgranBand;
 import android.telephony.gsm.GsmCellLocation;
+import android.util.ArraySet;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Information to represent a unique NR(New Radio 5G) cell.
@@ -46,7 +49,7 @@
     private final List<Integer> mBands;
 
     // a list of additional PLMN-IDs reported for this cell
-    private final List<String> mAdditionalPlmns;
+    private final ArraySet<String> mAdditionalPlmns;
 
     /**
      *
@@ -66,14 +69,14 @@
     public CellIdentityNr(int pci, int tac, int nrArfcn, @NgranBand List<Integer> bands,
                           @Nullable String mccStr, @Nullable String mncStr, long nci,
                           @Nullable String alphal, @Nullable String alphas,
-                          @NonNull List<String> additionalPlmns) {
+                          @NonNull Collection<String> additionalPlmns) {
         super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas);
         mPci = inRangeOrUnavailable(pci, 0, MAX_PCI);
         mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
         mNrArfcn = inRangeOrUnavailable(nrArfcn, 0, MAX_NRARFCN);
         mBands = new ArrayList<>(bands);
         mNci = inRangeOrUnavailable(nci, 0, MAX_NCI);
-        mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+        mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
         for (String plmn : additionalPlmns) {
             if (isValidPlmn(plmn)) {
                 mAdditionalPlmns.add(plmn);
@@ -85,7 +88,7 @@
     public CellIdentityNr(@NonNull android.hardware.radio.V1_4.CellIdentityNr cid) {
         this(cid.pci, cid.tac, cid.nrarfcn, Collections.emptyList(), cid.mcc, cid.mnc, cid.nci,
                 cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
-                Collections.emptyList());
+                new ArraySet<>());
     }
 
     /** @hide */
@@ -212,8 +215,8 @@
      * @return a list of additional PLMN IDs supported by this cell.
      */
     @NonNull
-    public List<String> getAdditionalPlmns() {
-        return Collections.unmodifiableList(mAdditionalPlmns);
+    public Set<String> getAdditionalPlmns() {
+        return Collections.unmodifiableSet(mAdditionalPlmns);
     }
 
     @Override
@@ -241,7 +244,7 @@
         dest.writeInt(mNrArfcn);
         dest.writeList(mBands);
         dest.writeLong(mNci);
-        dest.writeList(mAdditionalPlmns);
+        dest.writeArraySet(mAdditionalPlmns);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -252,7 +255,7 @@
         mNrArfcn = in.readInt();
         mBands = in.readArrayList(null);
         mNci = in.readLong();
-        mAdditionalPlmns = in.readArrayList(null);
+        mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
     }
 
     /** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 531487a..30f98bc 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -20,11 +20,12 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * CellIdentity is to represent a unique TD-SCDMA cell
@@ -50,7 +51,7 @@
     private final int mUarfcn;
 
     // a list of additional PLMN-IDs reported for this cell
-    private final List<String> mAdditionalPlmns;
+    private final ArraySet<String> mAdditionalPlmns;
 
     private ClosedSubscriberGroupInfo mCsgInfo;
 
@@ -63,7 +64,7 @@
         mCid = CellInfo.UNAVAILABLE;
         mCpid = CellInfo.UNAVAILABLE;
         mUarfcn = CellInfo.UNAVAILABLE;
-        mAdditionalPlmns = Collections.emptyList();
+        mAdditionalPlmns = new ArraySet<>();
         mCsgInfo = null;
     }
 
@@ -85,13 +86,14 @@
      */
     public CellIdentityTdscdma(@Nullable String mcc, @Nullable String mnc, int lac, int cid,
             int cpid, int uarfcn, @Nullable String alphal, @Nullable String alphas,
-            @NonNull List<String> additionalPlmns, @Nullable ClosedSubscriberGroupInfo csgInfo) {
+            @NonNull Collection<String> additionalPlmns,
+            @Nullable ClosedSubscriberGroupInfo csgInfo) {
         super(TAG, CellInfo.TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
         mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
         mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
         mCpid = inRangeOrUnavailable(cpid, 0, MAX_CPID);
         mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
-        mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+        mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
         for (String plmn : additionalPlmns) {
             if (isValidPlmn(plmn)) {
                 mAdditionalPlmns.add(plmn);
@@ -208,8 +210,8 @@
      * @return a list of additional PLMN IDs supported by this cell.
      */
     @NonNull
-    public List<String> getAdditionalPlmns() {
-        return mAdditionalPlmns;
+    public Set<String> getAdditionalPlmns() {
+        return Collections.unmodifiableSet(mAdditionalPlmns);
     }
 
     /**
@@ -289,7 +291,7 @@
         dest.writeInt(mCid);
         dest.writeInt(mCpid);
         dest.writeInt(mUarfcn);
-        dest.writeList(mAdditionalPlmns);
+        dest.writeArraySet(mAdditionalPlmns);
         dest.writeParcelable(mCsgInfo, flags);
     }
 
@@ -300,7 +302,7 @@
         mCid = in.readInt();
         mCpid = in.readInt();
         mUarfcn = in.readInt();
-        mAdditionalPlmns = in.readArrayList(null);
+        mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
         mCsgInfo = in.readParcelable(null);
         if (DBG) log(toString());
     }
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 15e491b..9d2cb74 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -22,11 +22,12 @@
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * CellIdentity to represent a unique UMTS cell
@@ -51,7 +52,7 @@
     private final int mUarfcn;
 
     // a list of additional PLMN-IDs reported for this cell
-    private final List<String> mAdditionalPlmns;
+    private final ArraySet<String> mAdditionalPlmns;
 
     @Nullable
     private final ClosedSubscriberGroupInfo mCsgInfo;
@@ -65,7 +66,7 @@
         mCid = CellInfo.UNAVAILABLE;
         mPsc = CellInfo.UNAVAILABLE;
         mUarfcn = CellInfo.UNAVAILABLE;
-        mAdditionalPlmns = Collections.emptyList();
+        mAdditionalPlmns = new ArraySet<>();
         mCsgInfo = null;
     }
 
@@ -86,13 +87,14 @@
      */
     public CellIdentityWcdma(int lac, int cid, int psc, int uarfcn, @Nullable String mccStr,
             @Nullable String mncStr, @Nullable String alphal, @Nullable String alphas,
-            @NonNull List<String> additionalPlmns, @Nullable ClosedSubscriberGroupInfo csgInfo) {
+            @NonNull Collection<String> additionalPlmns,
+            @Nullable ClosedSubscriberGroupInfo csgInfo) {
         super(TAG, CellInfo.TYPE_WCDMA, mccStr, mncStr, alphal, alphas);
         mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
         mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
         mPsc = inRangeOrUnavailable(psc, 0, MAX_PSC);
         mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
-        mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+        mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
         for (String plmn : additionalPlmns) {
             if (isValidPlmn(plmn)) {
                 mAdditionalPlmns.add(plmn);
@@ -104,14 +106,14 @@
     /** @hide */
     public CellIdentityWcdma(@NonNull android.hardware.radio.V1_0.CellIdentityWcdma cid) {
         this(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc, "", "",
-                Collections.emptyList(), null);
+                new ArraySet<>(), null);
     }
 
     /** @hide */
     public CellIdentityWcdma(@NonNull android.hardware.radio.V1_2.CellIdentityWcdma cid) {
         this(cid.base.lac, cid.base.cid, cid.base.psc, cid.base.uarfcn,
                 cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong,
-                cid.operatorNames.alphaShort, Collections.emptyList(), null);
+                cid.operatorNames.alphaShort, new ArraySet<>(), null);
     }
 
     /** @hide */
@@ -232,8 +234,8 @@
      * @return a list of additional PLMN IDs supported by this cell.
      */
     @NonNull
-    public List<String> getAdditionalPlmns() {
-        return mAdditionalPlmns;
+    public Set<String> getAdditionalPlmns() {
+        return Collections.unmodifiableSet(mAdditionalPlmns);
     }
 
     /**
@@ -305,7 +307,7 @@
         dest.writeInt(mCid);
         dest.writeInt(mPsc);
         dest.writeInt(mUarfcn);
-        dest.writeList(mAdditionalPlmns);
+        dest.writeArraySet(mAdditionalPlmns);
         dest.writeParcelable(mCsgInfo, flags);
     }
 
@@ -316,7 +318,7 @@
         mCid = in.readInt();
         mPsc = in.readInt();
         mUarfcn = in.readInt();
-        mAdditionalPlmns = in.readArrayList(null);
+        mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
         mCsgInfo = in.readParcelable(null);
         if (DBG) log(toString());
     }
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index ec86c14..bfa209b 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -16,9 +16,9 @@
 
 package android.telephony;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.radio.V1_4.CellInfo.Info;
 import android.os.Parcel;
@@ -178,18 +178,18 @@
     /**
      * Approximate time this cell information was received from the modem.
      *
-     * @return a time stamp in nanos since boot.
+     * @return a time stamp in millis since boot.
      */
-    @SuppressLint("MethodNameUnits")
-    public long getTimestampNanos() {
-        return mTimeStamp;
+    @ElapsedRealtimeLong
+    public long getTimestampMillis() {
+        return mTimeStamp / 1000000;
     }
 
     /**
      * Approximate time this cell information was received from the modem.
      *
      * @return a time stamp in nanos since boot.
-     * @deprecated Use {@link #getTimestampNanos} instead.
+     * @deprecated Use {@link #getTimestampMillis} instead.
      */
     @Deprecated
     public long getTimeStamp() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 1cd45e9..2529387 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -181,7 +181,7 @@
         mCqi = CellInfo.UNAVAILABLE;
         mTimingAdvance = CellInfo.UNAVAILABLE;
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        mParametersUseForLevel = USE_RSRP | USE_RSSNR;
+        mParametersUseForLevel = USE_RSRP;
     }
 
     /** {@inheritDoc} */
@@ -236,7 +236,7 @@
         int[] rsrpThresholds, rsrqThresholds, rssnrThresholds;
         boolean rsrpOnly;
         if (cc == null) {
-            mParametersUseForLevel = USE_RSRP | USE_RSSNR;
+            mParametersUseForLevel = USE_RSRP;
             rsrpThresholds = sRsrpThresholds;
             rsrqThresholds = sRsrqThresholds;
             rssnrThresholds = sRssnrThresholds;
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/telephony/java/android/telephony/DisplayInfo.aidl
similarity index 74%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to telephony/java/android/telephony/DisplayInfo.aidl
index fcacd52..861b0fe 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/telephony/java/android/telephony/DisplayInfo.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,9 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.telephony;
 
-package com.android.internal.view.animation;
-
-public interface NativeInterpolatorFactory {
-    long createNativeInterpolator();
-}
+parcelable DisplayInfo;
diff --git a/telephony/java/android/telephony/DisplayInfo.java b/telephony/java/android/telephony/DisplayInfo.java
new file mode 100644
index 0000000..d54bcf9
--- /dev/null
+++ b/telephony/java/android/telephony/DisplayInfo.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.OverrideNetworkType;
+
+import java.util.Objects;
+
+/**
+ * DisplayInfo contains telephony-related information used for display purposes only. This
+ * information is provided in accordance with carrier policy and branding preferences; it is not
+ * necessarily a precise or accurate representation of the current state and should be treated
+ * accordingly.
+ */
+public final class DisplayInfo implements Parcelable {
+    /**
+     * No override. {@link #getNetworkType()} should be used for display network
+     * type.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NONE = 0;
+
+    /**
+     * Override network type when the device is connected to
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network and is using carrier aggregation.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1;
+
+    /**
+     * Override network type when the device is connected to advanced pro
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2;
+
+    /**
+     * Override network type when the device is connected to
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+     * capability or is currently connected to the secondary
+     * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network.
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3;
+
+    /**
+     * Override network type when the device is connected to
+     * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+     * capability or is currently connected to the secondary
+     * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+     *
+     * @see AccessNetworkConstants.NgranBands#FREQUENCY_RANGE_GROUP_2
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
+
+    @NetworkType
+    private final  int mNetworkType;
+
+    @OverrideNetworkType
+    private final  int mOverrideNetworkType;
+
+    /**
+     * Constructor
+     *
+     * @param networkType Current packet-switching cellular network type
+     * @param overrideNetworkType The override network type
+     *
+     * @hide
+     */
+    public DisplayInfo(@NetworkType int networkType, @OverrideNetworkType int overrideNetworkType) {
+        mNetworkType = networkType;
+        mOverrideNetworkType = overrideNetworkType;
+    }
+
+    /** @hide */
+    public DisplayInfo(Parcel p) {
+        mNetworkType = p.readInt();
+        mOverrideNetworkType = p.readInt();
+    }
+
+    /**
+     * Get current packet-switching cellular network type. This is the actual network type the
+     * device is camped on.
+     *
+     * @return The network type.
+     */
+    @NetworkType
+    public int getNetworkType() {
+        return mNetworkType;
+    }
+
+    /**
+     * Get the override network type. Note the override network type is for market branding
+     * or visualization purposes only. It cannot be treated as the actual network type device is
+     * camped on.
+     *
+     * @return The override network type.
+     */
+    @OverrideNetworkType
+    public int getOverrideNetworkType() {
+        return mOverrideNetworkType;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mNetworkType);
+        dest.writeInt(mOverrideNetworkType);
+    }
+
+    public static final @NonNull Parcelable.Creator<DisplayInfo> CREATOR =
+            new Parcelable.Creator<DisplayInfo>() {
+                @Override
+                public DisplayInfo createFromParcel(Parcel source) {
+                    return new DisplayInfo(source);
+                }
+
+                @Override
+                public DisplayInfo[] newArray(int size) {
+                    return new DisplayInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DisplayInfo that = (DisplayInfo) o;
+        return mNetworkType == that.mNetworkType
+                && mOverrideNetworkType == that.mOverrideNetworkType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNetworkType, mOverrideNetworkType);
+    }
+
+    private static String overrideNetworkTypeToString(@OverrideNetworkType int type) {
+        switch (type) {
+            case OVERRIDE_NETWORK_TYPE_NONE: return "NONE";
+            case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
+            case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
+            case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
+            case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+            default: return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+                + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType);
+    }
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index f9de47d..c74e17f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -258,7 +258,7 @@
         mCellIdentity = cellIdentity;
         mEmergencyOnly = emergencyOnly;
         mNrState = NR_STATE_NONE;
-        mRplmn = (rplmn == null) ? "" : rplmn;
+        mRplmn = rplmn;
     }
 
     /**
@@ -408,13 +408,13 @@
      * <p>If the device is registered, this will return the registered PLMN-ID. If registration
      * has failed, then this will return the PLMN ID of the last attempted registration. If the
      * device is not registered, or if is registered to a non-3GPP radio technology, then this
-     * will return an empty string.
+     * will return null.
      *
      * <p>See 3GPP TS 23.122 for further information about the Registered PLMN.
      *
-     * @return the registered PLMN-ID or an empty string.
+     * @return the registered PLMN-ID or null.
      */
-    @NonNull public String getRegisteredPlmn() {
+    @Nullable public String getRegisteredPlmn() {
         return mRplmn;
     }
 
@@ -892,7 +892,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setRegisteredPlmn(@Nullable String rplmn) {
-            mRplmn = (rplmn == null) ? "" : rplmn;
+            mRplmn = rplmn;
             return this;
         }
 
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54980a2..250d9e8 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -256,337 +256,6 @@
     /** Access Blocked by CDMA network. */
     public static final int CDMA_ACCESS_BLOCKED                              = 1009;
 
-    /** Mapped from ImsReasonInfo */
-    // TODO: remove ImsReasonInfo from preciseDisconnectCause
-    /* The passed argument is an invalid */
-    /** @hide */
-    public static final int LOCAL_ILLEGAL_ARGUMENT                           = 1200;
-    // The operation is invoked in invalid call state
-    /** @hide */
-    public static final int LOCAL_ILLEGAL_STATE                              = 1201;
-    // IMS service internal error
-    /** @hide */
-    public static final int LOCAL_INTERNAL_ERROR                             = 1202;
-    // IMS service goes down (service connection is lost)
-    /** @hide */
-    public static final int LOCAL_IMS_SERVICE_DOWN                           = 1203;
-    // No pending incoming call exists
-    /** @hide */
-    public static final int LOCAL_NO_PENDING_CALL                            = 1204;
-    // Service unavailable; by power off
-    /** @hide */
-    public static final int LOCAL_POWER_OFF                                  = 1205;
-    // Service unavailable; by low battery
-    /** @hide */
-    public static final int LOCAL_LOW_BATTERY                                = 1206;
-    // Service unavailable; by out of service (data service state)
-    /** @hide */
-    public static final int LOCAL_NETWORK_NO_SERVICE                         = 1207;
-    /* Service unavailable; by no LTE coverage
-     * (VoLTE is not supported even though IMS is registered)
-     */
-    /** @hide */
-    public static final int LOCAL_NETWORK_NO_LTE_COVERAGE                    = 1208;
-    /** Service unavailable; by located in roaming area */
-    /** @hide */
-    public static final int LOCAL_NETWORK_ROAMING                            = 1209;
-    /** Service unavailable; by IP changed */
-    /** @hide */
-    public static final int LOCAL_NETWORK_IP_CHANGED                         = 1210;
-    /** Service unavailable; other */
-    /** @hide */
-    public static final int LOCAL_SERVICE_UNAVAILABLE                        = 1211;
-    /* Service unavailable; IMS connection is lost (IMS is not registered) */
-    /** @hide */
-    public static final int LOCAL_NOT_REGISTERED                             = 1212;
-    /** Max call exceeded */
-    /** @hide */
-    public static final int LOCAL_MAX_CALL_EXCEEDED                          = 1213;
-    /** Call decline */
-    /** @hide */
-    public static final int LOCAL_CALL_DECLINE                               = 1214;
-    /** SRVCC is in progress */
-    /** @hide */
-    public static final int LOCAL_CALL_VCC_ON_PROGRESSING                    = 1215;
-    /** Resource reservation is failed (QoS precondition) */
-    /** @hide */
-    public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED           = 1216;
-    /** Retry CS call; VoLTE service can't be provided by the network or remote end
-     *  Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
-     *  @hide
-     */
-    public static final int LOCAL_CALL_CS_RETRY_REQUIRED                     = 1217;
-    /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
-    /** @hide */
-    public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED                  = 1218;
-    /** IMS call is already terminated (in TERMINATED state) */
-    /** @hide */
-    public static final int LOCAL_CALL_TERMINATED                            = 1219;
-    /** Handover not feasible */
-    /** @hide */
-    public static final int LOCAL_HO_NOT_FEASIBLE                            = 1220;
-
-    /** 1xx waiting timer is expired after sending INVITE request (MO only) */
-    /** @hide */
-    public static final int TIMEOUT_1XX_WAITING                              = 1221;
-    /** User no answer during call setup operation (MO/MT)
-     *  MO : 200 OK to INVITE request is not received,
-     *  MT : No action from user after alerting the call
-     *  @hide
-     */
-    public static final int TIMEOUT_NO_ANSWER                                = 1222;
-    /** User no answer during call update operation (MO/MT)
-     *  MO : 200 OK to re-INVITE request is not received,
-     *  MT : No action from user after alerting the call
-     *  @hide
-     */
-    public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE                    = 1223;
-
-    /**
-     * STATUSCODE (SIP response code) (IMS -> Telephony)
-     */
-    /** SIP request is redirected */
-    /** @hide */
-    public static final int SIP_REDIRECTED                                   = 1300;
-    /** 4xx responses */
-    /** 400 : Bad Request */
-    /** @hide */
-    public static final int SIP_BAD_REQUEST                                  = 1310;
-    /** 403 : Forbidden */
-    /** @hide */
-    public static final int SIP_FORBIDDEN                                    = 1311;
-    /** 404 : Not Found */
-    /** @hide */
-    public static final int SIP_NOT_FOUND                                    = 1312;
-    /** 415 : Unsupported Media Type
-     *  416 : Unsupported URI Scheme
-     *  420 : Bad Extension
-     */
-    /** @hide */
-    public static final int SIP_NOT_SUPPORTED                                = 1313;
-    /** 408 : Request Timeout */
-    /** @hide */
-    public static final int SIP_REQUEST_TIMEOUT                              = 1314;
-    /** 480 : Temporarily Unavailable */
-    /** @hide */
-    public static final int SIP_TEMPRARILY_UNAVAILABLE                       = 1315;
-    /** 484 : Address Incomplete */
-    /** @hide */
-    public static final int SIP_BAD_ADDRESS                                  = 1316;
-    /** 486 : Busy Here
-     *  600 : Busy Everywhere
-     */
-    /** @hide */
-    public static final int SIP_BUSY                                         = 1317;
-    /** 487 : Request Terminated */
-    /** @hide */
-    public static final int SIP_REQUEST_CANCELLED                            = 1318;
-    /** 406 : Not Acceptable
-     *  488 : Not Acceptable Here
-     *  606 : Not Acceptable
-     */
-    /** @hide */
-    public static final int SIP_NOT_ACCEPTABLE                               = 1319;
-    /** 410 : Gone
-     *  604 : Does Not Exist Anywhere
-     */
-    /** @hide */
-    public static final int SIP_NOT_REACHABLE                                = 1320;
-    /** Others */
-    /** @hide */
-    public static final int SIP_CLIENT_ERROR                                 = 1321;
-    /** 481 : Transaction Does Not Exist */
-    /** @hide */
-    public static final int SIP_TRANSACTION_DOES_NOT_EXIST                   = 1322;
-    /** 5xx responses
-     *  501 : Server Internal Error
-     */
-    /** @hide */
-    public static final int SIP_SERVER_INTERNAL_ERROR                        = 1330;
-    /** 503 : Service Unavailable */
-    /** @hide */
-    public static final int SIP_SERVICE_UNAVAILABLE                          = 1331;
-    /** 504 : Server Time-out */
-    /** @hide */
-    public static final int SIP_SERVER_TIMEOUT                               = 1332;
-    /** Others */
-    /** @hide */
-    public static final int SIP_SERVER_ERROR                                 = 1333;
-    /** 6xx responses
-     *  603 : Decline
-     */
-    /** @hide */
-    public static final int SIP_USER_REJECTED                                = 1340;
-    /** Others */
-    /** @hide */
-    public static final int SIP_GLOBAL_ERROR                                 = 1341;
-    /** Emergency failure */
-    /** @hide */
-    public static final int EMERGENCY_TEMP_FAILURE                           = 1342;
-    /** @hide */
-    public static final int EMERGENCY_PERM_FAILURE                           = 1343;
-    /** Media resource initialization failed */
-    /** @hide */
-    public static final int MEDIA_INIT_FAILED                                = 1400;
-    /** RTP timeout (no audio / video traffic in the session) */
-    /** @hide */
-    public static final int MEDIA_NO_DATA                                    = 1401;
-    /** Media is not supported; so dropped the call */
-    /** @hide */
-    public static final int MEDIA_NOT_ACCEPTABLE                             = 1402;
-    /** Unknown media related errors */
-    /** @hide */
-    public static final int MEDIA_UNSPECIFIED                                = 1403;
-    /** User triggers the call end */
-    /** @hide */
-    public static final int USER_TERMINATED                                  = 1500;
-    /** No action while an incoming call is ringing */
-    /** @hide */
-    public static final int USER_NOANSWER                                    = 1501;
-    /** User ignores an incoming call */
-    /** @hide */
-    public static final int USER_IGNORE                                      = 1502;
-    /** User declines an incoming call */
-    /** @hide */
-    public static final int USER_DECLINE                                     = 1503;
-    /** Device declines/ends a call due to low battery */
-    /** @hide */
-    public static final int LOW_BATTERY                                      = 1504;
-    /** Device declines call due to blacklisted call ID */
-    /** @hide */
-    public static final int BLACKLISTED_CALL_ID                              = 1505;
-    /** The call is terminated by the network or remote user */
-    /** @hide */
-    public static final int USER_TERMINATED_BY_REMOTE                        = 1510;
-
-    /**
-     * UT
-     */
-    /** @hide */
-    public static final int UT_NOT_SUPPORTED                                 = 1800;
-    /** @hide */
-    public static final int UT_SERVICE_UNAVAILABLE                           = 1801;
-    /** @hide */
-    public static final int UT_OPERATION_NOT_ALLOWED                         = 1802;
-    /** @hide */
-    public static final int UT_NETWORK_ERROR                                 = 1803;
-    /** @hide */
-    public static final int UT_CB_PASSWORD_MISMATCH                          = 1804;
-
-    /**
-     * ECBM
-     * @hide
-     */
-    public static final int ECBM_NOT_SUPPORTED                               = 1900;
-
-    /**
-     * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
-     * @hide
-     */
-    public static final int MULTIENDPOINT_NOT_SUPPORTED                      = 1901;
-
-    /**
-     * CALL DROP error codes (Call could drop because of many reasons like Network not available,
-     *  handover, failed, etc)
-     */
-
-    /**
-     * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
-     * active wifi call and at the edge of coverage and there is no qualified LTE network available
-     * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
-     * code is received as part of the handover message.
-     * @hide
-     */
-    public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE               = 2000;
-
-    /**
-     * MT call has ended due to a release from the network
-     * because the call was answered elsewhere
-     * @hide
-     */
-    public static final int ANSWERED_ELSEWHERE                               = 2100;
-
-    /**
-     * For MultiEndpoint - Call Pull request has failed
-     * @hide
-     */
-    public static final int CALL_PULL_OUT_OF_SYNC                            = 2101;
-
-    /**
-     * For MultiEndpoint - Call has been pulled from primary to secondary
-     * @hide
-     */
-    public static final int CALL_PULLED                                      = 2102;
-
-    /**
-     * Supplementary services (HOLD/RESUME) failure error codes.
-     * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
-     * @hide
-     */
-    public static final int SUPP_SVC_FAILED                                  = 2300;
-    /** @hide */
-    public static final int SUPP_SVC_CANCELLED                               = 2301;
-    /** @hide */
-    public static final int SUPP_SVC_REINVITE_COLLISION                      = 2302;
-
-    /**
-     * DPD Procedure received no response or send failed
-     * @hide
-     */
-    public static final int IWLAN_DPD_FAILURE                                = 2400;
-
-    /**
-     * Establishment of the ePDG Tunnel Failed
-     * @hide
-     */
-    public static final int EPDG_TUNNEL_ESTABLISH_FAILURE                    = 2500;
-
-    /**
-     * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
-     * @hide
-     */
-    public static final int EPDG_TUNNEL_REKEY_FAILURE                        = 2501;
-
-    /**
-     * Connection to the packet gateway is lost
-     * @hide
-     */
-    public static final int EPDG_TUNNEL_LOST_CONNECTION                      = 2502;
-
-    /**
-     * The maximum number of calls allowed has been reached.  Used in a multi-endpoint scenario
-     * where the number of calls across all connected devices has reached the maximum.
-     * @hide
-     */
-    public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED                  = 2503;
-
-    /**
-     * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
-     * declined the call.  Used in a multi-endpoint scenario where a remote device declined an
-     * incoming call.
-     * @hide
-     */
-    public static final int REMOTE_CALL_DECLINE                              = 2504;
-
-    /**
-     * Indicates the call was disconnected due to the user reaching their data limit.
-     * @hide
-     */
-    public static final int DATA_LIMIT_REACHED                               = 2505;
-
-    /**
-     * Indicates the call was disconnected due to the user disabling cellular data.
-     * @hide
-     */
-    public static final int DATA_DISABLED                                    = 2506;
-
-    /**
-     * Indicates a call was disconnected due to loss of wifi signal.
-     * @hide
-     */
-    public static final int WIFI_LOST                                        = 2507;
-
-
     /* OEM specific error codes. To be used by OEMs when they don't want to
        reveal error code which would be replaced by ERROR_UNSPECIFIED */
     public static final int OEM_CAUSE_1                                      = 0xf001;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 5a4dac5..ad58c54 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,8 +16,8 @@
 
 package android.telephony;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -79,8 +79,9 @@
     /* The type of signal measurement */
     private static final String MEASUREMENT_TYPE_RSCP = "rscp";
 
-    // timeStamp of signalStrength in nanoseconds since boot
-    private long mTimestamp = Long.MAX_VALUE;
+    // Timestamp of SignalStrength since boot
+    // Effectively final. Timestamp is set during construction of SignalStrength
+    private long mTimestampMillis;
 
     CellSignalStrengthCdma mCdma;
     CellSignalStrengthGsm mGsm;
@@ -139,7 +140,7 @@
         mTdscdma = tdscdma;
         mLte = lte;
         mNr = nr;
-        mTimestamp = SystemClock.elapsedRealtimeNanos();
+        mTimestampMillis = SystemClock.elapsedRealtime();
     }
 
     /**
@@ -274,7 +275,6 @@
         mTdscdma.updateLevel(cc, ss);
         mLte.updateLevel(cc, ss);
         mNr.updateLevel(cc, ss);
-        mTimestamp = SystemClock.elapsedRealtimeNanos();
     }
 
     /**
@@ -300,7 +300,7 @@
         mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma);
         mLte = new CellSignalStrengthLte(s.mLte);
         mNr = new CellSignalStrengthNr(s.mNr);
-        mTimestamp = s.getTimestampNanos();
+        mTimestampMillis = s.getTimestampMillis();
     }
 
     /**
@@ -318,7 +318,7 @@
         mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
         mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
         mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
-        mTimestamp = in.readLong();
+        mTimestampMillis = in.readLong();
     }
 
     /**
@@ -331,15 +331,17 @@
         out.writeParcelable(mTdscdma, flags);
         out.writeParcelable(mLte, flags);
         out.writeParcelable(mNr, flags);
-        out.writeLong(mTimestamp);
+        out.writeLong(mTimestampMillis);
     }
 
     /**
-     * @return mTimestamp in nanoseconds
+     * @return timestamp in milliseconds since boot for {@link SignalStrength}.
+     * This timestamp reports the approximate time that the signal was measured and reported
+     * by the modem. It can be used to compare the recency of {@link SignalStrength} instances.
      */
-    @SuppressLint("MethodNameUnits")
-    public long getTimestampNanos() {
-        return mTimestamp;
+    @ElapsedRealtimeLong
+    public long getTimestampMillis() {
+        return mTimestampMillis;
     }
 
    /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 15103bf..68b6683 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13135,7 +13135,7 @@
     */
     static IPhoneSubInfo getSubscriberInfoService() {
         // Keeps cache disabled until test fixes are checked into AOSP.
-        if (true) {
+        if (!sServiceHandleCacheEnabled) {
             return IPhoneSubInfo.Stub.asInterface(
                 TelephonyFrameworkInitializer
                         .getTelephonyServiceManager()
@@ -13169,7 +13169,7 @@
     */
     static ISub getSubscriptionService() {
         // Keeps cache disabled until test fixes are checked into AOSP.
-        if (true) {
+        if (!sServiceHandleCacheEnabled) {
             return ISub.Stub.asInterface(
                     TelephonyFrameworkInitializer
                             .getTelephonyServiceManager()
@@ -13203,7 +13203,7 @@
     */
     static ISms getSmsService() {
         // Keeps cache disabled until test fixes are checked into AOSP.
-        if (true) {
+        if (!sServiceHandleCacheEnabled) {
             return ISms.Stub.asInterface(
                     TelephonyFrameworkInitializer
                             .getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index c40573b..6fdc13e 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -492,6 +492,7 @@
     int RIL_REQUEST_ENABLE_UICC_APPLICATIONS = 208;
     int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209;
     int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
+    int RIL_REQUEST_GET_BARRING_INFO = 211;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 20d0e96..ae20cae 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -430,10 +430,9 @@
     private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
         String apkPath = getApkPath(TARGET_PACKAGE);
         String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+        // Exclude directories since we only care about files.
         HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
-                expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
-        assertTrue(actualFiles.remove("lib"));
-        assertTrue(actualFiles.remove("oat"));
+                expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n")));
 
         HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
         assertEquals(expectedFiles, actualFiles);
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 95b8f67..da45d9a 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -296,6 +296,8 @@
                     AppLaunchResult launchResults = null;
                     if (hasFailureOnFirstLaunch(launch)) {
                         // skip if the app has failures while launched first
+                        Log.w(TAG, "Has failures on first launch: " + launch.getApp());
+                        forceStopApp(launch.getApp());
                         continue;
                     }
                     AtraceLogger atraceLogger = null;
diff --git a/tests/RollbackTest/RollbackTest/AndroidManifest.xml b/tests/RollbackTest/RollbackTest/AndroidManifest.xml
index 2b8c964..9274da2 100644
--- a/tests/RollbackTest/RollbackTest/AndroidManifest.xml
+++ b/tests/RollbackTest/RollbackTest/AndroidManifest.xml
@@ -17,6 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.tests.rollback" >
 
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 155c61f..eb78529 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -148,6 +148,7 @@
     @Mock private AppOpsManager mAppOps;
     @Mock private NotificationManager mNotificationManager;
     @Mock private Vpn.SystemServices mSystemServices;
+    @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
     @Mock private ConnectivityManager mConnectivityManager;
     @Mock private KeyStore mKeyStore;
     private final VpnProfile mVpnProfile = new VpnProfile("key");
@@ -867,7 +868,8 @@
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
-        return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
+        return new Vpn(Looper.myLooper(), mContext, mNetService,
+                userId, mSystemServices, mIkev2SessionCreator);
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index dc5ac1e..e1450cb 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -486,7 +486,7 @@
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
                 allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
                 allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                requirePMF = true;
+                requirePmf = true;
                 break;
             case SECURITY_TYPE_EAP_SUITE_B:
                 allowedProtocols.set(WifiConfiguration.Protocol.RSN);
@@ -496,14 +496,14 @@
                 allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
                 // Note: allowedSuiteBCiphers bitset will be set by the service once the
                 // certificates are attached to this profile
-                requirePMF = true;
+                requirePmf = true;
                 break;
             case SECURITY_TYPE_OWE:
                 allowedProtocols.set(WifiConfiguration.Protocol.RSN);
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
                 allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
                 allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                requirePMF = true;
+                requirePmf = true;
                 break;
             case SECURITY_TYPE_WAPI_PSK:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_PSK);
@@ -662,7 +662,7 @@
      * @hide
      */
     @SystemApi
-    public boolean requirePMF;
+    public boolean requirePmf;
 
     /**
      * Update identifier, for Passpoint network.
@@ -2169,7 +2169,7 @@
                 append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN)
                 .append(" PRIO: ").append(this.priority)
                 .append(" HIDDEN: ").append(this.hiddenSSID)
-                .append(" PMF: ").append(this.requirePMF)
+                .append(" PMF: ").append(this.requirePmf)
                 .append("CarrierId: ").append(this.carrierId)
                 .append('\n');
 
@@ -2761,7 +2761,7 @@
             mRandomizedMacAddress = source.mRandomizedMacAddress;
             macRandomizationSetting = source.macRandomizationSetting;
             randomizedMacExpirationTimeMs = source.randomizedMacExpirationTimeMs;
-            requirePMF = source.requirePMF;
+            requirePmf = source.requirePmf;
             updateIdentifier = source.updateIdentifier;
             carrierId = source.carrierId;
             mPasspointUniqueId = source.mPasspointUniqueId;
@@ -2793,7 +2793,7 @@
         dest.writeInt(wepTxKeyIndex);
         dest.writeInt(priority);
         dest.writeInt(hiddenSSID ? 1 : 0);
-        dest.writeInt(requirePMF ? 1 : 0);
+        dest.writeInt(requirePmf ? 1 : 0);
         dest.writeString(updateIdentifier);
 
         writeBitSet(dest, allowedKeyManagement);
@@ -2869,7 +2869,7 @@
                 config.wepTxKeyIndex = in.readInt();
                 config.priority = in.readInt();
                 config.hiddenSSID = in.readInt() != 0;
-                config.requirePMF = in.readInt() != 0;
+                config.requirePmf = in.readInt() != 0;
                 config.updateIdentifier = in.readString();
 
                 config.allowedKeyManagement   = readBitSet(in);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b6f4490..f693315 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -28,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.ActivityManager;
@@ -1621,11 +1622,13 @@
          * @param wifiConfiguration WifiConfiguration object corresponding to the network
          *                          user selected.
          */
+        @SuppressLint("CallbackMethodName")
         default void select(@NonNull WifiConfiguration wifiConfiguration) {}
 
         /**
          * User rejected the app's request.
          */
+        @SuppressLint("CallbackMethodName")
         default void reject() {}
     }
 
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 9990180..9c01d36 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -738,7 +738,16 @@
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
-            builder.append("IMSI: ").append(mImsi).append("\n");
+            String imsi;
+            if (mImsi != null) {
+                if (mImsi.length() > 6 && mImsi.charAt(6) != '*') {
+                    // Truncate the full IMSI from the log
+                    imsi = mImsi.substring(0, 6) + "****";
+                } else {
+                    imsi = mImsi;
+                }
+                builder.append("IMSI: ").append(imsi).append("\n");
+            }
             builder.append("EAPType: ").append(mEapType).append("\n");
             return builder.toString();
         }
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java
similarity index 98%
rename from wifi/java/android/net/wifi/wificond/WifiCondManager.java
rename to wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java
index 61f18e0..89f642f 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java
@@ -49,15 +49,16 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The
+ * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used
+ * to encapsulate the Wi-Fi 80211nl management interface. The
  * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
  *
  * @hide
  */
 @SystemApi
-@SystemService(Context.WIFI_COND_SERVICE)
-public class WifiCondManager {
-    private static final String TAG = "WifiCondManager";
+@SystemService(Context.WIFI_NL80211_SERVICE)
+public class WifiNl80211Manager {
+    private static final String TAG = "WifiNl80211Manager";
     private boolean mVerboseLoggingEnabled = false;
 
     /**
@@ -316,14 +317,14 @@
     public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
 
     /** @hide */
-    public WifiCondManager(Context context) {
+    public WifiNl80211Manager(Context context) {
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mEventHandler = new Handler(context.getMainLooper());
     }
 
     /** @hide */
     @VisibleForTesting
-    public WifiCondManager(Context context, IWificond wificond) {
+    public WifiNl80211Manager(Context context, IWificond wificond) {
         this(context);
         mWificond = wificond;
     }
@@ -485,7 +486,7 @@
     }
 
     /**
-     * Enable or disable verbose logging of the WifiCondManager module.
+     * Enable or disable verbose logging of the WifiNl80211Manager module.
      * @param enable True to enable verbose logging. False to disable verbose logging.
      */
     public void enableVerboseLogging(boolean enable) {
@@ -493,7 +494,7 @@
     }
 
     /**
-     * Register a death notification for the WifiCondManager which acts as a proxy for the
+     * Register a death notification for the WifiNl80211Manager which acts as a proxy for the
      * wificond daemon (i.e. the death listener will be called when and if the wificond daemon
      * dies).
      *
@@ -518,7 +519,7 @@
             // We already have a wificond handle.
             return true;
         }
-        IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE);
+        IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE);
         mWificond = IWificond.Stub.asInterface(binder);
         if (mWificond == null) {
             Log.e(TAG, "Failed to get reference to wificond");
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 05a3dce..00790d5 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -363,7 +363,7 @@
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE));
         assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
         assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
-        assertTrue(config.requirePMF);
+        assertTrue(config.requirePmf);
     }
 
     /**
@@ -380,7 +380,7 @@
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE));
         assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
         assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
-        assertTrue(config.requirePMF);
+        assertTrue(config.requirePmf);
     }
 
     /**
@@ -399,7 +399,7 @@
         assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
         assertTrue(config.allowedGroupManagementCiphers
                 .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
-        assertTrue(config.requirePMF);
+        assertTrue(config.requirePmf);
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index af6fb5c..51bf738 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -140,7 +140,7 @@
         assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
                 .get(WifiConfiguration.KeyMgmt.OWE));
         assertNull(suggestion.wifiConfiguration.preSharedKey);
-        assertTrue(suggestion.wifiConfiguration.requirePMF);
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
         assertTrue(suggestion.isInitialAutoJoinEnabled);
     }
@@ -163,7 +163,7 @@
                 .get(WifiConfiguration.KeyMgmt.SAE));
         assertEquals("\"" + TEST_PRESHARED_KEY + "\"",
                 suggestion.wifiConfiguration.preSharedKey);
-        assertTrue(suggestion.wifiConfiguration.requirePMF);
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
         assertFalse(suggestion.isInitialAutoJoinEnabled);
     }
@@ -191,7 +191,7 @@
                 .get(WifiConfiguration.GroupCipher.GCMP_256));
         assertTrue(suggestion.wifiConfiguration.allowedGroupManagementCiphers
                 .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
-        assertTrue(suggestion.wifiConfiguration.requirePMF);
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
         assertNull(suggestion.wifiConfiguration.preSharedKey);
         // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
         // here.
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java
similarity index 95%
rename from wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
rename to wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java
index b745a34..a818406 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java
@@ -71,10 +71,10 @@
 import java.util.Set;
 
 /**
- * Unit tests for {@link android.net.wifi.WifiCondManager}.
+ * Unit tests for {@link android.net.wifi.wificond.WifiNl80211Manager}.
  */
 @SmallTest
-public class WifiCondManagerTest {
+public class WifiNl80211ManagerTest {
     @Mock
     private IWificond mWificond;
     @Mock
@@ -86,21 +86,21 @@
     @Mock
     private IApInterface mApInterface;
     @Mock
-    private WifiCondManager.SoftApCallback mSoftApListener;
+    private WifiNl80211Manager.SoftApCallback mSoftApListener;
     @Mock
-    private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+    private WifiNl80211Manager.SendMgmtFrameCallback mSendMgmtFrameCallback;
     @Mock
-    private WifiCondManager.ScanEventCallback mNormalScanCallback;
+    private WifiNl80211Manager.ScanEventCallback mNormalScanCallback;
     @Mock
-    private WifiCondManager.ScanEventCallback mPnoScanCallback;
+    private WifiNl80211Manager.ScanEventCallback mPnoScanCallback;
     @Mock
-    private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback;
+    private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
     @Mock
     private Context mContext;
     private TestLooper mLooper;
     private TestAlarmManager mTestAlarmManager;
     private AlarmManager mAlarmManager;
-    private WifiCondManager mWificondControl;
+    private WifiNl80211Manager mWificondControl;
     private static final String TEST_INTERFACE_NAME = "test_wlan_if";
     private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
     private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
@@ -180,7 +180,7 @@
         when(mWificond.tearDownApInterface(any())).thenReturn(true);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-        mWificondControl = new WifiCondManager(mContext, mWificond);
+        mWificondControl = new WifiNl80211Manager(mContext, mWificond);
         assertEquals(true,
                 mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
                         mNormalScanCallback, mPnoScanCallback));
@@ -492,7 +492,7 @@
         // getScanResults should fail.
         assertEquals(0,
                 mWificondControl.getScanResults(TEST_INTERFACE_NAME,
-                        WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size());
+                        WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN).size());
     }
 
     /**
@@ -786,10 +786,10 @@
      */
     @Test
     public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
-        WifiCondManager.SendMgmtFrameCallback cb1 = mock(
-                WifiCondManager.SendMgmtFrameCallback.class);
-        WifiCondManager.SendMgmtFrameCallback cb2 = mock(
-                WifiCondManager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb1 = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb2 = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
 
         mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
                 Runnable::run, cb1);
@@ -800,7 +800,7 @@
 
         mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
                 Runnable::run, cb2);
-        verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+        verify(cb2).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
         // verify SendMgmtFrame() still was only called once i.e. not called again
         verify(mClientInterface, times(1))
                 .SendMgmtFrame(any(), any(), anyInt());
@@ -811,8 +811,8 @@
      */
     @Test
     public void testSendMgmtFrameThrowsException() throws Exception {
-        WifiCondManager.SendMgmtFrameCallback cb = mock(
-                WifiCondManager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
 
         final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
                 ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -834,7 +834,7 @@
         verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
 
         sendMgmtFrameEventCaptor.getValue().OnFailure(
-                WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
         mLooper.dispatchAll();
 
         handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
@@ -848,8 +848,8 @@
      */
     @Test
     public void testSendMgmtFrameSuccess() throws Exception {
-        WifiCondManager.SendMgmtFrameCallback cb = mock(
-                WifiCondManager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
 
         final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
                 ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -882,8 +882,8 @@
      */
     @Test
     public void testSendMgmtFrameFailure() throws Exception {
-        WifiCondManager.SendMgmtFrameCallback cb = mock(
-                WifiCondManager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
 
         final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
                 ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -898,10 +898,10 @@
                 Runnable::run, cb);
 
         sendMgmtFrameEventCaptor.getValue().OnFailure(
-                WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
         mLooper.dispatchAll();
         verify(cb, never()).onAck(anyInt());
-        verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
         verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
 
         // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
@@ -917,8 +917,8 @@
      */
     @Test
     public void testSendMgmtFrameTimeout() throws Exception {
-        WifiCondManager.SendMgmtFrameCallback cb = mock(
-                WifiCondManager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
 
         final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
                 ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -935,7 +935,7 @@
         handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
         mLooper.dispatchAll();
         verify(cb, never()).onAck(anyInt());
-        verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+        verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
 
         // verify that even if onAck() callback is triggered after timeout,
         // SendMgmtFrameCallback is not triggered again
@@ -1006,7 +1006,8 @@
         sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
         mLooper.dispatchAll();
         verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
-        verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+        verify(mSendMgmtFrameCallback).onFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
     }
 
     /**
@@ -1032,9 +1033,10 @@
         handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
         // OnFailure posts to the handler
         sendMgmtFrameEventCaptor.getValue().OnFailure(
-                WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
         mLooper.dispatchAll();
-        verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+        verify(mSendMgmtFrameCallback).onFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
     }
 
     /**