Merge "Add default_teams to TouchLatency build targets" into main
diff --git a/Android.bp b/Android.bp
index 4f715f8..d6b303f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,7 +97,7 @@
         // AIDL sources from external directories
         ":android.frameworks.location.altitude-V2-java-source",
         ":android.hardware.biometrics.common-V4-java-source",
-        ":android.hardware.biometrics.fingerprint-V3-java-source",
+        ":android.hardware.biometrics.fingerprint-V5-java-source",
         ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 75e2efd2..e20f525 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -28,3 +28,13 @@
     description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
     bug: "299329948"
 }
+
+flag {
+    name: "count_quota_fix"
+    namespace: "backstage_power"
+    description: "Fix job count quota check"
+    bug: "300862949"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 012ede2..3bb395f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1650,6 +1650,16 @@
                     continue;
                 }
 
+                if (Flags.countQuotaFix() && !nextPending.isReady()) {
+                    // This could happen when the constraints for the job have been marked
+                    // as unsatisfiled but hasn't been removed from the pending queue yet.
+                    if (DEBUG) {
+                        Slog.w(TAG, "Pending+not ready job: " + nextPending);
+                    }
+                    pendingJobQueue.remove(nextPending);
+                    continue;
+                }
+
                 if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
                     Slog.w(TAG, "Already running similar job to: " + nextPending);
                 }
@@ -1737,6 +1747,16 @@
                     continue;
                 }
 
+                if (Flags.countQuotaFix() && !nextPending.isReady()) {
+                    // This could happen when the constraints for the job have been marked
+                    // as unsatisfiled but hasn't been removed from the pending queue yet.
+                    if (DEBUG) {
+                        Slog.w(TAG, "Pending+not ready job: " + nextPending);
+                    }
+                    pendingJobQueue.remove(nextPending);
+                    continue;
+                }
+
                 if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
                     Slog.w(TAG, "Already running similar job to: " + nextPending);
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 3c9648b..c240b3f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -70,6 +70,7 @@
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
 import com.android.server.job.ConstantsProto;
+import com.android.server.job.Flags;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 import com.android.server.usage.AppStandbyInternal;
@@ -512,7 +513,7 @@
 
     /** An app has reached its quota. The message should contain a {@link UserPackage} object. */
     @VisibleForTesting
-    static final int MSG_REACHED_QUOTA = 0;
+    static final int MSG_REACHED_TIME_QUOTA = 0;
     /** Drop any old timing sessions. */
     private static final int MSG_CLEAN_UP_SESSIONS = 1;
     /** Check if a package is now within its quota. */
@@ -524,7 +525,7 @@
      * object.
      */
     @VisibleForTesting
-    static final int MSG_REACHED_EJ_QUOTA = 4;
+    static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
     /**
      * Process a new {@link UsageEvents.Event}. The event will be the message's object and the
      * userId will the first arg.
@@ -533,6 +534,11 @@
     /** A UID's free quota grace period has ended. */
     @VisibleForTesting
     static final int MSG_END_GRACE_PERIOD = 6;
+    /**
+     * An app has reached its job count quota. The message should contain a {@link UserPackage}
+     * object.
+     */
+    static final int MSG_REACHED_COUNT_QUOTA = 7;
 
     public QuotaController(@NonNull JobSchedulerService service,
             @NonNull BackgroundJobsController backgroundJobsController,
@@ -874,17 +880,46 @@
     }
 
     @VisibleForTesting
+    @GuardedBy("mLock")
     boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
         final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
         // A job is within quota if one of the following is true:
         //   1. it was started while the app was in the TOP state
         //   2. the app is currently in the foreground
         //   3. the app overall is within its quota
-        return jobStatus.shouldTreatAsUserInitiatedJob()
+        if (!Flags.countQuotaFix()) {
+            return jobStatus.shouldTreatAsUserInitiatedJob()
+                    || isTopStartedJobLocked(jobStatus)
+                    || isUidInForeground(jobStatus.getSourceUid())
+                    || isWithinQuotaLocked(
+                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+        }
+
+        if (jobStatus.shouldTreatAsUserInitiatedJob()
                 || isTopStartedJobLocked(jobStatus)
-                || isUidInForeground(jobStatus.getSourceUid())
-                || isWithinQuotaLocked(
-                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+                || isUidInForeground(jobStatus.getSourceUid())) {
+            return true;
+        }
+
+        if (standbyBucket == NEVER_INDEX) return false;
+
+        if (isQuotaFreeLocked(standbyBucket)) return true;
+
+        final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
+                jobStatus.getSourcePackageName(), standbyBucket);
+        if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
+            // Out of execution time quota.
+            return false;
+        }
+
+        if (standbyBucket != RESTRICTED_INDEX && mService.isCurrentlyRunningLocked(jobStatus)) {
+            // Running job is considered as within quota except for the restricted one, which
+            // requires additional constraints.
+            return true;
+        }
+
+        // Check if the app is within job count quota.
+        return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
     }
 
     @GuardedBy("mLock")
@@ -909,12 +944,11 @@
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
         // TODO: use a higher minimum remaining time for jobs with MINIMUM priority
         return getRemainingExecutionTimeLocked(stats) > 0
-                && isUnderJobCountQuotaLocked(stats, standbyBucket)
-                && isUnderSessionCountQuotaLocked(stats, standbyBucket);
+                && isUnderJobCountQuotaLocked(stats)
+                && isUnderSessionCountQuotaLocked(stats);
     }
 
-    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
-            final int standbyBucket) {
+    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
         final long now = sElapsedRealtimeClock.millis();
         final boolean isUnderAllowedTimeQuota =
                 (stats.jobRateLimitExpirationTimeElapsed <= now
@@ -923,8 +957,7 @@
                 && stats.bgJobCountInWindow < stats.jobCountLimit;
     }
 
-    private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
-            final int standbyBucket) {
+    private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
         final long now = sElapsedRealtimeClock.millis();
         final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
                 || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1449,6 +1482,9 @@
                 stats.jobCountInRateLimitingWindow = 0;
             }
             stats.jobCountInRateLimitingWindow += count;
+            if (Flags.countQuotaFix()) {
+                stats.bgJobCountInWindow += count;
+            }
         }
     }
 
@@ -1683,10 +1719,11 @@
                     changedJobs.add(js);
                 }
             } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
-                    && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+                    && realStandbyBucket == js.getEffectiveStandbyBucket()
+                    && !(Flags.countQuotaFix() && mService.isCurrentlyRunningLocked(js))) {
                 // An app in the ACTIVE bucket may be out of quota while the job could be in quota
                 // for some reason. Therefore, avoid setting the real value here and check each job
-                // individually.
+                // individually. Running job need to determine its own quota status as well.
                 if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
                     changedJobs.add(js);
                 }
@@ -1805,9 +1842,8 @@
         }
 
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
-        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
-        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
-                standbyBucket);
+        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
+        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
         final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
 
         final boolean inRegularQuota =
@@ -2126,6 +2162,13 @@
                 mBgJobCount++;
                 if (mRegularJobTimer) {
                     incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
+                    if (Flags.countQuotaFix()) {
+                        final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
+                                mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
+                        if (!isUnderJobCountQuotaLocked(stats)) {
+                            mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
+                        }
+                    }
                 }
                 if (mRunningBgJobs.size() == 1) {
                     // Started tracking the first job.
@@ -2257,7 +2300,6 @@
                     // repeatedly plugged in and unplugged, or an app changes foreground state
                     // very frequently, the job count for a package may be artificially high.
                     mBgJobCount = mRunningBgJobs.size();
-
                     if (mRegularJobTimer) {
                         incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
                         // Starting the timer means that all cached execution stats are now
@@ -2284,7 +2326,8 @@
                     return;
                 }
                 Message msg = mHandler.obtainMessage(
-                        mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+                        mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
+                        mPkg);
                 final long timeRemainingMs = mRegularJobTimer
                         ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
                         : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2301,7 +2344,7 @@
 
         private void cancelCutoff() {
             mHandler.removeMessages(
-                    mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+                    mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
         }
 
         public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2557,7 +2600,7 @@
                     break;
                 default:
                     if (DEBUG) {
-                        Slog.d(TAG, "Dropping event " + event.getEventType());
+                        Slog.d(TAG, "Dropping usage event " + event.getEventType());
                     }
                     break;
             }
@@ -2666,7 +2709,7 @@
         public void handleMessage(Message msg) {
             synchronized (mLock) {
                 switch (msg.what) {
-                    case MSG_REACHED_QUOTA: {
+                    case MSG_REACHED_TIME_QUOTA: {
                         UserPackage pkg = (UserPackage) msg.obj;
                         if (DEBUG) {
                             Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2685,7 +2728,7 @@
                             // This could potentially happen if an old session phases out while a
                             // job is currently running.
                             // Reschedule message
-                            Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
+                            Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
                             timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
                                     pkg.packageName);
                             if (DEBUG) {
@@ -2695,7 +2738,7 @@
                         }
                         break;
                     }
-                    case MSG_REACHED_EJ_QUOTA: {
+                    case MSG_REACHED_EJ_TIME_QUOTA: {
                         UserPackage pkg = (UserPackage) msg.obj;
                         if (DEBUG) {
                             Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2713,7 +2756,7 @@
                             // This could potentially happen if an old session phases out while a
                             // job is currently running.
                             // Reschedule message
-                            Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
+                            Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
                             timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
                                     pkg.userId, pkg.packageName);
                             if (DEBUG) {
@@ -2723,6 +2766,18 @@
                         }
                         break;
                     }
+                    case MSG_REACHED_COUNT_QUOTA: {
+                        UserPackage pkg = (UserPackage) msg.obj;
+                        if (DEBUG) {
+                            Slog.d(TAG, pkg + " has reached its count quota.");
+                        }
+
+                        mStateChangedListener.onControllerStateChanged(
+                                maybeUpdateConstraintForPkgLocked(
+                                        sElapsedRealtimeClock.millis(),
+                                        pkg.userId, pkg.packageName));
+                        break;
+                    }
                     case MSG_CLEAN_UP_SESSIONS:
                         if (DEBUG) {
                             Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/core/api/current.txt b/core/api/current.txt
index c86e4cd..e2d860e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26804,7 +26804,6 @@
     field public static final int STATE_FAST_FORWARDING = 4; // 0x4
     field public static final int STATE_NONE = 0; // 0x0
     field public static final int STATE_PAUSED = 2; // 0x2
-    field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
     field public static final int STATE_PLAYING = 3; // 0x3
     field public static final int STATE_REWINDING = 5; // 0x5
     field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index d681a2c..d1531a1 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -162,12 +162,21 @@
         result.putInt(IP_VERSION_KEY, params.getIpVersion());
         result.putInt(ENCAP_TYPE_KEY, params.getEncapType());
 
-        // TODO: b/185941731 Make sure IkeSessionParamsUtils is automatically updated when a new
-        // IKE_OPTION is defined in IKE module and added in the IkeSessionParams
         final List<Integer> enabledIkeOptions = new ArrayList<>();
-        for (int option : IKE_OPTIONS) {
-            if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
-                enabledIkeOptions.add(option);
+
+        try {
+            // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+            // com.android.ipsec.flags.enabled_ike_options_api flag but that flag is not accessible
+            // right now. We should either update the code when the flag is accessible or remove the
+            // legacy behavior after VIC SDK finalization
+            enabledIkeOptions.addAll(params.getIkeOptions());
+        } catch (Exception e) {
+            // getIkeOptions throws. It means the API is not available
+            enabledIkeOptions.clear();
+            for (int option : IKE_OPTIONS) {
+                if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
+                    enabledIkeOptions.add(option);
+                }
             }
         }
 
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 269839b..e6a84df 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -37,6 +37,7 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutoFillManagerClient;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -641,7 +642,7 @@
 
         @Override
         public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
-                IBinder autofillClientCallback) {
+                IAutoFillManagerClient autofillClientCallback) {
             ICancellationSignal transport = CancellationSignal.createTransport();
             try {
                 callback.onCancellable(transport);
@@ -723,7 +724,7 @@
      */
     public void onFillCredentialRequest(@NonNull FillRequest request,
             @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
-            @NonNull IBinder autofillClientCallback) {}
+            @NonNull IAutoFillManagerClient autofillClientCallback) {}
 
     /**
      * Called by the Android system to convert a credential manager response to a dataset
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 3b64b8a..2c2feae 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -16,13 +16,13 @@
 
 package android.service.autofill;
 
-import android.os.IBinder;
 import android.service.autofill.ConvertCredentialRequest;
 import android.service.autofill.IConvertCredentialCallback;
 import android.service.autofill.FillRequest;
 import android.service.autofill.IFillCallback;
 import android.service.autofill.ISaveCallback;
 import android.service.autofill.SaveRequest;
+import android.view.autofill.IAutoFillManagerClient;
 import com.android.internal.os.IResultReceiver;
 
 /**
@@ -34,7 +34,7 @@
     void onConnectedStateChanged(boolean connected);
     void onFillRequest(in FillRequest request, in IFillCallback callback);
     void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
-        in IBinder client);
+        in IAutoFillManagerClient client);
     void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
     void onSavedPasswordCountRequest(in IResultReceiver receiver);
     void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ac1f646..4c4a22c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13443,6 +13443,15 @@
     }
 
     /**
+     * @return True if the window has the {@link OnContentApplyWindowInsetsListener}, and this means
+     *         the framework will apply window insets on the content of the window.
+     * @hide
+     */
+    protected boolean hasContentOnApplyWindowInsetsListener() {
+        return mAttachInfo != null && mAttachInfo.mContentOnApplyWindowInsetsListener != null;
+    }
+
+    /**
      * Sets whether or not this view should account for system screen decorations
      * such as the status bar and inset its content; that is, controlling whether
      * the default implementation of {@link #fitSystemWindows(Rect)} will be
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index adbf645..0992db9 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -436,7 +436,7 @@
         // overlay.
         mContentInsets.set(mBaseContentInsets);
         mInnerInsets = mBaseInnerInsets;
-        if (!mOverlayMode && !stable) {
+        if (!mOverlayMode && !stable && hasContentOnApplyWindowInsetsListener()) {
             mContentInsets.top += topInset;
             mContentInsets.bottom += bottomInset;
             // Content view has been shrunk, shrink all insets to match.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 773823d..80a7599 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -82,6 +82,14 @@
         "android_util_StringBlock.cpp",
         "android_util_XmlBlock.cpp",
         "android_util_jar_StrictJarFile.cpp",
+        "android_view_InputDevice.cpp",
+        "android_view_KeyCharacterMap.cpp",
+        "android_view_KeyEvent.cpp",
+        "android_view_MotionEvent.cpp",
+        "android_view_Surface.cpp",
+        "android_view_VelocityTracker.cpp",
+        "android_view_VerifiedKeyEvent.cpp",
+        "android_view_VerifiedMotionEvent.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "core_jni_helpers.cpp",
         ":deviceproductinfoconstants_aidl",
@@ -158,16 +166,11 @@
                 "android_view_CompositionSamplingListener.cpp",
                 "android_view_DisplayEventReceiver.cpp",
                 "android_view_InputChannel.cpp",
-                "android_view_InputDevice.cpp",
                 "android_view_InputEventReceiver.cpp",
                 "android_view_InputEventSender.cpp",
                 "android_view_InputQueue.cpp",
-                "android_view_KeyCharacterMap.cpp",
-                "android_view_KeyEvent.cpp",
-                "android_view_MotionEvent.cpp",
                 "android_view_MotionPredictor.cpp",
                 "android_view_PointerIcon.cpp",
-                "android_view_Surface.cpp",
                 "android_view_SurfaceControl.cpp",
                 "android_view_SurfaceControlHdrLayerInfoListener.cpp",
                 "android_view_WindowManagerGlobal.cpp",
@@ -175,9 +178,6 @@
                 "android_view_SurfaceSession.cpp",
                 "android_view_TextureView.cpp",
                 "android_view_TunnelModeEnabledListener.cpp",
-                "android_view_VelocityTracker.cpp",
-                "android_view_VerifiedKeyEvent.cpp",
-                "android_view_VerifiedMotionEvent.cpp",
                 "android_text_Hyphenator.cpp",
                 "android_os_Debug.cpp",
                 "android_os_GraphicsEnvironment.cpp",
@@ -394,7 +394,8 @@
                 "-Wno-unused-function",
             ],
             srcs: [
-                "LayoutlibLoader.cpp",
+                "platform/host/HostRuntime.cpp",
+                "platform/host/native_window_jni.cpp",
             ],
             include_dirs: [
                 "external/vulkan-headers/include",
@@ -414,6 +415,7 @@
                 "libhostgraphics",
                 "libhwui",
                 "libimage_type_recognition",
+                "libinput",
                 "libjpeg",
                 "libpiex",
                 "libpng",
@@ -441,16 +443,9 @@
                 "android_os_MessageQueue.cpp",
                 "android_os_Parcel.cpp",
 
-                "android_view_KeyCharacterMap.cpp",
-                "android_view_KeyEvent.cpp",
                 "android_view_InputChannel.cpp",
-                "android_view_InputDevice.cpp",
                 "android_view_InputEventReceiver.cpp",
                 "android_view_InputEventSender.cpp",
-                "android_view_MotionEvent.cpp",
-                "android_view_VelocityTracker.cpp",
-                "android_view_VerifiedKeyEvent.cpp",
-                "android_view_VerifiedMotionEvent.cpp",
 
                 "android_util_AssetManager.cpp",
                 "android_util_Binder.cpp",
@@ -458,7 +453,6 @@
                 "android_util_FileObserver.cpp",
             ],
             static_libs: [
-                "libinput",
                 "libbinderthreadstateutils",
                 "libsqlite",
                 "libgui_window_info_static",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b801a69..3e3af40 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -28,6 +28,8 @@
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 
+#include <sstream>
+
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
@@ -598,8 +600,8 @@
 
 // ----------------- @CriticalNative ------------------------------
 
-static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
-        jboolean keepHistory) {
+static jlong android_view_MotionEvent_nativeCopy(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+                                                 jlong sourceNativePtr, jboolean keepHistory) {
     MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
     if (!destEvent) {
         destEvent = new MotionEvent();
@@ -609,8 +611,8 @@
     return reinterpret_cast<jlong>(destEvent);
 }
 
-static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
-                                                  jint idBits) {
+static jlong android_view_MotionEvent_nativeSplit(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+                                                  jlong sourceNativePtr, jint idBits) {
     MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
     if (!destEvent) {
         destEvent = new MotionEvent();
@@ -621,168 +623,192 @@
     return reinterpret_cast<jlong>(destEvent);
 }
 
-static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getId();
 }
 
-static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDeviceId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDeviceId();
 }
 
-static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getSource();
 }
 
-static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                     jint source) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setSource(source);
 }
 
-static jint android_view_MotionEvent_nativeGetDisplayId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDisplayId();
 }
 
-static void android_view_MotionEvent_nativeSetDisplayId(jlong nativePtr, jint displayId) {
+static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                        jint displayId) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->setDisplayId(displayId);
 }
 
-static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getAction();
 }
 
-static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+static void android_view_MotionEvent_nativeSetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                     jint action) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setAction(action);
 }
 
-static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+static int android_view_MotionEvent_nativeGetActionButton(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getActionButton();
 }
 
-static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+static void android_view_MotionEvent_nativeSetActionButton(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint button) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setActionButton(button);
 }
 
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->isTouchEvent();
 }
 
-static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getFlags();
 }
 
-static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                    jint flags) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setFlags(flags);
 }
 
-static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getEdgeFlags();
 }
 
-static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+static void android_view_MotionEvent_nativeSetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                        jint edgeFlags) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setEdgeFlags(edgeFlags);
 }
 
-static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetMetaState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getMetaState();
 }
 
-static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetButtonState(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getButtonState();
 }
 
-static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+static void android_view_MotionEvent_nativeSetButtonState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                          jint buttonState) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setButtonState(buttonState);
 }
 
-static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetClassification(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return static_cast<jint>(event->getClassification());
 }
 
-static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
-        jfloat deltaY) {
+static void android_view_MotionEvent_nativeOffsetLocation(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                          jfloat deltaX, jfloat deltaY) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->offsetLocation(deltaX, deltaY);
 }
 
-static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getRawXOffset();
 }
 
-static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getRawYOffset();
 }
 
-static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXPrecision(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getXPrecision();
 }
 
-static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYPrecision(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getYPrecision();
 }
 
-static jfloat android_view_MotionEvent_nativeGetXCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXCursorPosition(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getXCursorPosition();
 }
 
-static jfloat android_view_MotionEvent_nativeGetYCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYCursorPosition(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getYCursorPosition();
 }
 
-static void android_view_MotionEvent_nativeSetCursorPosition(jlong nativePtr, jfloat x, jfloat y) {
+static void android_view_MotionEvent_nativeSetCursorPosition(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat x, jfloat y) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setCursorPosition(x, y);
 }
 
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDownTime();
 }
 
-static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+static void android_view_MotionEvent_nativeSetDownTimeNanos(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong downTimeNanos) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setDownTime(downTimeNanos);
 }
 
-static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetPointerCount(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return jint(event->getPointerCount());
 }
 
-static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+static jint android_view_MotionEvent_nativeFindPointerIndex(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pointerId) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return jint(event->findPointerIndex(pointerId));
 }
 
-static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetHistorySize(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return jint(event->getHistorySize());
 }
 
-static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+static void android_view_MotionEvent_nativeScale(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                 jfloat scale) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->scale(scale);
 }
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 869b53d..ac6298d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,15 +27,19 @@
 #include <android_runtime/android_graphics_SurfaceTexture.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/Log.h>
+#ifdef __ANDROID__
 #include <private/android/AHardwareBufferHelpers.h>
 
 #include "android_os_Parcel.h"
 #include <binder/Parcel.h>
 
 #include <gui/BLASTBufferQueue.h>
+#endif
 #include <gui/Surface.h>
+#ifdef __ANDROID__
 #include <gui/SurfaceControl.h>
 #include <gui/view/Surface.h>
+#endif
 
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
@@ -67,6 +71,7 @@
     jfieldID bottom;
 } gRectClassInfo;
 
+#ifdef __ANDROID__
 class JNamedColorSpace {
 public:
     // ColorSpace.Named.SRGB.ordinal() = 0;
@@ -84,6 +89,7 @@
             return ui::Dataspace::V0_SRGB;
     }
 }
+#endif
 
 // ----------------------------------------------------------------------------
 
@@ -144,6 +150,7 @@
 
 // ----------------------------------------------------------------------------
 
+#ifdef __ANDROID__
 static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
         jobject surfaceTextureObj) {
     sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
@@ -162,6 +169,7 @@
     surface->incStrong(&sRefBaseOwner);
     return jlong(surface.get());
 }
+#endif
 
 static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
     sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
@@ -269,7 +277,7 @@
 }
 
 // ----------------------------------------------------------------------------
-
+#ifdef __ANDROID__
 static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
         jlong surfaceControlNativeObj) {
     sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
@@ -380,6 +388,7 @@
     // to the Parcel
     surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
 }
+#endif
 
 static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
     Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -412,6 +421,7 @@
     return surface->disconnect(-1, IGraphicBufferProducer::DisconnectMode::AllLocal);
 }
 
+#ifdef __ANDROID__
 static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz, jlong nativeObject,
                                                      jobject hardwareBuffer, jint colorSpaceId) {
     Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -422,6 +432,7 @@
             fromNamedColorSpaceValueToDataspace(colorSpaceId));
     return err;
 }
+#endif
 
 static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject,
         jboolean enabled) {
@@ -457,8 +468,10 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gSurfaceMethods[] = {
+#ifdef __ANDROID__
         {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
          (void*)nativeCreateFromSurfaceTexture},
+#endif
         {"nativeRelease", "(J)V", (void*)nativeRelease},
         {"nativeIsValid", "(J)Z", (void*)nativeIsValid},
         {"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
@@ -467,21 +480,27 @@
         {"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
          (void*)nativeUnlockCanvasAndPost},
         {"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+#ifdef __ANDROID__
         {"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
         {"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
         {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
         {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
         {"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
         {"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
         {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
         {"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
         {"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+#ifdef __ANDROID__
         {"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
          (void*)nativeAttachAndQueueBufferWithColorSpace},
+#endif
         {"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
         {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
         {"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
+#ifdef __ANDROID__
         {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+#endif
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
 };
 
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 637b823..23cedb8 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -19,6 +19,7 @@
 
 #include <android/native_window.h>
 #include <ui/PublicFormat.h>
+#include <utils/StrongPointer.h>
 
 #include "jni.h"
 
diff --git a/core/jni/platform/OWNERS b/core/jni/platform/OWNERS
new file mode 100644
index 0000000..10ce5cf
--- /dev/null
+++ b/core/jni/platform/OWNERS
@@ -0,0 +1,4 @@
+include /graphics/java/android/graphics/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/platform/host/HostRuntime.cpp
similarity index 94%
rename from core/jni/LayoutlibLoader.cpp
rename to core/jni/platform/host/HostRuntime.cpp
index 83b6afa..0433855 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -80,7 +80,7 @@
 
 namespace android {
 
-extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
+extern int register_android_animation_PropertyValuesHolder(JNIEnv* env);
 extern int register_android_content_AssetManager(JNIEnv* env);
 extern int register_android_content_StringBlock(JNIEnv* env);
 extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -106,15 +106,17 @@
 extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
-extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv* env);
 
-#define REG_JNI(name)      { name }
+#define REG_JNI(name) \
+    { name }
 struct RegJNIRec {
     int (*mProc)(JNIEnv*);
 };
 
-// Map of all possible class names to register to their corresponding JNI registration function pointer
-// The actual list of registered classes will be determined at runtime via the 'native_classes' System property
+// Map of all possible class names to register to their corresponding JNI registration function
+// pointer The actual list of registered classes will be determined at runtime via the
+// 'native_classes' System property
 static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
         {"android.animation.PropertyValuesHolder",
          REG_JNI(register_android_animation_PropertyValuesHolder)},
@@ -154,8 +156,7 @@
 };
 
 static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
-        const vector<string>& classesToRegister, JNIEnv* env) {
-
+                              const vector<string>& classesToRegister, JNIEnv* env) {
     for (const string& className : classesToRegister) {
         if (jniRegMap.at(className).mProc(env) < 0) {
             return -1;
@@ -169,15 +170,14 @@
     return 0;
 }
 
-int AndroidRuntime::registerNativeMethods(JNIEnv* env,
-        const char* className, const JNINativeMethod* gMethods, int numMethods) {
+int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+                                          const JNINativeMethod* gMethods, int numMethods) {
     return jniRegisterNativeMethods(env, className, gMethods, numMethods);
 }
 
 JNIEnv* AndroidRuntime::getJNIEnv() {
     JNIEnv* env;
-    if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK)
-        return nullptr;
+    if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
     return env;
 }
 
@@ -186,11 +186,10 @@
 }
 
 static vector<string> parseCsv(const string& csvString) {
-    vector<string>   result;
+    vector<string> result;
     istringstream stream(csvString);
     string segment;
-    while(getline(stream, segment, ','))
-    {
+    while (getline(stream, segment, ',')) {
         result.push_back(segment);
     }
     return result;
@@ -274,7 +273,9 @@
     }
 
     struct CloseHandleWrapper {
-        void operator()(HANDLE h) { CloseHandle(h); }
+        void operator()(HANDLE h) {
+            CloseHandle(h);
+        }
     };
     std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
             CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
@@ -384,8 +385,9 @@
     // Configuration is stored as java System properties.
     // Get a reference to System.getProperty
     jclass system = FindClassOrDie(env, "java/lang/System");
-    jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
-                                                         "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+    jmethodID getPropertyMethod =
+            GetStaticMethodIDOrDie(env, system, "getProperty",
+                                   "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
 
     // Java system properties that contain LayoutLib config. The initial values in the map
     // are the default values if the property is not specified.
diff --git a/core/jni/platform/host/native_window_jni.cpp b/core/jni/platform/host/native_window_jni.cpp
new file mode 100644
index 0000000..c17c480
--- /dev/null
+++ b/core/jni/platform/host/native_window_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android_runtime/android_view_Surface.h>
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+using namespace android;
+
+ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
+    sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
+    if (win != NULL) {
+        ANativeWindow_acquire(win.get());
+    }
+    return win.get();
+}
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..822aa22 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -14,185 +14,101 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
     android:width="512dp"
     android:height="512dp"
     android:viewportWidth="512"
     android:viewportHeight="512">
+  <!-- space -->
   <path
-      android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
-    <aapt:attr name="android:fillColor">
-      <gradient 
-          android:startX="256"
-          android:startY="21.81"
-          android:endX="256"
-          android:endY="350.42"
-          android:type="linear">
-        <item android:offset="0" android:color="#FF073042"/>
-        <item android:offset="1" android:color="#FF073042"/>
-      </gradient>
-    </aapt:attr>
-  </path>
+      android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+      android:fillColor="#202124"/>
   <group>
     <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"/>
+    <!-- thrust plume -->
     <path
-        android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
-        android:fillColor="#3ddc84"/>
+        android:pathData="M253,153C249.82,187.48 225.67,262.17 167.98,285.04C110.3,307.92 73.96,318.12 63,320.36L256,399L449,320.36C438.04,318.12 401.7,307.92 344.02,285.04C286.33,262.17 262.18,187.48 259,153H256H253Z"
+        android:fillColor="#C6FF00"
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="M253,153C251.5,187.42 241.7,261.98 214.5,284.82C187.3,307.65 170.17,317.84 165,320.08L256,398.58L347,320.08C341.83,317.84 324.7,307.65 297.5,284.82C270.3,261.98 260.5,187.42 259,153H256H253Z"
+        android:fillColor="#ffffff"
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="M256,153m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
+        android:fillColor="#ffffff"/>
+    <!-- android head and body -->
+    <path
+        android:pathData="M151,350h199v104h-199z"
+        android:fillColor="#5F6368"/>
+    <path
+        android:pathData="M358.42,350.44C358.36,350.02 358.29,349.6 358.22,349.18C357.8,346.6 357.27,344.04 356.65,341.52C355.57,337.12 354.21,332.82 352.59,328.66C351.22,325.13 349.66,321.7 347.93,318.38C345.7,314.11 343.18,310.01 340.41,306.11C337.01,301.34 333.21,296.86 329.06,292.74C327.32,291.01 325.52,289.34 323.65,287.73C319.62,284.26 315.32,281.09 310.78,278.26C310.82,278.19 310.85,278.12 310.89,278.05C312.97,274.46 315.05,270.88 317.13,267.29C319.17,263.78 321.2,260.28 323.23,256.77C324.69,254.26 326.15,251.74 327.61,249.22C327.95,248.62 328.22,248.01 328.43,247.37C329,245.61 329.02,243.76 328.57,242.03C328.45,241.61 328.31,241.19 328.14,240.78C327.97,240.38 327.77,239.98 327.54,239.6C326.76,238.29 325.65,237.16 324.26,236.33C323.02,235.6 321.64,235.16 320.23,235.03C319.64,234.98 319.04,234.99 318.45,235.05C317.96,235.1 317.47,235.19 316.99,235.32C315.26,235.77 313.67,236.72 312.42,238.08C311.98,238.57 311.58,239.12 311.23,239.71C309.77,242.23 308.31,244.75 306.85,247.27L300.76,257.78C298.68,261.37 296.6,264.96 294.52,268.55C294.29,268.94 294.06,269.33 293.83,269.73C293.52,269.6 293.21,269.48 292.89,269.36C281.43,264.99 269,262.6 256.01,262.6C255.65,262.6 255.3,262.6 254.94,262.6C243.39,262.72 232.29,264.73 221.93,268.33C220.73,268.75 219.55,269.19 218.38,269.65C218.16,269.29 217.95,268.92 217.74,268.55C215.66,264.96 213.58,261.38 211.5,257.79C209.47,254.28 207.43,250.78 205.4,247.27C203.94,244.76 202.48,242.23 201.02,239.72C200.68,239.12 200.28,238.58 199.83,238.09C198.59,236.72 196.99,235.78 195.27,235.32C194.79,235.2 194.3,235.1 193.81,235.05C193.22,234.99 192.62,234.99 192.03,235.04C190.61,235.16 189.23,235.6 188,236.34C186.6,237.16 185.5,238.3 184.71,239.6C184.49,239.99 184.29,240.38 184.12,240.79C183.95,241.2 183.8,241.61 183.69,242.04C183.23,243.76 183.26,245.62 183.82,247.38C184.03,248.01 184.3,248.63 184.65,249.23C186.11,251.74 187.57,254.26 189.02,256.78C191.06,260.28 193.09,263.79 195.12,267.29C197.2,270.88 199.28,274.47 201.36,278.06C201.38,278.09 201.4,278.12 201.41,278.15C197.22,280.76 193.23,283.64 189.47,286.8C187.21,288.69 185.04,290.68 182.96,292.75C178.81,296.87 175.01,301.35 171.6,306.12C168.82,310.02 166.31,314.11 164.09,318.39C162.35,321.71 160.79,325.14 159.42,328.67C157.8,332.83 156.44,337.13 155.36,341.53C154.75,344.05 154.22,346.6 153.79,349.19C153.72,349.61 153.66,350.03 153.59,350.45C153.36,351.95 153.16,353.46 153,354.98L359,354.98C358.84,353.46 358.64,351.95 358.41,350.45L358.42,350.44Z"
+        android:fillColor="#5F6368"/>
   </group>
+  <!-- stars -->
   <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
     <path
-        android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M131.04,134.34H127V138.38H131.04V134.34Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M167.04,256H163V260.04H167.04V256Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M373.49,127H369.45V131.04H373.49V127Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M292.04,226H288V230.04H292.04V226Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M319.04,186.91H315V190.95H319.04V186.91Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M355.04,222H351V226.04H355.04V222Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M192.04,136H188V140.04H192.04V136Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M336.08,196H328V204.08H336.08V196Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M222.04,212H218V216.04H222.04V212Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M163.08,175H155V183.08H163.08V175Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M211.08,143H203V151.08H211.08V143Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M369.08,204H361V212.08H369.08V204Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M169.21,204.34H161.13V212.42H169.21V204.34Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+        android:pathData="M383.04,160.07H374.95V168.15H383.04V160.07Z"
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
+        android:pathData="M192.08,183H184V191.08H192.08V183Z"
+        android:fillColor="#ffffff"/>
   </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M378.92,192h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
+  <!-- patch frame -->
   <path
-      android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
-      android:strokeWidth="56.561"
+      android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+      android:strokeWidth="55"
       android:fillColor="#00000000"
-      android:strokeColor="#f86734"/>
+      android:strokeColor="#34A853"/>
+  <!-- text: ANDROID -->
   <path
-      android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
-      android:fillColor="#3ddc84"/>
+      android:pathData="M170.11,92.71C170.97,94.9 171.41,96 171.41,96C171.41,96 170.37,96 168.29,96C166.22,96 165.18,96 165.18,96C165.18,96 164.93,95.27 164.42,93.82L159.71,80.63L158.82,77.75H158.61L157.82,80.63L153.28,93.82C152.82,95.27 152.6,96 152.6,96C152.6,96 151.54,96 149.43,96C147.33,96 146.28,96 146.28,96C146.28,96 146.7,94.89 147.56,92.67L155.21,72.87C155.89,71.07 156.23,70.17 156.23,70.17C156.23,70.17 157.06,70.17 158.72,70.17C160.52,70.17 161.42,70.17 161.42,70.17C161.42,70.17 161.76,71.07 162.46,72.87L170.11,92.71ZM166.04,91.12H158.64V86.91H164.42L166.04,91.12ZM158.64,91.12H151.08L152.63,86.91H158.64V91.12ZM203.85,93.46C203.85,95.15 203.85,96 203.85,96C203.85,96 202.95,96 201.15,96C199.38,96 198.5,96 198.5,96C198.5,96 198.03,95.26 197.08,93.79L188.08,79.75H187.88L188.01,83.45V93.25C188.01,95.08 188.01,96 188.01,96C188.01,96 187.03,96 185.09,96C183.15,96 182.17,96 182.17,96C182.17,96 182.17,95.08 182.17,93.25L182.16,73.9C182.16,71.45 182.16,70.22 182.16,70.22C182.16,70.22 183.19,70.22 185.25,70.22C187.24,70.22 188.24,70.22 188.24,70.22C188.24,70.22 188.7,70.96 189.63,72.42L198.16,85.85H198.36L198.21,82.09V72.89C198.21,71.11 198.21,70.22 198.21,70.22C198.21,70.22 199.15,70.22 201.04,70.22C202.91,70.22 203.85,70.22 203.85,70.22C203.85,70.22 203.85,71.11 203.85,72.89V93.46ZM226.52,96H220.17C218.24,96 217.27,96 217.27,96C217.27,96 217.27,95.02 217.27,93.05V73.19C217.27,71.21 217.27,70.22 217.27,70.22C217.27,70.22 218.24,70.22 220.17,70.22H226.52C230.46,70.22 233.63,71.41 236.03,73.77C238.43,76.12 239.63,79.23 239.63,83.09C239.63,86.98 238.43,90.11 236.03,92.47C233.63,94.82 230.46,96 226.52,96ZM223.17,75.64V90.74H226.18C228.46,90.77 230.27,90.11 231.62,88.78C232.96,87.44 233.63,85.57 233.63,83.17C233.63,80.78 232.96,78.93 231.62,77.62C230.28,76.3 228.47,75.64 226.18,75.64H223.17ZM257.51,93.23C257.51,95.08 257.51,96 257.51,96C257.51,96 256.54,96 254.6,96C252.66,96 251.7,96 251.7,96C251.7,96 251.7,95.09 251.7,93.26V73.19C251.7,71.21 251.7,70.22 251.7,70.22C251.7,70.22 252.66,70.22 254.6,70.22H261.89C264.44,70.22 266.6,70.98 268.35,72.49C270.1,74 270.98,76.03 270.98,78.56C270.98,80.9 270.14,82.83 268.47,84.35C266.81,85.87 264.65,86.62 262.01,86.62H254.02V82.41H261.33C262.4,82.41 263.29,82.08 264.01,81.42C264.73,80.75 265.09,79.88 265.09,78.81C265.09,77.84 264.74,77.03 264.05,76.38C263.35,75.72 262.49,75.39 261.47,75.39H257.51V93.23ZM264.77,93.82L258.66,84.46L264.8,84.25L271.23,93.62C272.37,95.21 272.94,96 272.94,96C272.94,96 271.82,96 269.57,96C267.3,96 266.17,96 266.17,96C266.17,96 265.7,95.27 264.77,93.82ZM296.04,96.58C292.33,96.58 289.16,95.33 286.52,92.85C283.89,90.37 282.58,87.11 282.58,83.09C282.58,79.07 283.9,75.83 286.54,73.36C289.19,70.88 292.36,69.65 296.04,69.65C299.71,69.65 302.87,70.9 305.51,73.41C308.16,75.91 309.49,79.13 309.49,83.09C309.49,87.03 308.17,90.26 305.53,92.8C302.9,95.32 299.74,96.58 296.04,96.58ZM296.04,90.83C298.19,90.83 299.98,90.1 301.41,88.64C302.85,87.17 303.57,85.33 303.57,83.09C303.57,80.84 302.84,78.99 301.39,77.55C299.95,76.11 298.17,75.39 296.04,75.39C293.92,75.39 292.13,76.12 290.68,77.57C289.24,79.01 288.52,80.85 288.52,83.09C288.52,85.35 289.23,87.2 290.66,88.66C292.1,90.11 293.89,90.83 296.04,90.83ZM327.64,93.05C327.64,95.02 327.64,96 327.64,96C327.64,96 326.63,96 324.61,96C322.59,96 321.57,96 321.57,96C321.57,96 321.57,95.02 321.57,93.05V73.18C321.57,71.21 321.57,70.22 321.57,70.22C321.57,70.22 322.58,70.22 324.6,70.22C326.63,70.22 327.64,70.22 327.64,70.22C327.64,70.22 327.64,71.21 327.64,73.18V93.05ZM350.31,96H343.96C342.03,96 341.06,96 341.06,96C341.06,96 341.06,95.02 341.06,93.05V73.19C341.06,71.21 341.06,70.22 341.06,70.22C341.06,70.22 342.03,70.22 343.96,70.22H350.31C354.25,70.22 357.42,71.41 359.82,73.77C362.22,76.12 363.42,79.23 363.42,83.09C363.42,86.98 362.22,90.11 359.82,92.47C357.42,94.82 354.25,96 350.31,96ZM346.96,75.64V90.74H349.97C352.25,90.77 354.06,90.11 355.41,88.78C356.75,87.44 357.42,85.57 357.42,83.17C357.42,80.78 356.75,78.93 355.41,77.62C354.07,76.3 352.26,75.64 349.97,75.64H346.96Z"
+      android:fillColor="#E9F3EB"/>
+  <!-- text: 15 -->
   <path
-      android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
-      android:fillColor="#fff"/>
+      android:pathData="M236.59,363.25C236.59,365.75 236.59,367 236.59,367C236.59,367 235.32,367 232.79,367C230.32,367 229.09,367 229.09,367C229.09,367 229.09,365.75 229.09,363.25V305.85L216.64,314.75C215,315.92 214.19,316.5 214.19,316.5C214.19,316.5 213.54,315.5 212.24,313.5C210.97,311.6 210.34,310.65 210.34,310.65C210.34,310.62 211.2,309.98 212.94,308.75L227.64,298.2C230.3,296.23 231.64,295.25 231.64,295.25C231.64,295.25 232.1,295.25 233.04,295.25C235.4,295.25 236.59,295.25 236.59,295.25C236.59,295.25 236.59,296.47 236.59,298.9V363.25ZM247.09,330L251.19,299C251.52,296.6 251.69,295.4 251.69,295.4C251.69,295.4 252.77,295.4 254.94,295.4H284.54C286.97,295.4 288.19,295.4 288.19,295.4C288.19,295.4 288.19,296.58 288.19,298.95C288.19,301.48 288.19,302.75 288.19,302.75C288.19,302.75 286.97,302.75 284.54,302.75H257.49L254.19,327.45L254.39,327.5C256.09,325.77 258.27,324.38 260.94,323.35C263.61,322.28 266.65,321.75 270.09,321.75C276.55,321.75 281.99,323.97 286.39,328.4C290.79,332.8 292.99,338.32 292.99,344.95C292.99,351.75 290.8,357.4 286.44,361.9C282.11,366.37 276.42,368.6 269.39,368.6C263.09,368.6 257.77,367 253.44,363.8C249.1,360.6 246.26,356.85 244.89,352.55C244.26,350.32 243.94,349.2 243.94,349.2C243.94,349.2 245.09,348.77 247.39,347.9C249.79,347 250.99,346.55 250.99,346.55C250.99,346.55 251.3,347.73 251.94,350.1C252.8,352.73 254.71,355.27 257.64,357.7C260.61,360.13 264.44,361.35 269.14,361.35C274.27,361.35 278.24,359.88 281.04,356.95C283.84,353.98 285.24,350.03 285.24,345.1C285.24,340.37 283.67,336.52 280.54,333.55C277.44,330.58 273.4,329.1 268.44,329.1C265.47,329.1 262.95,329.52 260.89,330.35C258.82,331.15 257.09,332.28 255.69,333.75C254.39,335.25 253.74,336 253.74,336C253.74,336 252.55,335.52 250.19,334.55C247.85,333.62 246.69,333.15 246.69,333.15C246.69,333.15 246.82,332.1 247.09,330Z"
+      android:fillColor="#E9F3EB"/>
+  <!-- spacecraft -->
   <path
-      android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
-      android:fillColor="#fff"/>
-  <path
-      android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
-      android:fillColor="#fff"/>
+      android:pathData="M256.12,121C249.43,121 244,126.27 244,132.77V147.29C244,148.54 245.02,149.56 246.27,149.56C247.53,149.56 248.55,148.55 248.55,147.29V143.38C248.55,140.87 250.58,138.83 253.09,138.83H259.15C261.66,138.83 263.7,140.87 263.7,143.38V147.29C263.7,148.54 264.71,149.56 265.97,149.56C267.23,149.56 268.24,148.55 268.24,147.29V132.77C268.24,126.27 262.82,121 256.12,121H256.12Z"
+      android:fillColor="#E9F3EB"/>
 </vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 53a6836..8ae2a9b 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2023 The Android Open Source Project
+Copyright (C) 2024 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.
@@ -19,36 +19,20 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
+  <group>
+    <clip-path
+        android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"/>
     <path
-        android:name="ring"
-        android:pathData="M 12 21 C 16.971 21 21 16.971 21 12 C 21 7.029 16.971 3 12 3 C 7.029 3 3 7.029 3 12 C 3 16.971 7.029 21 12 21 Z"
-        android:fillColor="#00000000"
-        android:strokeColor="#ffffff"
-        android:strokeWidth="2"/>
-    <group android:name="group">
-        <clip-path
-            android:pathData="M 20.5 12 C 20.5 16.694 16.694 20.5 12 20.5 C 7.306 20.5 3.5 16.694 3.5 12 C 3.5 7.306 7.306 3.5 12 3.5 C 16.694 3.5 20.5 7.306 20.5 12 Z"/>
-        <path
-            android:pathData="M 14.812 9.023 C 13.014 9.707 11.027 9.707 9.229 9.023 L 8.265 10.693 C 8.06 11.048 7.605 11.17 7.25 10.964 C 6.895 10.759 6.773 10.305 6.978 9.949 L 7.899 8.355 C 5.988 7.137 4.702 5.084 4.502 2.695 L 4.456 2.153 L 19.584 2.153 L 19.539 2.695 C 19.339 5.084 18.052 7.137 16.142 8.355 L 17.067 9.958 C 17.259 10.307 17.142 10.746 16.801 10.952 C 16.45 11.165 15.993 11.052 15.781 10.701 L 15.775 10.693 L 14.812 9.023 Z"
-            android:fillColor="#ffffff"/>
-        <group android:name="stars">
-            <path android:pathData="
-                M 7,14  h1v1h-1z
-                M 13,15 h1v1h-1z
-                M 14,11 h1v1h-1z
-
-                M 11,17 h0.5v0.5h-0.5z
-                M 10,15 h0.5v0.5h-0.5z
-                M 13,18 h0.5v0.5h-0.5z
-                M 17,15 h0.5v0.5h-0.5z
-                M 15,14 h0.5v0.5h-0.5z
-                M 18,12 h0.5v0.5h-0.5z
-                M 5,13  h0.5v0.5h-0.5z
-                M 5,10  h0.5v0.5h-0.5z
-                M 9,11  h0.5v0.5h-0.5z
-                M 8,17  h0.5v0.5h-0.5z
-                M 12,12 h0.5v0.5h-0.5z
-            " android:fillColor="#ffffff"/>
-        </group>
-    </group>
+        android:pathData="M5,14.978h14v9.8h-14z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M18.722,15.576C18.717,15.548 18.713,15.521 18.708,15.493C18.68,15.324 18.646,15.156 18.605,14.991C18.534,14.701 18.445,14.42 18.339,14.146C18.249,13.915 18.146,13.69 18.033,13.472C17.886,13.192 17.722,12.923 17.539,12.667C17.316,12.354 17.067,12.06 16.795,11.789C16.68,11.676 16.562,11.566 16.44,11.461C16.175,11.233 15.893,11.025 15.595,10.839C15.598,10.834 15.6,10.83 15.602,10.825C15.739,10.59 15.875,10.355 16.012,10.119C16.145,9.889 16.279,9.659 16.412,9.429C16.508,9.264 16.604,9.098 16.699,8.933C16.722,8.894 16.74,8.854 16.753,8.812C16.791,8.696 16.792,8.575 16.762,8.462C16.754,8.434 16.745,8.406 16.734,8.38C16.723,8.353 16.71,8.327 16.695,8.302C16.644,8.216 16.571,8.142 16.479,8.087C16.399,8.039 16.308,8.011 16.215,8.002C16.176,7.999 16.137,7.999 16.098,8.003C16.066,8.007 16.034,8.013 16.002,8.021C15.889,8.051 15.785,8.113 15.703,8.202C15.674,8.235 15.647,8.27 15.624,8.309C15.529,8.475 15.433,8.64 15.337,8.805L14.937,9.495C14.801,9.731 14.664,9.966 14.528,10.202C14.513,10.227 14.498,10.253 14.483,10.279C14.462,10.271 14.442,10.263 14.421,10.255C13.669,9.968 12.853,9.811 12,9.811C11.977,9.811 11.954,9.811 11.931,9.811C11.172,9.819 10.444,9.951 9.764,10.188C9.686,10.215 9.608,10.244 9.531,10.274C9.517,10.25 9.503,10.226 9.489,10.202C9.353,9.966 9.216,9.731 9.08,9.495C8.946,9.265 8.813,9.035 8.679,8.805C8.584,8.64 8.488,8.475 8.392,8.31C8.37,8.271 8.343,8.235 8.314,8.203C8.232,8.113 8.127,8.051 8.014,8.021C7.983,8.013 7.951,8.007 7.919,8.004C7.88,8 7.841,7.999 7.802,8.003C7.709,8.011 7.618,8.039 7.537,8.088C7.446,8.142 7.373,8.217 7.322,8.302C7.307,8.327 7.294,8.353 7.283,8.38C7.271,8.407 7.262,8.434 7.255,8.462C7.225,8.575 7.226,8.697 7.264,8.812C7.277,8.854 7.295,8.894 7.318,8.934C7.413,9.099 7.509,9.264 7.605,9.429C7.738,9.659 7.872,9.889 8.005,10.119C8.141,10.355 8.278,10.59 8.414,10.826C8.415,10.828 8.417,10.83 8.418,10.832C8.143,11.003 7.881,11.192 7.634,11.4C7.486,11.524 7.343,11.654 7.207,11.79C6.935,12.061 6.685,12.354 6.462,12.668C6.279,12.923 6.114,13.192 5.968,13.472C5.855,13.691 5.752,13.915 5.662,14.147C5.556,14.42 5.467,14.702 5.396,14.991C5.355,15.157 5.321,15.324 5.293,15.494C5.288,15.521 5.284,15.549 5.279,15.576C5.264,15.675 5.251,15.774 5.241,15.874L18.759,15.874C18.749,15.774 18.736,15.675 18.72,15.576L18.722,15.576Z"
+        android:fillColor="#ffffff"/>
+  </group>
+  <path
+      android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
 </vector>
+
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 8d66cfc..1dbb775 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
 import android.view.DisplayCutout;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
@@ -49,6 +50,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@Presubmit
 public class ActionBarOverlayLayoutTest {
 
     private static final Insets TOP_INSET_5 = Insets.of(0, 5, 0, 0);
@@ -167,10 +169,67 @@
         assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
     }
 
+    @Test
+    public void topInset_cutout_noContentOnApplyWindowInsetsListener() {
+        mLayout.setHasContentOnApplyWindowInsetsListener(false);
+        mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+        assertThat(mContentInsetsListener.captured, nullValue());
+
+        mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+        // Action bar height is added to the top inset
+        assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CUTOUT_5)));
+    }
+
+    @Test
+    public void topInset_cutout__hasContentOnApplyWindowInsetsListener() {
+        mLayout.setHasContentOnApplyWindowInsetsListener(true);
+        mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+        assertThat(mContentInsetsListener.captured, nullValue());
+
+        mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+        assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+    }
+
+    @Test
+    public void topInset_noCutout_noContentOnApplyWindowInsetsListener() {
+        mLayout.setHasContentOnApplyWindowInsetsListener(false);
+        mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+        assertThat(mContentInsetsListener.captured, nullValue());
+
+        mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+        // Action bar height is added to the top inset
+        assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, NO_CUTOUT)));
+    }
+
+    @Test
+    public void topInset_noCutout__hasContentOnApplyWindowInsetsListener() {
+        mLayout.setHasContentOnApplyWindowInsetsListener(true);
+        mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+        assertThat(mContentInsetsListener.captured, nullValue());
+
+        mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+        assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+    }
+
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
-        return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
-                false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false,
-                null, null, 0, 0);
+        final Insets cutoutInsets = cutout != null
+                ? Insets.of(cutout.getSafeInsets())
+                : Insets.NONE;
+        return new WindowInsets.Builder()
+                .setSystemWindowInsets(content)
+                .setDisplayCutout(cutout)
+                .setInsets(WindowInsets.Type.displayCutout(), cutoutInsets)
+                .setInsetsIgnoringVisibility(WindowInsets.Type.displayCutout(), cutoutInsets)
+                .setVisible(WindowInsets.Type.displayCutout(), true)
+                .build();
     }
 
     private ViewGroup createViewGroupWithId(int id) {
@@ -181,14 +240,16 @@
 
     static class TestActionBarOverlayLayout extends ActionBarOverlayLayout {
         private boolean mStable;
+        private boolean mHasContentOnApplyWindowInsetsListener;
 
         public TestActionBarOverlayLayout(Context context) {
             super(context);
+            mHasContentOnApplyWindowInsetsListener = true;
         }
 
         @Override
         public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
-            if (mStable) {
+            if (mStable || !hasContentOnApplyWindowInsetsListener()) {
                 // Emulate the effect of makeOptionalFitsSystemWindows, because we can't do that
                 // without being attached to a window.
                 outLocalInsets.setEmpty();
@@ -202,6 +263,15 @@
             setSystemUiVisibility(stable ? SYSTEM_UI_FLAG_LAYOUT_STABLE : 0);
         }
 
+        void setHasContentOnApplyWindowInsetsListener(boolean hasListener) {
+            mHasContentOnApplyWindowInsetsListener = hasListener;
+        }
+
+        @Override
+        protected boolean hasContentOnApplyWindowInsetsListener() {
+            return mHasContentOnApplyWindowInsetsListener;
+        }
+
         @Override
         public int getWindowSystemUiVisibility() {
             return getSystemUiVisibility();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fc4277e..82d2381 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,6 +594,8 @@
         <permission name="android.permission.EMERGENCY_INSTALL_PACKAGES" />
         <!-- Permission required for Cts test - CtsSettingsTestCases -->
         <permission name="android.permission.PREPARE_FACTORY_RESET" />
+        <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+        <permission name="android.permission.SETUP_FSVERITY" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index a454d48..e829d4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -17,8 +17,10 @@
 package com.android.wm.shell.pip2.phone;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
@@ -182,6 +184,10 @@
             mResizeTransition = null;
             return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
         }
+
+        if (isRemovePipTransition(info)) {
+            return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
+        }
         return false;
     }
 
@@ -291,6 +297,10 @@
         startOverlayFadeoutAnimation();
     }
 
+    //
+    // Subroutines setting up and starting transitions' animations.
+    //
+
     private void startOverlayFadeoutAnimation() {
         ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
         animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -326,6 +336,7 @@
         mPipScheduler.setPipTaskToken(mPipTaskToken);
 
         startTransaction.apply();
+        // TODO: b/275910498 Use a new implementation of the PiP animator here.
         finishCallback.onTransitionFinished(null);
         return true;
     }
@@ -353,11 +364,26 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         startTransaction.apply();
+        // TODO: b/275910498 Use a new implementation of the PiP animator here.
         finishCallback.onTransitionFinished(null);
         onExitPip();
         return true;
     }
 
+    private boolean removePipImmediately(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null);
+        onExitPip();
+        return true;
+    }
+
+    //
+    // Utility methods for checking PiP-related transition info and requests.
+    //
+
     @Nullable
     private TransitionInfo.Change getPipChange(TransitionInfo info) {
         for (TransitionInfo.Change change : info.getChanges()) {
@@ -415,6 +441,25 @@
                 && info.getChanges().size() == 1;
     }
 
+    private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
+        if (mPipTaskToken == null) {
+            // PiP removal makes sense if enter-PiP has cached a valid pinned task token.
+            return false;
+        }
+        TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+        if (pipChange == null) {
+            // Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
+            return false;
+        }
+
+        boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK
+                && pipChange.getMode() == TRANSIT_TO_BACK;
+        boolean isPipClosed = info.getType() == TRANSIT_CLOSE
+                && pipChange.getMode() == TRANSIT_CLOSE;
+        // PiP is being removed if the pinned task is either moved to back or closed.
+        return isPipMovedToBack || isPipClosed;
+    }
+
     /**
      * TODO: b/275910498 Use a new implementation of the PiP animator here.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 2bbe530..da1699c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -467,8 +467,8 @@
      * until a resize event calls showResizeVeil below.
      */
     void createResizeVeil() {
-        mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo, mTaskSurface,
-                mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
+        mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconDrawable, mTaskInfo,
+                mTaskSurface, mSurfaceControlTransactionSupplier);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index d072f8c..2c4092a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.ColorRes;
+import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -40,7 +41,7 @@
 import android.window.TaskConstants;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.DisplayController;
 
 import java.util.function.Supplier;
 
@@ -48,6 +49,7 @@
  * Creates and updates a veil that covers task contents on resize.
  */
 public class ResizeVeil {
+    private static final String TAG = "ResizeVeil";
     private static final int RESIZE_ALPHA_DURATION = 100;
 
     private static final int VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL;
@@ -57,8 +59,10 @@
     private static final int VEIL_ICON_LAYER = 1;
 
     private final Context mContext;
-    private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
+    private final DisplayController mDisplayController;
     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
+    private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory;
+    private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
     private final Drawable mAppIcon;
     private ImageView mIconView;
@@ -74,41 +78,82 @@
 
     private final RunningTaskInfo mTaskInfo;
     private SurfaceControlViewHost mViewHost;
-    private final Display mDisplay;
+    private Display mDisplay;
     private ValueAnimator mVeilAnimator;
 
-    public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
+    private boolean mIsShowing = false;
+
+    private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+            new DisplayController.OnDisplaysChangedListener() {
+                @Override
+                public void onDisplayAdded(int displayId) {
+                    if (mTaskInfo.displayId != displayId) {
+                        return;
+                    }
+                    mDisplayController.removeDisplayWindowListener(this);
+                    setupResizeVeil();
+                }
+            };
+
+    public ResizeVeil(Context context,
+            @NonNull DisplayController displayController,
+            Drawable appIcon, RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
-            Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+        this(context,
+                displayController,
+                appIcon,
+                taskInfo,
+                taskSurface,
+                surfaceControlTransactionSupplier,
+                new SurfaceControlBuilderFactory() {},
+                new WindowDecoration.SurfaceControlViewHostFactory() {});
+    }
+
+    public ResizeVeil(Context context,
+            @NonNull DisplayController displayController,
+            Drawable appIcon, RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+            SurfaceControlBuilderFactory surfaceControlBuilderFactory,
+            WindowDecoration.SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
         mContext = context;
+        mDisplayController = displayController;
         mAppIcon = appIcon;
-        mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mTaskInfo = taskInfo;
         mParentSurface = taskSurface;
-        mDisplay = display;
+        mSurfaceControlBuilderFactory = surfaceControlBuilderFactory;
+        mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
         setupResizeVeil();
     }
-
     /**
      * Create the veil in its default invisible state.
      */
     private void setupResizeVeil() {
-        mVeilSurface = mSurfaceControlBuilderSupplier.get()
+        if (!obtainDisplayOrRegisterListener()) {
+            // Display may not be available yet, skip this until then.
+            return;
+        }
+        mVeilSurface = mSurfaceControlBuilderFactory
+                .create("Resize veil of Task=" + mTaskInfo.taskId)
                 .setContainerLayer()
-                .setName("Resize veil of Task=" + mTaskInfo.taskId)
                 .setHidden(true)
                 .setParent(mParentSurface)
                 .setCallsite("ResizeVeil#setupResizeVeil")
                 .build();
-        mBackgroundSurface = SurfaceUtils.makeColorLayer(mVeilSurface,
-                "Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession);
-        mIconSurface = mSurfaceControlBuilderSupplier.get()
-                .setName("Resize veil icon of Task= " + mTaskInfo.taskId)
-                .setContainerLayer()
-                .setParent(mVeilSurface)
+        mBackgroundSurface = mSurfaceControlBuilderFactory
+                .create("Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession)
+                .setColorLayer()
                 .setHidden(true)
+                .setParent(mVeilSurface)
+                .setCallsite("ResizeVeil#setupResizeVeil")
+                .build();
+        mIconSurface = mSurfaceControlBuilderFactory
+                .create("Resize veil icon of Task=" + mTaskInfo.taskId)
+                .setContainerLayer()
+                .setHidden(true)
+                .setParent(mVeilSurface)
                 .setCallsite("ResizeVeil#setupResizeVeil")
                 .build();
 
@@ -131,10 +176,20 @@
 
         final WindowlessWindowManager wwm = new WindowlessWindowManager(mTaskInfo.configuration,
                 mIconSurface, null /* hostInputToken */);
-        mViewHost = new SurfaceControlViewHost(mContext, mDisplay, wwm, "ResizeVeil");
+
+        mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil");
         mViewHost.setView(root, lp);
     }
 
+    private boolean obtainDisplayOrRegisterListener() {
+        mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+        if (mDisplay == null) {
+            mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Shows the veil surface/view.
      *
@@ -146,6 +201,12 @@
      */
     public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
             Rect taskBounds, boolean fadeIn) {
+        if (!isReady() || isVisible()) {
+            t.apply();
+            return;
+        }
+        mIsShowing = true;
+
         // Parent surface can change, ensure it is up to date.
         if (!parentSurface.equals(mParentSurface)) {
             t.reparent(mVeilSurface, parentSurface);
@@ -226,6 +287,9 @@
      * Animate veil's alpha to 1, fading it in.
      */
     public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+        if (!isReady() || isVisible()) {
+            return;
+        }
         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
     }
@@ -247,6 +311,9 @@
      * @param newBounds bounds to update veil to.
      */
     public void updateResizeVeil(Rect newBounds) {
+        if (!isVisible()) {
+            return;
+        }
         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         updateResizeVeil(t, newBounds);
     }
@@ -260,6 +327,10 @@
      * @param newBounds bounds to update veil to.
      */
     public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+        if (!isVisible()) {
+            t.apply();
+            return;
+        }
         if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
             mVeilAnimator.removeAllUpdateListeners();
             mVeilAnimator.end();
@@ -272,6 +343,9 @@
      * Animate veil's alpha to 0, fading it out.
      */
     public void hideVeil() {
+        if (!isVisible()) {
+            return;
+        }
         cancelAnimation();
         mVeilAnimator = new ValueAnimator();
         mVeilAnimator.setFloatValues(1, 0);
@@ -292,6 +366,7 @@
             }
         });
         mVeilAnimator.start();
+        mIsShowing = false;
     }
 
     @ColorRes
@@ -318,10 +393,26 @@
     }
 
     /**
+     * Whether the resize veil is currently visible.
+     *
+     * Note: when animating a {@link ResizeVeil#hideVeil()}, the veil is considered visible as soon
+     * as the animation starts.
+     */
+    private boolean isVisible() {
+        return mIsShowing;
+    }
+
+    /** Whether the resize veil is ready to be shown. */
+    private boolean isReady() {
+        return mViewHost != null;
+    }
+
+    /**
      * Dispose of veil when it is no longer needed, likely on close of its container decor.
      */
     void dispose() {
         cancelAnimation();
+        mIsShowing = false;
         mVeilAnimator = null;
 
         if (mViewHost != null) {
@@ -342,5 +433,16 @@
             mVeilSurface = null;
         }
         t.apply();
+        mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+    }
+
+    interface SurfaceControlBuilderFactory {
+        default SurfaceControl.Builder create(@NonNull String name) {
+            return new SurfaceControl.Builder().setName(name);
+        }
+        default SurfaceControl.Builder create(@NonNull String name,
+                @NonNull SurfaceSession surfaceSession) {
+            return new SurfaceControl.Builder(surfaceSession).setName(name);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 51b0a24..36da1ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -668,6 +668,10 @@
         default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
             return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
         }
+        default SurfaceControlViewHost create(Context c, Display d,
+                WindowlessWindowManager wmm, String callsite) {
+            return new SurfaceControlViewHost(c, d, wmm, callsite);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
new file mode 100644
index 0000000..847c2dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+
+/**
+ * Tests for [ResizeVeil].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ResizeVeilTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ResizeVeilTest : ShellTestCase() {
+
+    @Mock
+    private lateinit var mockDisplayController: DisplayController
+    @Mock
+    private lateinit var mockAppIcon: Drawable
+    @Mock
+    private lateinit var mockDisplay: Display
+    @Mock
+    private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost
+    @Mock
+    private lateinit var mockSurfaceControlBuilderFactory: ResizeVeil.SurfaceControlBuilderFactory
+    @Mock
+    private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory
+    @Spy
+    private val spyResizeVeilSurfaceBuilder = SurfaceControl.Builder()
+    @Mock
+    private lateinit var mockResizeVeilSurface: SurfaceControl
+    @Spy
+    private val spyBackgroundSurfaceBuilder = SurfaceControl.Builder()
+    @Mock
+    private lateinit var mockBackgroundSurface: SurfaceControl
+    @Spy
+    private val spyIconSurfaceBuilder = SurfaceControl.Builder()
+    @Mock
+    private lateinit var mockIconSurface: SurfaceControl
+    @Mock
+    private lateinit var mockTransaction: SurfaceControl.Transaction
+
+    private val taskInfo = TestRunningTaskInfoBuilder().build()
+
+    @Before
+    fun setUp() {
+        whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any(), any()))
+                .thenReturn(mockSurfaceControlViewHost)
+        whenever(mockSurfaceControlBuilderFactory
+            .create("Resize veil of Task=" + taskInfo.taskId))
+            .thenReturn(spyResizeVeilSurfaceBuilder)
+        doReturn(mockResizeVeilSurface).whenever(spyResizeVeilSurfaceBuilder).build()
+        whenever(mockSurfaceControlBuilderFactory
+            .create(eq("Resize veil background of Task=" + taskInfo.taskId), any()))
+            .thenReturn(spyBackgroundSurfaceBuilder)
+        doReturn(mockBackgroundSurface).whenever(spyBackgroundSurfaceBuilder).build()
+        whenever(mockSurfaceControlBuilderFactory
+            .create("Resize veil icon of Task=" + taskInfo.taskId))
+            .thenReturn(spyIconSurfaceBuilder)
+        doReturn(mockIconSurface).whenever(spyIconSurfaceBuilder).build()
+    }
+
+    @Test
+    fun init_displayAvailable_viewHostCreated() {
+        createResizeVeil(withDisplayAvailable = true)
+
+        verify(mockSurfaceControlViewHostFactory)
+            .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+    }
+
+    @Test
+    fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() {
+        createResizeVeil(withDisplayAvailable = false)
+
+        verify(mockSurfaceControlViewHostFactory, never())
+            .create(any(), eq(mockDisplay), any<WindowlessWindowManager>(), eq("ResizeVeil"))
+        val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+        verify(mockDisplayController).addDisplayWindowListener(captor.capture())
+
+        whenever(mockDisplayController.getDisplay(taskInfo.displayId)).thenReturn(mockDisplay)
+        captor.value.onDisplayAdded(taskInfo.displayId)
+
+        verify(mockSurfaceControlViewHostFactory)
+            .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+        verify(mockDisplayController).removeDisplayWindowListener(any())
+    }
+
+    @Test
+    fun dispose_removesDisplayWindowListener() {
+        createResizeVeil().dispose()
+
+        verify(mockDisplayController).removeDisplayWindowListener(any())
+    }
+
+    @Test
+    fun showVeil() {
+        val veil = createResizeVeil()
+        val tx = mock<SurfaceControl.Transaction>()
+
+        veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+        verify(tx).show(mockResizeVeilSurface)
+        verify(tx).show(mockBackgroundSurface)
+        verify(tx).show(mockIconSurface)
+        verify(tx).apply()
+    }
+
+    @Test
+    fun showVeil_displayUnavailable_doesNotShow() {
+        val veil = createResizeVeil(withDisplayAvailable = false)
+        val tx = mock<SurfaceControl.Transaction>()
+
+        veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+        verify(tx, never()).show(mockResizeVeilSurface)
+        verify(tx, never()).show(mockBackgroundSurface)
+        verify(tx, never()).show(mockIconSurface)
+        verify(tx).apply()
+    }
+
+    @Test
+    fun showVeil_alreadyVisible_doesNotShowAgain() {
+        val veil = createResizeVeil()
+        val tx = mock<SurfaceControl.Transaction>()
+
+        veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+        veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+        verify(tx, times(1)).show(mockResizeVeilSurface)
+        verify(tx, times(1)).show(mockBackgroundSurface)
+        verify(tx, times(1)).show(mockIconSurface)
+        verify(tx, times(2)).apply()
+    }
+
+    @Test
+    fun showVeil_reparentsVeilToNewParent() {
+        val veil = createResizeVeil(parent = mock())
+        val tx = mock<SurfaceControl.Transaction>()
+
+        val newParent = mock<SurfaceControl>()
+        veil.showVeil(tx, newParent, Rect(0, 0, 100, 100), false /* fadeIn */)
+
+        verify(tx).reparent(mockResizeVeilSurface, newParent)
+    }
+
+    @Test
+    fun hideVeil_alreadyHidden_doesNothing() {
+        val veil = createResizeVeil()
+
+        veil.hideVeil()
+
+        verifyZeroInteractions(mockTransaction)
+    }
+
+    private fun createResizeVeil(
+        withDisplayAvailable: Boolean = true,
+        parent: SurfaceControl = mock()
+    ): ResizeVeil {
+        whenever(mockDisplayController.getDisplay(taskInfo.displayId))
+            .thenReturn(if (withDisplayAvailable) mockDisplay else null)
+        return ResizeVeil(
+            context,
+            mockDisplayController,
+            mockAppIcon,
+            taskInfo,
+            parent,
+            { mockTransaction },
+            mockSurfaceControlBuilderFactory,
+            mockSurfaceControlViewHostFactory
+        )
+    }
+}
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index 2573931..36d8fba 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -52,6 +52,8 @@
 
     virtual void destroy() {}
 
+    int getBuffersDataSpace() { return 0; }
+
 protected:
     virtual ~Surface() {}
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7439fbc..753a699 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_android_core_graphics_stack",
     default_applicable_licenses: ["frameworks_base_libs_hwui_license"],
 }
 
@@ -93,6 +94,7 @@
         host: {
             include_dirs: [
                 "external/vulkan-headers/include",
+                "frameworks/av/media/ndk/include",
             ],
             cflags: [
                 "-Wno-unused-variable",
@@ -142,7 +144,6 @@
                 "libsync",
                 "libui",
                 "aconfig_text_flags_c_lib",
-                "server_configurable_flags",
             ],
             static_libs: [
                 "libEGL_blobCache",
@@ -267,6 +268,7 @@
     cppflags: ["-Wno-conversion-null"],
 
     srcs: [
+        "apex/android_canvas.cpp",
         "apex/android_matrix.cpp",
         "apex/android_paint.cpp",
         "apex/android_region.cpp",
@@ -279,7 +281,6 @@
         android: {
             srcs: [ // sources that depend on android only libraries
                 "apex/android_bitmap.cpp",
-                "apex/android_canvas.cpp",
                 "apex/jni_runtime.cpp",
             ],
         },
@@ -338,6 +339,8 @@
         "jni/android_graphics_ColorSpace.cpp",
         "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
         "jni/android_graphics_drawable_VectorDrawable.cpp",
+        "jni/android_graphics_HardwareRenderer.cpp",
+        "jni/android_graphics_HardwareBufferRenderer.cpp",
         "jni/android_graphics_HardwareRendererObserver.cpp",
         "jni/android_graphics_Matrix.cpp",
         "jni/android_graphics_Picture.cpp",
@@ -422,8 +425,6 @@
         android: {
             srcs: [ // sources that depend on android only libraries
                 "jni/android_graphics_TextureLayer.cpp",
-                "jni/android_graphics_HardwareRenderer.cpp",
-                "jni/android_graphics_HardwareBufferRenderer.cpp",
                 "jni/GIFMovie.cpp",
                 "jni/GraphicsStatsService.cpp",
                 "jni/Movie.cpp",
@@ -448,6 +449,12 @@
                 "libstatssocket_lazy",
             ],
         },
+        linux: {
+            srcs: ["platform/linux/utils/SharedLib.cpp"],
+        },
+        darwin: {
+            srcs: ["platform/darwin/utils/SharedLib.cpp"],
+        },
         host: {
             cflags: [
                 "-Wno-unused-const-variable",
@@ -543,6 +550,7 @@
         "renderthread/CanvasContext.cpp",
         "renderthread/DrawFrameTask.cpp",
         "renderthread/Frame.cpp",
+        "renderthread/RenderEffectCapabilityQuery.cpp",
         "renderthread/RenderProxy.cpp",
         "renderthread/RenderTask.cpp",
         "renderthread/TimeLord.cpp",
@@ -576,6 +584,7 @@
         "HWUIProperties.sysprop",
         "Interpolator.cpp",
         "JankTracker.cpp",
+        "Layer.cpp",
         "LayerUpdateQueue.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
@@ -624,7 +633,6 @@
                 "renderthread/CacheManager.cpp",
                 "renderthread/EglManager.cpp",
                 "renderthread/ReliableSurface.cpp",
-                "renderthread/RenderEffectCapabilityQuery.cpp",
                 "renderthread/VulkanManager.cpp",
                 "renderthread/VulkanSurface.cpp",
                 "renderthread/RenderThread.cpp",
@@ -635,7 +643,6 @@
                 "AutoBackendTextureRelease.cpp",
                 "DeferredLayerUpdater.cpp",
                 "HardwareBitmapUploader.cpp",
-                "Layer.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
                 "WebViewFunctorManager.cpp",
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
index 7e3f771..d3b48d3 100644
--- a/libs/hwui/jni/HardwareBufferHelpers.cpp
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -16,7 +16,9 @@
 
 #include "HardwareBufferHelpers.h"
 
+#ifdef __ANDROID__
 #include <dlfcn.h>
+#endif
 #include <log/log.h>
 
 #ifdef __ANDROID__
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d9e2c8c..df9f830 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -25,13 +25,16 @@
 #include <SkColorSpace.h>
 #include <SkData.h>
 #include <SkImage.h>
+#ifdef __ANDROID__
 #include <SkImageAndroid.h>
+#else
+#include <SkImagePriv.h>
+#endif
 #include <SkPicture.h>
 #include <SkPixmap.h>
 #include <SkSerialProcs.h>
 #include <SkStream.h>
 #include <SkTypeface.h>
-#include <dlfcn.h>
 #include <gui/TraceUtils.h>
 #include <include/encode/SkPngEncoder.h>
 #include <inttypes.h>
@@ -39,8 +42,10 @@
 #include <media/NdkImage.h>
 #include <media/NdkImageReader.h>
 #include <nativehelper/JNIPlatformHelp.h>
+#ifdef __ANDROID__
 #include <pipeline/skia/ShaderCache.h>
 #include <private/EGL/cache.h>
+#endif
 #include <renderthread/CanvasContext.h>
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
@@ -59,6 +64,7 @@
 #include "JvmErrorReporter.h"
 #include "android_graphics_HardwareRendererObserver.h"
 #include "utils/ForceDark.h"
+#include "utils/SharedLib.h"
 
 namespace android {
 
@@ -498,7 +504,11 @@
                 return sk_ref_sp(img);
             }
             bm.setImmutable();
+#ifdef __ANDROID__
             return SkImages::PinnableRasterFromBitmap(bm);
+#else
+            return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+#endif
         }
         return sk_ref_sp(img);
     }
@@ -713,6 +723,7 @@
 
 static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+#ifdef __ANDROID__
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     if (jwidth <= 0 || jheight <= 0) {
         ALOGW("Invalid width %d or height %d", jwidth, jheight);
@@ -796,6 +807,9 @@
     sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
     return bitmap::createBitmap(env, bitmap.release(),
             android::bitmap::kBitmapCreateFlag_Premultiplied);
+#else
+    return nullptr;
+#endif
 }
 
 static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
@@ -909,6 +923,7 @@
 
 static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
         jstring diskCachePath, jstring skiaDiskCachePath) {
+#ifdef __ANDROID__
     const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
     android::egl_set_cache_filename(cacheArray);
     env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -916,6 +931,7 @@
     const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
     uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
     env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
+#endif
 }
 
 static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) {
@@ -1092,8 +1108,12 @@
     gCopyRequest.getDestinationBitmap =
             GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
 
-    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
-    fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+#ifdef __ANDROID__
+    void* handle_ = SharedLib::openSharedLib("libandroid");
+#else
+    void* handle_ = SharedLib::openSharedLib("libandroid_runtime");
+#endif
+    fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface");
     LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
                         "Failed to find required symbol ANativeWindow_fromSurface!");
 
diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp
new file mode 100644
index 0000000..6e9f0b4
--- /dev/null
+++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+    return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+    return dlsym(library, symbol);
+}
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp
new file mode 100644
index 0000000..a9acf37
--- /dev/null
+++ b/libs/hwui/platform/linux/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+    return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+    return dlsym(library, symbol);
+}
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h
new file mode 100644
index 0000000..f4dcf0f
--- /dev/null
+++ b/libs/hwui/utils/SharedLib.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef SHAREDLIB_H
+#define SHAREDLIB_H
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+class SharedLib {
+public:
+    static void* openSharedLib(std::string filename);
+    static void* getSymbol(void* library, const char* symbol);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif  // SHAREDLIB_H
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 212e390..988b5cf 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -332,7 +332,8 @@
 
 <pre class=prettyprint>
 &lt;service android:name="<strong>MySynthDeviceService</strong>"
-  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+  android:exported="true">
   &lt;intent-filter>
     &lt;action android:name="android.media.midi.MidiDeviceService" />
   &lt;/intent-filter>
@@ -474,7 +475,8 @@
 
 <pre class=prettyprint>
 &lt;service android:name="<strong>MidiEchoDeviceService</strong>"
-  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+  android:exported="true">
   &lt;intent-filter>
     &lt;action android:name="android.media.midi.MidiUmpDeviceService" />
   &lt;/intent-filter>
@@ -509,6 +511,11 @@
 import android.media.midi.MidiReceiver;
 import android.media.midi.MidiUmpDeviceService;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 public class MidiEchoDeviceService extends MidiUmpDeviceService {
     private static final String TAG = "MidiEchoDeviceService";
     // Other apps will write to this port.
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 47637b8..4bacbf9 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,10 +15,7 @@
  */
 package android.media.session;
 
-import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
-
 import android.annotation.DrawableRes;
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.Nullable;
@@ -298,8 +295,9 @@
      * foreground.
      *
      * @see Builder#setState
+     * @hide
      */
-    @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+    // TODO: b/335561702 Unhide this symbol for the next API bump.
     public static final int STATE_PLAYBACK_SUPPRESSED = 12;
 
     /**
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 97dfba1..8fe771c 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -41,7 +41,7 @@
         android:supportsRtl="true">
 
         <activity
-            android:name=".CompanionDeviceActivity"
+            android:name=".CompanionAssociationActivity"
             android:exported="true"
             android:launchMode="singleInstance"
             android:excludeFromRecents="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
similarity index 88%
rename from packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
rename to packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 1231b63..bf81d3f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -65,7 +65,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.text.Spanned;
-import android.util.Log;
+import android.util.Slog;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -91,9 +91,8 @@
  *  nearby devices to be associated with.
  */
 @SuppressLint("LongLogTag")
-public class CompanionDeviceActivity extends FragmentActivity implements
+public class CompanionAssociationActivity extends FragmentActivity implements
         CompanionVendorHelperDialogFragment.CompanionVendorHelperDialogListener {
-    private static final boolean DEBUG = false;
     private static final String TAG = "CDM_CompanionDeviceActivity";
 
     // Keep the following constants in sync with
@@ -183,11 +182,11 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        if (DEBUG) Log.d(TAG, "onCreate()");
-        boolean forceCancelDialog = getIntent().getBooleanExtra("cancel_confirmation", false);
+        boolean forceCancelDialog = getIntent().getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION,
+                false);
         // Must handle the force cancel request in onNewIntent.
         if (forceCancelDialog) {
-            Log.i(TAG, "The confirmation does not exist, skipping the cancel request");
+            Slog.i(TAG, "The confirmation does not exist, skipping the cancel request");
             finish();
         }
 
@@ -198,13 +197,13 @@
     @Override
     protected void onStart() {
         super.onStart();
-        if (DEBUG) Log.d(TAG, "onStart()");
 
         final Intent intent = getIntent();
-        mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+        mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST, AssociationRequest.class);
         mAppCallback = IAssociationRequestCallback.Stub.asInterface(
                 intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
-        mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
+        mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER,
+                ResultReceiver.class);
 
         requireNonNull(mRequest);
         requireNonNull(mAppCallback);
@@ -221,29 +220,22 @@
         initUI();
     }
 
-    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
-    protected void onNewIntent(Intent intent) {
+    protected void onNewIntent(@NonNull Intent intent) {
+        super.onNewIntent(intent);
+
         // Force cancels the CDM dialog if this activity receives another intent with
         // EXTRA_FORCE_CANCEL_CONFIRMATION.
         boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
-
         if (forCancelDialog) {
-            Log.i(TAG, "Cancelling the user confirmation");
-
-            cancel(/* discoveryTimeOut */ false,
-                    /* userRejected */ false, /* internalError */ false);
+            Slog.i(TAG, "Cancelling the user confirmation");
+            cancel(/* discoveryTimeOut */ false, /* userRejected */ false,
+                    /* internalError */ false);
             return;
         }
 
         // Handle another incoming request (while we are not done with the original - mRequest -
-        // yet).
-        final AssociationRequest request = requireNonNull(
-                intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST));
-
-        if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request);
-
-        // We can only "process" one request at a time.
+        // yet). We can only "process" one request at a time.
         final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub
                 .asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
         try {
@@ -255,7 +247,6 @@
     @Override
     protected void onStop() {
         super.onStop();
-        if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
 
         // TODO: handle config changes without cancelling.
         if (!isDone()) {
@@ -264,26 +255,8 @@
         }
     }
 
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (DEBUG) Log.d(TAG, "onDestroy()");
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (DEBUG) Log.d(TAG, "onBackPressed()");
-        super.onBackPressed();
-    }
-
-    @Override
-    public void finish() {
-        if (DEBUG) Log.d(TAG, "finish()", new Exception("Stack Trace Dump"));
-        super.finish();
-    }
-
     private void initUI() {
-        if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
+        Slog.d(TAG, "initUI(), request=" + mRequest);
 
         final String packageName = mRequest.getPackageName();
         final int userId = mRequest.getUserId();
@@ -292,7 +265,7 @@
         try {
             appLabel = getApplicationLabel(this, packageName, userId);
         } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
+            Slog.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
 
             CompanionDeviceDiscoveryService.stop(this);
             setResultAndFinish(null, RESULT_INTERNAL_ERROR);
@@ -341,9 +314,9 @@
         if (mRequest.isSelfManaged()) {
             initUiForSelfManagedAssociation();
         } else if (mRequest.isSingleDevice()) {
-            initUiForSingleDevice(appLabel);
+            initUiForSingleDevice();
         } else {
-            initUiForMultipleDevices(appLabel);
+            initUiForMultipleDevices();
         }
     }
 
@@ -364,12 +337,12 @@
 
     private void onAssociationApproved(@Nullable MacAddress macAddress) {
         if (isDone()) {
-            if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+            Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
             return;
         }
         mApproved = true;
 
-        if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
+        Slog.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
 
         if (!mRequest.isSelfManaged()) {
             requireNonNull(macAddress);
@@ -390,17 +363,8 @@
     }
 
     private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) {
-        if (DEBUG) {
-            Log.i(TAG, "cancel(), discoveryTimeout="
-                    + discoveryTimeout
-                    + ", userRejected="
-                    + userRejected
-                    + ", internalError="
-                    + internalError, new Exception("Stack Trace Dump"));
-        }
-
         if (isDone()) {
-            if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+            Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
             return;
         }
         mCancelled = true;
@@ -428,6 +392,7 @@
 
         // First send callback to the app directly...
         try {
+            Slog.i(TAG, "Sending onFailure to app due to reason=" + cancelReason);
             mAppCallback.onFailure(cancelReason);
         } catch (RemoteException ignore) {
         }
@@ -437,7 +402,7 @@
     }
 
     private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
-        Log.i(TAG, "setResultAndFinish(), association="
+        Slog.i(TAG, "setResultAndFinish(), association="
                 + (association == null ? "null" : association)
                 + "resultCode=" + resultCode);
 
@@ -454,7 +419,7 @@
     }
 
     private void initUiForSelfManagedAssociation() {
-        if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
+        Slog.d(TAG, "initUiForSelfManagedAssociation()");
 
         final CharSequence deviceName = mRequest.getDisplayName();
         final String deviceProfile = mRequest.getDeviceProfile();
@@ -477,7 +442,7 @@
                 mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null));
             }
         } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
+            Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
             cancel(/* discoveryTimeout */ false,
                     /* userRejected */ false, /* internalError */ true);
             return;
@@ -506,8 +471,8 @@
         mBorderBottom.setVisibility(View.GONE);
     }
 
-    private void initUiForSingleDevice(CharSequence appLabel) {
-        if (DEBUG) Log.i(TAG, "initUiFor_SingleDevice()");
+    private void initUiForSingleDevice() {
+        Slog.d(TAG, "initUiForSingleDevice()");
 
         final String deviceProfile = mRequest.getDeviceProfile();
 
@@ -515,9 +480,16 @@
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        CompanionDeviceDiscoveryService.getScanResult().observe(this,
-                deviceFilterPairs -> updateSingleDeviceUi(
-                        deviceFilterPairs, deviceProfile, appLabel));
+        final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
+        mProfileIcon.setImageDrawable(profileIcon);
+
+        CompanionDeviceDiscoveryService.getScanResult().observe(this, deviceFilterPairs -> {
+            if (deviceFilterPairs.isEmpty()) {
+                return;
+            }
+            mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
+            updateSingleDeviceUi();
+        });
 
         mSingleDeviceSpinner.setVisibility(View.VISIBLE);
         // Hide permission list and confirmation dialog first before the
@@ -527,33 +499,8 @@
         mAssociationConfirmationDialog.setVisibility(View.GONE);
     }
 
-    private void updateSingleDeviceUi(List<DeviceFilterPair<?>> deviceFilterPairs,
-            String deviceProfile, CharSequence appLabel) {
-        // Ignore "empty" scan reports.
-        if (deviceFilterPairs.isEmpty()) return;
-
-        mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
-
-        final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
-
-        // No need to show permission consent dialog if it is a isSkipPrompt(true)
-        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
-        if (mRequest.isSkipPrompt()) {
-            Log.d(TAG, "Skipping the permission consent dialog.");
-            mSingleDeviceSpinner.setVisibility(View.GONE);
-            onUserSelectedDevice(mSelectedDevice);
-            return;
-        }
-
-        updatePermissionUi();
-
-        mProfileIcon.setImageDrawable(profileIcon);
-        mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
-        mSingleDeviceSpinner.setVisibility(View.GONE);
-    }
-
-    private void initUiForMultipleDevices(CharSequence appLabel) {
-        if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
+    private void initUiForMultipleDevices() {
+        Slog.d(TAG, "initUiForMultipleDevices()");
 
         final Drawable profileIcon;
         final Spanned title;
@@ -566,7 +513,7 @@
         profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
 
         if (deviceProfile == null) {
-            title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
+            title = getHtmlFromResources(this, R.string.chooser_title_non_profile, mAppLabel);
             mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
         } else {
             title = getHtmlFromResources(this,
@@ -606,7 +553,7 @@
         final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position);
         // To prevent double tap on the selected device.
         if (mSelectedDevice != null) {
-            if (DEBUG) Log.w(TAG, "Already selected.");
+            Slog.w(TAG, "Already selected.");
             return;
         }
         // Notify the adapter to highlight the selected item.
@@ -614,17 +561,9 @@
 
         mSelectedDevice = requireNonNull(selectedDevice);
 
-        Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
+        Slog.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
 
-        // No need to show permission consent dialog if it is a isSkipPrompt(true)
-        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
-        if (mRequest.isSkipPrompt()) {
-            Log.d(TAG, "Skipping the permission consent dialog.");
-            onUserSelectedDevice(mSelectedDevice);
-            return;
-        }
-
-        updatePermissionUi();
+        updateSingleDeviceUi();
 
         mSummary.setVisibility(View.VISIBLE);
         mButtonAllow.setVisibility(View.VISIBLE);
@@ -633,7 +572,18 @@
         mNotAllowMultipleDevicesLayout.setVisibility(View.GONE);
     }
 
-    private void updatePermissionUi() {
+    private void updateSingleDeviceUi() {
+        // No need to show permission consent dialog if it is a isSkipPrompt(true)
+        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+        if (mRequest.isSkipPrompt()) {
+            Slog.d(TAG, "Skipping the permission consent dialog.");
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        }
+
+        mSingleDeviceSpinner.setVisibility(View.GONE);
+        mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
+
         final String deviceProfile = mRequest.getDeviceProfile();
         final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
         final String remoteDeviceName = mSelectedDevice.getDisplayName();
@@ -658,7 +608,7 @@
     }
 
     private void onPositiveButtonClick(View v) {
-        if (DEBUG) Log.d(TAG, "on_Positive_ButtonClick()");
+        Slog.d(TAG, "onPositiveButtonClick()");
 
         // Disable the button, to prevent more clicks.
         v.setEnabled(false);
@@ -671,7 +621,7 @@
     }
 
     private void onNegativeButtonClick(View v) {
-        if (DEBUG) Log.d(TAG, "on_Negative_ButtonClick()");
+        Slog.d(TAG, "onNegativeButtonClick()");
 
         // Disable the button, to prevent more clicks.
         v.setEnabled(false);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index a0ebbfe..a5bb34f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -55,7 +55,7 @@
 import android.os.Parcelable;
 import android.os.SystemProperties;
 import android.text.TextUtils;
-import android.util.Log;
+import android.util.Slog;
 
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
@@ -71,7 +71,6 @@
  */
 @SuppressLint("LongLogTag")
 public class CompanionDeviceDiscoveryService extends Service {
-    private static final boolean DEBUG = false;
     private static final String TAG = "CDM_CompanionDeviceDiscoveryService";
 
     private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout";
@@ -147,7 +146,6 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        if (DEBUG) Log.d(TAG, "onCreate()");
 
         mBtManager = getSystemService(BluetoothManager.class);
         mBtAdapter = mBtManager.getAdapter();
@@ -158,7 +156,6 @@
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         final String action = intent.getAction();
-        if (DEBUG) Log.d(TAG, "onStartCommand() action=" + action);
 
         switch (action) {
             case ACTION_START_DISCOVERY:
@@ -174,15 +171,9 @@
         return START_NOT_STICKY;
     }
 
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        if (DEBUG) Log.d(TAG, "onDestroy()");
-    }
-
     @MainThread
     private void startDiscovery(@NonNull AssociationRequest request) {
-        if (DEBUG) Log.i(TAG, "startDiscovery() request=" + request);
+        Slog.d(TAG, "startDiscovery() request=" + request);
         requireNonNull(request);
 
         if (mDiscoveryStarted) throw new RuntimeException("Discovery in progress.");
@@ -218,7 +209,7 @@
 
     @MainThread
     private void stopDiscoveryAndFinish(boolean timeout) {
-        if (DEBUG) Log.i(TAG, "stopDiscovery()");
+        Slog.d(TAG, "stopDiscoveryAndFinish(" + timeout + ")");
 
         if (!mDiscoveryStarted) {
             stopSelf();
@@ -298,10 +289,9 @@
     private BluetoothBroadcastReceiver startBtScanningIfNeeded(
             List<BluetoothDeviceFilter> filters, boolean force) {
         if (isEmpty(filters) && !force) return null;
-        if (DEBUG) Log.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
+        Slog.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
 
         final BluetoothBroadcastReceiver receiver = new BluetoothBroadcastReceiver(filters);
-
         final IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
         registerReceiver(receiver, intentFilter);
 
@@ -313,7 +303,7 @@
     private WifiBroadcastReceiver startWifiScanningIfNeeded(
             List<WifiDeviceFilter> filters, boolean force) {
         if (isEmpty(filters) && !force) return null;
-        if (DEBUG) Log.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
+        Slog.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
 
         final WifiBroadcastReceiver receiver = new WifiBroadcastReceiver(filters);
 
@@ -329,10 +319,10 @@
     private ScanCallback startBleScanningIfNeeded(
             List<BluetoothLeDeviceFilter> filters, boolean force) {
         if (isEmpty(filters) && !force) return null;
-        if (DEBUG) Log.d(TAG, "BLEScanner.startScan");
+        Slog.d(TAG, "BLEScanner.startScan");
 
         if (mBleScanner == null) {
-            Log.w(TAG, "BLE Scanner is not available.");
+            Slog.w(TAG, "BLE Scanner is not available.");
             return null;
         }
 
@@ -350,18 +340,13 @@
 
     private void onDeviceFound(@NonNull DeviceFilterPair<?> device) {
         runOnMainThread(() -> {
-            if (DEBUG) Log.v(TAG, "onDeviceFound() " + device);
             if (mDiscoveryStopped) return;
             if (mDevicesFound.contains(device)) {
                 // TODO: update the device instead of ignoring (new found device may contain
                 //  additional/updated info, eg. name of the device).
-                if (DEBUG) {
-                    Log.d(TAG, "onDeviceFound() " + device.toShortString()
-                            + " - Already seen: ignore.");
-                }
                 return;
             }
-            Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+            Slog.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
 
             // First: make change.
             mDevicesFound.add(device);
@@ -376,7 +361,7 @@
 
     private void onDeviceLost(@NonNull DeviceFilterPair<?> device) {
         runOnMainThread(() -> {
-            Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+            Slog.i(TAG, "onDeviceLost(), device=" + device.toShortString());
 
             // First: make change.
             mDevicesFound.remove(device);
@@ -395,13 +380,10 @@
             timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN)
         }
 
-        if (DEBUG) Log.d(TAG, "scheduleTimeout(), timeout=" + timeout);
-
         Handler.getMain().postDelayed(mTimeoutRunnable, timeout);
     }
 
     private void timeout() {
-        if (DEBUG) Log.i(TAG, "timeout()");
         stopDiscoveryAndFinish(/* timeout */ true);
     }
 
@@ -419,10 +401,6 @@
 
         @Override
         public void onScanResult(int callbackType, ScanResult result) {
-            if (DEBUG) {
-                Log.v(TAG, "BLE.onScanResult() callback=" + callbackType + ", result=" + result);
-            }
-
             final DeviceFilterPair<ScanResult> match = findMatch(result, mFilters);
             if (match == null) return;
 
@@ -447,8 +425,6 @@
             final String action = intent.getAction();
             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
-            if (DEBUG) Log.v(TAG, action + ", device=" + device);
-
             if (action == null) return;
 
             final DeviceFilterPair<BluetoothDevice> match = findMatch(device, mFilters);
@@ -477,10 +453,6 @@
             }
 
             final List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
-            if (DEBUG) {
-                Log.v(TAG, "WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, results:\n  "
-                        + TextUtils.join("\n  ", scanResults));
-            }
 
             for (int i = 0; i < scanResults.size(); i++) {
                 final android.net.wifi.ScanResult scanResult = scanResults.get(i);
@@ -505,9 +477,7 @@
 
         DeviceFilterPair<T> result = matchingFilter != null
                 ? new DeviceFilterPair<>(dev, matchingFilter) : null;
-        if (DEBUG) {
-            Log.v(TAG, "findMatch(dev=" + dev + ", filters=" + filters + ") -> " + result);
-        }
+
         return result;
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 50ebdd5..4109079 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -47,9 +47,9 @@
 import android.service.credentials.CredentialProviderService
 import android.util.Log
 import android.content.Intent
-import android.os.IBinder
 import android.view.autofill.AutofillId
 import android.view.autofill.AutofillManager
+import android.view.autofill.IAutoFillManagerClient
 import android.widget.RemoteViews
 import android.widget.inline.InlinePresentationSpec
 import androidx.autofill.inline.v1.InlineSuggestionUi
@@ -95,7 +95,7 @@
             request: FillRequest,
             cancellationSignal: CancellationSignal,
             callback: FillCallback,
-            autofillCallback: IBinder
+            autofillCallback: IAutoFillManagerClient
     ) {
         val context = request.fillContexts
         val structure = context[context.size - 1].structure
@@ -160,7 +160,7 @@
                 CancellationSignal(),
                 Executors.newSingleThreadExecutor(),
                 outcome,
-                autofillCallback
+                autofillCallback.asBinder()
         )
     }
 
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,7 @@
     sdk_version: "system_current",
     rename_resources_package: false,
     static_libs: [
+        "xz-java",
         "androidx.leanback_leanback",
         "androidx.annotation_annotation",
         "androidx.fragment_fragment",
@@ -78,6 +79,7 @@
     overrides: ["PackageInstaller"],
 
     static_libs: [
+        "xz-java",
         "androidx.leanback_leanback",
         "androidx.fragment_fragment",
         "androidx.lifecycle_lifecycle-livedata",
@@ -110,6 +112,7 @@
     overrides: ["PackageInstaller"],
 
     static_libs: [
+        "xz-java",
         "androidx.leanback_leanback",
         "androidx.annotation_annotation",
         "androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 05f4d69..bf69d3b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,6 +146,17 @@
                 android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
                 android:exported="false" />
 
+        <!-- Wearable Components -->
+        <service android:name=".wear.WearPackageInstallerService"
+                 android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+                 android:foregroundServiceType="systemExempted"
+                 android:exported="true"/>
+
+        <provider android:name=".wear.WearPackageIconProvider"
+                  android:authorities="com.google.android.packageinstaller.wear.provider"
+                  android:grantUriPermissions="true"
+                  android:exported="false" />
+
         <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
             tools:node="remove" />
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
new file mode 100644
index 0000000..53a460d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
@@ -0,0 +1,173 @@
+/*
+ * 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 com.android.packageinstaller.wear;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Task that installs an APK. This must not be called on the main thread.
+ * This code is based off the Finsky/Wearsky implementation
+ */
+public class InstallTask {
+    private static final String TAG = "InstallTask";
+
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    private final Context mContext;
+    private String mPackageName;
+    private ParcelFileDescriptor mParcelFileDescriptor;
+    private PackageInstallerImpl.InstallListener mCallback;
+    private PackageInstaller.Session mSession;
+    private IntentSender mCommitCallback;
+
+    private Exception mException = null;
+    private int mErrorCode = 0;
+    private String mErrorDesc = null;
+
+    public InstallTask(Context context, String packageName,
+            ParcelFileDescriptor parcelFileDescriptor,
+            PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
+            IntentSender commitCallback) {
+        mContext = context;
+        mPackageName = packageName;
+        mParcelFileDescriptor = parcelFileDescriptor;
+        mCallback = callback;
+        mSession = session;
+        mCommitCallback = commitCallback;
+    }
+
+    public boolean isError() {
+        return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
+    }
+
+    public void execute() {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new IllegalStateException("This method cannot be called from the UI thread.");
+        }
+
+        OutputStream sessionStream = null;
+        try {
+            sessionStream = mSession.openWrite(mPackageName, 0, -1);
+
+            // 2b: Stream the asset to the installer. Note:
+            // Note: writeToOutputStreamFromAsset() always safely closes the input stream
+            writeToOutputStreamFromAsset(sessionStream);
+            mSession.fsync(sessionStream);
+        } catch (Exception e) {
+            mException = e;
+            mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
+            mErrorDesc = "Could not write to stream";
+        } finally {
+            if (sessionStream != null) {
+                // 2c: close output stream
+                try {
+                    sessionStream.close();
+                } catch (Exception e) {
+                    // Ignore otherwise
+                    if (mException == null) {
+                        mException = e;
+                        mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
+                        mErrorDesc = "Could not close session stream";
+                    }
+                }
+            }
+        }
+
+        if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
+            // An error occurred, we're done
+            Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
+                    + mErrorDesc + ", " + mException);
+            mSession.close();
+            mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
+        } else {
+            // 3. Commit the session (this actually installs it.)  Session map
+            // will be cleaned up in the callback.
+            mCallback.installBeginning();
+            mSession.commit(mCommitCallback);
+            mSession.close();
+        }
+    }
+
+    /**
+     * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
+     * corresponding to the {@code Asset} and then write the contents into an
+     * {@code OutputStream} that is passed in.
+     * <br>
+     * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
+     */
+    private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
+        if (outputStream == null) {
+            mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
+            mErrorDesc = "Got a null OutputStream.";
+            return false;
+        }
+
+        if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null)  {
+            mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
+            mErrorDesc = "Could not get FD";
+            return false;
+        }
+
+        InputStream inputStream = null;
+        try {
+            byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
+            int bytesRead;
+            inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
+
+            while ((bytesRead = inputStream.read(inputBuf)) > -1) {
+                if (bytesRead > 0) {
+                    outputStream.write(inputBuf, 0, bytesRead);
+                }
+            }
+
+            outputStream.flush();
+        } catch (IOException e) {
+            mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
+            mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
+            return false;
+        } finally {
+            safeClose(inputStream);
+        }
+
+        return true;
+    }
+
+    /**
+     * Quietly close a closeable resource (e.g. a stream or file). The input may already
+     * be closed and it may even be null.
+     */
+    public static void safeClose(Closeable resource) {
+        if (resource != null) {
+            try {
+                resource.close();
+            } catch (IOException ioe) {
+                // Catch and discard the error
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
new file mode 100644
index 0000000..3daf3d8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.android.packageinstaller.wear;
+
+/**
+ * Constants for Installation / Uninstallation requests.
+ * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
+ */
+public class InstallerConstants {
+    /** Request succeeded */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * The new PackageInstaller also returns a small set of less granular error codes, which
+     * we'll remap to the range -500 and below to keep away from existing installer codes
+     * (which run from -1 to -110).
+     */
+    public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
+
+    public static final int ERROR_COULD_NOT_GET_FD = -603;
+    /** This node is not targeted by this request. */
+
+    /** The install did not complete because could not create PackageInstaller session */
+    public final static int ERROR_INSTALL_CREATE_SESSION = -612;
+    /** The install did not complete because could not open PackageInstaller session  */
+    public final static int ERROR_INSTALL_OPEN_SESSION = -613;
+    /** The install did not complete because could not open PackageInstaller output stream */
+    public final static int ERROR_INSTALL_OPEN_STREAM = -614;
+    /** The install did not complete because of an exception while streaming bytes */
+    public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
+    /** The install did not complete because of an unexpected exception from PackageInstaller */
+    public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
+    /** The install did not complete because of an unexpected userActionRequired callback */
+    public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
+    /** The install did not complete because of an unexpected broadcast (missing fields) */
+    public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
+    /** The install did not complete because of an error while copying from downloaded file */
+    public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
+    /** The install did not complete because of an error while copying to the PackageInstaller
+     * output stream */
+    public final static int ERROR_INSTALL_COPY_STREAM = -620;
+    /** The install did not complete because of an error while closing the PackageInstaller
+     * output stream */
+    public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
new file mode 100644
index 0000000..bdc22cf
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.packageinstaller.wear;
+
+import android.content.Context;
+
+/**
+ * Factory that creates a Package Installer.
+ */
+public class PackageInstallerFactory {
+    private static PackageInstallerImpl sPackageInstaller;
+
+    /**
+     * Return the PackageInstaller shared object. {@code init} should have already been called.
+     */
+    public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
+        if (sPackageInstaller == null) {
+            sPackageInstaller = new PackageInstallerImpl(context);
+        }
+        return sPackageInstaller;
+    }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
new file mode 100644
index 0000000..1e37f15
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * 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 com.android.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of package manager installation using modern PackageInstaller api.
+ *
+ * Heavily copied from Wearsky/Finsky implementation
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class PackageInstallerImpl {
+    private static final String TAG = "PackageInstallerImpl";
+
+    /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
+    private static final String ACTION_INSTALL_COMMIT =
+            "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
+
+    private final Context mContext;
+    private final PackageInstaller mPackageInstaller;
+    private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
+    private final Map<String, PackageInstaller.Session> mOpenSessionMap;
+
+    public PackageInstallerImpl(Context context) {
+        mContext = context.getApplicationContext();
+        mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+
+        // Capture a map of known sessions
+        // This list will be pruned a bit later (stale sessions will be canceled)
+        mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
+        List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
+        for (int i = 0; i < mySessions.size(); i++) {
+            PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
+            String packageName = sessionInfo.getAppPackageName();
+            PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
+
+            // Checking for old info is strictly for logging purposes
+            if (oldInfo != null) {
+                Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
+                        .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
+            }
+        }
+        mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
+    }
+
+    /**
+     * This callback will be made after an installation attempt succeeds or fails.
+     */
+    public interface InstallListener {
+        /**
+         * This callback signals that preflight checks have succeeded and installation
+         * is beginning.
+         */
+        void installBeginning();
+
+        /**
+         * This callback signals that installation has completed.
+         */
+        void installSucceeded();
+
+        /**
+         * This callback signals that installation has failed.
+         */
+        void installFailed(int errorCode, String errorDesc);
+    }
+
+    /**
+     * This is a placeholder implementation that bundles an entire "session" into a single
+     * call. This will be replaced by more granular versions that allow longer session lifetimes,
+     * download progress tracking, etc.
+     *
+     * This must not be called on main thread.
+     */
+    public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
+            final InstallListener callback) {
+        // 0. Generic try/catch block because I am not really sure what exceptions (other than
+        // IOException) might be thrown by PackageInstaller and I want to handle them
+        // at least slightly gracefully.
+        try {
+            // 1. Create or recover a session, and open it
+            // Try recovery first
+            PackageInstaller.Session session = null;
+            PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+            if (sessionInfo != null) {
+                // See if it's openable, or already held open
+                session = getSession(packageName);
+            }
+            // If open failed, or there was no session, create a new one and open it.
+            // If we cannot create or open here, the failure is terminal.
+            if (session == null) {
+                try {
+                    innerCreateSession(packageName);
+                } catch (IOException ioe) {
+                    Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
+                    callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
+                            "Could not create session");
+                    mSessionInfoMap.remove(packageName);
+                    return;
+                }
+                sessionInfo = mSessionInfoMap.get(packageName);
+                try {
+                    session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+                    mOpenSessionMap.put(packageName, session);
+                } catch (SecurityException se) {
+                    Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
+                    callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
+                            "Can't open session");
+                    mSessionInfoMap.remove(packageName);
+                    return;
+                }
+            }
+
+            // 2. Launch task to handle file operations.
+            InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
+                    callback, session,
+                    getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
+            task.execute();
+            if (task.isError()) {
+                cancelSession(sessionInfo.getSessionId(), packageName);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
+                    + e.getMessage());
+            callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
+                    "Unexpected exception while installing " + packageName);
+        }
+    }
+
+    /**
+     * Retrieve an existing session. Will open if needed, but does not attempt to create.
+     */
+    private PackageInstaller.Session getSession(String packageName) {
+        // Check for already-open session
+        PackageInstaller.Session session = mOpenSessionMap.get(packageName);
+        if (session != null) {
+            try {
+                // Probe the session to ensure that it's still open. This may or may not
+                // throw (if non-open), but it may serve as a canary for stale sessions.
+                session.getNames();
+                return session;
+            } catch (IOException ioe) {
+                Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
+                mOpenSessionMap.remove(packageName);
+            } catch (SecurityException se) {
+                Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
+                mOpenSessionMap.remove(packageName);
+            }
+        }
+        // Check to see if this is a known session
+        PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+        if (sessionInfo == null) {
+            return null;
+        }
+        // Try to open it. If we fail here, assume that the SessionInfo was stale.
+        try {
+            session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+        } catch (SecurityException se) {
+            Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
+            mSessionInfoMap.remove(packageName);
+            return null;
+        } catch (IOException ioe) {
+            Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
+                    + " - deleting info");
+            mSessionInfoMap.remove(packageName);
+            return null;
+        }
+        mOpenSessionMap.put(packageName, session);
+        return session;
+    }
+
+    /** This version throws an IOException when the session cannot be created */
+    private void innerCreateSession(String packageName) throws IOException {
+        if (mSessionInfoMap.containsKey(packageName)) {
+            Log.w(TAG, "Creating session for " + packageName + " when one already exists");
+            return;
+        }
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.setAppPackageName(packageName);
+
+        // IOException may be thrown at this point
+        int sessionId = mPackageInstaller.createSession(params);
+        PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
+        mSessionInfoMap.put(packageName, sessionInfo);
+    }
+
+    /**
+     * Cancel a session based on its sessionId. Package name is for logging only.
+     */
+    private void cancelSession(int sessionId, String packageName) {
+        // Close if currently held open
+        closeSession(packageName);
+        // Remove local record
+        mSessionInfoMap.remove(packageName);
+        try {
+            mPackageInstaller.abandonSession(sessionId);
+        } catch (SecurityException se) {
+            // The session no longer exists, so we can exit quietly.
+            return;
+        }
+    }
+
+    /**
+     * Close a session if it happens to be held open.
+     */
+    private void closeSession(String packageName) {
+        PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
+        if (session != null) {
+            // Unfortunately close() is not idempotent. Try our best to make this safe.
+            try {
+                session.close();
+            } catch (Exception e) {
+                Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
+                        + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Creates a commit callback for the package install that's underway. This will be called
+     * some time after calling session.commit() (above).
+     */
+    private IntentSender getCommitCallback(final String packageName, final int sessionId,
+            final InstallListener callback) {
+        // Create a single-use broadcast receiver
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mContext.unregisterReceiver(this);
+                handleCommitCallback(intent, packageName, sessionId, callback);
+            }
+        };
+        // Create a matching intent-filter and register the receiver
+        String action = ACTION_INSTALL_COMMIT + "." + packageName;
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(action);
+        mContext.registerReceiver(broadcastReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED);
+
+        // Create a matching PendingIntent and use it to generate the IntentSender
+        Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
+                broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
+                        | PendingIntent.FLAG_MUTABLE);
+        return pendingIntent.getIntentSender();
+    }
+
+    /**
+     * Examine the extras to determine information about the package update/install, decode
+     * the result, and call the appropriate callback.
+     *
+     * @param intent The intent, which the PackageInstaller will have added Extras to
+     * @param packageName The package name we created the receiver for
+     * @param sessionId The session Id we created the receiver for
+     * @param callback The callback to report success/failure to
+     */
+    private void handleCommitCallback(Intent intent, String packageName, int sessionId,
+            InstallListener callback) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Installation of " + packageName + " finished with extras "
+                    + intent.getExtras());
+        }
+        String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+        int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            cancelSession(sessionId, packageName);
+            callback.installSucceeded();
+        } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
+            // TODO - use the constant when the correct/final name is in the SDK
+            // TODO This is unexpected, so we are treating as failure for now
+            cancelSession(sessionId, packageName);
+            callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
+                    "Unexpected: user action required");
+        } else {
+            cancelSession(sessionId, packageName);
+            int errorCode = getPackageManagerErrorCode(status);
+            Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
+                    + statusMessage);
+            callback.installFailed(errorCode, null);
+        }
+    }
+
+    private int getPackageManagerErrorCode(int status) {
+        // This is a hack: because PackageInstaller now reports error codes
+        // with small positive values, we need to remap them into a space
+        // that is more compatible with the existing package manager error codes.
+        // See https://sites.google.com/a/google.com/universal-store/documentation
+        //       /android-client/download-error-codes
+        int errorCode;
+        if (status == Integer.MIN_VALUE) {
+            errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
+        } else {
+            errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
+        }
+        return errorCode;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 0000000..2c289b2
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+    private static final String KEY_PACKAGE_NAME =
+            "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+    private static final String KEY_ASSET_URI =
+            "com.google.android.clockwork.EXTRA_ASSET_URI";
+    private static final String KEY_START_ID =
+            "com.google.android.clockwork.EXTRA_START_ID";
+    private static final String KEY_PERM_URI =
+            "com.google.android.clockwork.EXTRA_PERM_URI";
+    private static final String KEY_CHECK_PERMS =
+            "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+    private static final String KEY_SKIP_IF_SAME_VERSION =
+            "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+    private static final String KEY_COMPRESSION_ALG =
+            "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+    private static final String KEY_COMPANION_SDK_VERSION =
+            "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+    private static final String KEY_COMPANION_DEVICE_VERSION =
+            "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+    private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+            "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+    private static final String KEY_SKIP_IF_LOWER_VERSION =
+            "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
+
+    public static String getPackageName(Bundle b) {
+        return b.getString(KEY_PACKAGE_NAME);
+    }
+
+    public static Bundle setPackageName(Bundle b, String packageName) {
+        b.putString(KEY_PACKAGE_NAME, packageName);
+        return b;
+    }
+
+    public static Uri getAssetUri(Bundle b) {
+        return b.getParcelable(KEY_ASSET_URI);
+    }
+
+    public static Uri getPermUri(Bundle b) {
+        return b.getParcelable(KEY_PERM_URI);
+    }
+
+    public static boolean checkPerms(Bundle b) {
+        return b.getBoolean(KEY_CHECK_PERMS);
+    }
+
+    public static boolean skipIfSameVersion(Bundle b) {
+        return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+    }
+
+    public static int getCompanionSdkVersion(Bundle b) {
+        return b.getInt(KEY_COMPANION_SDK_VERSION);
+    }
+
+    public static int getCompanionDeviceVersion(Bundle b) {
+        return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+    }
+
+    public static String getCompressionAlg(Bundle b) {
+        return b.getString(KEY_COMPRESSION_ALG);
+    }
+
+    public static int getStartId(Bundle b) {
+        return b.getInt(KEY_START_ID);
+    }
+
+    public static boolean skipIfLowerVersion(Bundle b) {
+        return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
+    }
+
+    public static Bundle setStartId(Bundle b, int startId) {
+        b.putInt(KEY_START_ID, startId);
+        return b;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 0000000..02b9d29
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+    private static final String TAG = "WearPackageIconProvider";
+    public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+    private static final String REQUIRED_PERMISSION =
+            "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+    /** MIME types. */
+    public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException("Query is not supported.");
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("URI passed in is null.");
+        }
+
+        if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+            return ICON_TYPE;
+        }
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException("Insert is not supported.");
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (uri == null) {
+            throw new IllegalArgumentException("URI passed in is null.");
+        }
+
+        enforcePermissions(uri);
+
+        if (ICON_TYPE.equals(getType(uri))) {
+            final File file = WearPackageUtil.getIconFile(
+                    this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+            if (file != null) {
+                file.delete();
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("Update is not supported.");
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(
+            Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+        if (uri == null) {
+            throw new IllegalArgumentException("URI passed in is null.");
+        }
+
+        enforcePermissions(uri);
+
+        if (ICON_TYPE.equals(getType(uri))) {
+            final File file = WearPackageUtil.getIconFile(
+                    this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+            if (file != null) {
+                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+            }
+        }
+        return null;
+    }
+
+    public static Uri getUriForPackage(final String packageName) {
+        return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+    }
+
+    private String getPackageNameFromUri(Uri uri) {
+        if (uri == null) {
+            return null;
+        }
+        List<String> pathSegments = uri.getPathSegments();
+        String packageName = pathSegments.get(pathSegments.size() - 1);
+
+        if (packageName.endsWith(".icon")) {
+            packageName = packageName.substring(0, packageName.lastIndexOf("."));
+        }
+        return packageName;
+    }
+
+    /**
+     * Make sure the calling app is either a system app or the same app or has the right permission.
+     * @throws SecurityException if the caller has insufficient permissions.
+     */
+    @TargetApi(Build.VERSION_CODES.BASE_1_1)
+    private void enforcePermissions(Uri uri) {
+        // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+        // allow System process to access this provider.
+        Context context = getContext();
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final int myUid = android.os.Process.myUid();
+
+        if (uid == myUid || isSystemApp(context, pid)) {
+            return;
+        }
+
+        if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+            return;
+        }
+
+        // last chance, check against any uri grants
+        if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                == PERMISSION_GRANTED) {
+            return;
+        }
+
+        throw new SecurityException("Permission Denial: reading "
+                + getClass().getName() + " uri " + uri + " from pid=" + pid
+                + ", uid=" + uid);
+    }
+
+    /**
+     * From the pid of the calling process, figure out whether this is a system app or not. We do
+     * this by checking the application information corresponding to the pid and then checking if
+     * FLAG_SYSTEM is set.
+     */
+    @TargetApi(Build.VERSION_CODES.CUPCAKE)
+    private boolean isSystemApp(Context context, int pid) {
+        // Get the Activity Manager Object
+        ActivityManager aManager =
+                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        // Get the list of running Applications
+        List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+                aManager.getRunningAppProcesses();
+        for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+            if (rapInfo.pid == pid) {
+                try {
+                    PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+                            rapInfo.pkgList[0], 0);
+                    if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+                            (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        Log.d(TAG, pid + " is a system app.");
+                        return true;
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.e(TAG, "Could not find package information.", e);
+                    return false;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 0000000..ae0f4ec
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.Nullable;
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ *  Install Action example:
+ *  adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
+ *     -d package://com.google.android.gms \
+ *     --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ *     --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
+ *     --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
+ *     --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
+ *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ *  Uninstall Action example:
+ *  adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ *     -d package://com.google.android.gms \
+ *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ *  Retry GMS:
+ *  adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service
+        implements EventResultPersister.EventResultObserver {
+    private static final String TAG = "WearPkgInstallerService";
+
+    private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+    private static final String BROADCAST_ACTION =
+            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
+
+    private final int START_INSTALL = 1;
+    private final int START_UNINSTALL = 2;
+
+    private int mInstallNotificationId = 1;
+    private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+    private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+    private class UninstallParams {
+        public String mPackageName;
+        public PowerManager.WakeLock mLock;
+
+        UninstallParams(String packageName, PowerManager.WakeLock lock) {
+            mPackageName = packageName;
+            mLock = lock;
+        }
+    }
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case START_INSTALL:
+                    installPackage(msg.getData());
+                    break;
+                case START_UNINSTALL:
+                    uninstallPackage(msg.getData());
+                    break;
+            }
+        }
+    }
+    private ServiceHandler mServiceHandler;
+    private NotificationChannel mNotificationChannel;
+    private static volatile PowerManager.WakeLock lockStatic = null;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        HandlerThread thread = new HandlerThread("PackageInstallerThread",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        mServiceHandler = new ServiceHandler(thread.getLooper());
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (!DeviceUtils.isWear(this)) {
+            Log.w(TAG, "Not running on wearable.");
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        if (intent == null) {
+            Log.w(TAG, "Got null intent.");
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Got install/uninstall request " + intent);
+        }
+
+        Uri packageUri = intent.getData();
+        if (packageUri == null) {
+            Log.e(TAG, "No package URI in intent");
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+        if (packageName == null) {
+            Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+        if (!lock.isHeld()) {
+            lock.acquire();
+        }
+
+        Bundle intentBundle = intent.getExtras();
+        if (intentBundle == null) {
+            intentBundle = new Bundle();
+        }
+        WearPackageArgs.setStartId(intentBundle, startId);
+        WearPackageArgs.setPackageName(intentBundle, packageName);
+        Message msg;
+        String notifTitle;
+        if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+            msg = mServiceHandler.obtainMessage(START_INSTALL);
+            notifTitle = getString(R.string.installing);
+        } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+            msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+            notifTitle = getString(R.string.uninstalling);
+        } else {
+            Log.e(TAG, "Unknown action : " + intent.getAction());
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+        Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
+        startForeground(notifPair.first, notifPair.second);
+        msg.setData(intentBundle);
+        mServiceHandler.sendMessage(msg);
+        return START_NOT_STICKY;
+    }
+
+    private void installPackage(Bundle argsBundle) {
+        int startId = WearPackageArgs.getStartId(argsBundle);
+        final String packageName = WearPackageArgs.getPackageName(argsBundle);
+        final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+        final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+        boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+        boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+        int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+        int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+        String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+        boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+                    ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+                    checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+                    ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+                    companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
+                    ", skipIfLowerVersion: " + skipIfLowerVersion);
+        }
+        final PackageManager pm = getPackageManager();
+        File tempFile = null;
+        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+        boolean messageSent = false;
+        try {
+            PackageInfo existingPkgInfo = null;
+            try {
+                existingPkgInfo = pm.getPackageInfo(packageName,
+                        PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
+                if (existingPkgInfo != null) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Replacing package:" + packageName);
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Ignore this exception. We could not find the package, will treat as a new
+                // installation.
+            }
+            // TODO(28021618): This was left as a temp file due to the fact that this code is being
+            //       deprecated and that we need the bare minimum to continue working moving forward
+            //       If this code is used as reference, this permission logic might want to be
+            //       reworked to use a stream instead of a file so that we don't need to write a
+            //       file at all.  Note that there might be some trickiness with opening a stream
+            //       for multiple users.
+            ParcelFileDescriptor parcelFd = getContentResolver()
+                    .openFileDescriptor(assetUri, "r");
+            tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+                    parcelFd, packageName, compressionAlg);
+            if (tempFile == null) {
+                Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+                return;
+            }
+            PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
+                    PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
+            if (pkgInfo == null) {
+                Log.e(TAG, "Could not parse apk information for " + packageName);
+                return;
+            }
+
+            if (!pkgInfo.packageName.equals(packageName)) {
+                Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+                        packageName);
+                return;
+            }
+
+            ApplicationInfo appInfo = pkgInfo.applicationInfo;
+            appInfo.sourceDir = tempFile.getPath();
+            appInfo.publicSourceDir = tempFile.getPath();
+            getLabelAndUpdateNotification(packageName,
+                    getString(R.string.installing_app, appInfo.loadLabel(pm)));
+
+            List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
+
+            // Log if the installed pkg has a higher version number.
+            if (existingPkgInfo != null) {
+                long longVersionCode = pkgInfo.getLongVersionCode();
+                if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
+                    if (skipIfSameVersion) {
+                        Log.w(TAG, "Version number (" + longVersionCode +
+                                ") of new app is equal to existing app for " + packageName +
+                                "; not installing due to versionCheck");
+                        return;
+                    } else {
+                        Log.w(TAG, "Version number of new app (" + longVersionCode +
+                                ") is equal to existing app for " + packageName);
+                    }
+                } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
+                    if (skipIfLowerVersion) {
+                        // Starting in Feldspar, we are not going to allow downgrades of any app.
+                        Log.w(TAG, "Version number of new app (" + longVersionCode +
+                                ") is lower than existing app ( "
+                                + existingPkgInfo.getLongVersionCode() +
+                                ") for " + packageName + "; not installing due to versionCheck");
+                        return;
+                    } else {
+                        Log.w(TAG, "Version number of new app (" + longVersionCode +
+                                ") is lower than existing app ( "
+                                + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
+                    }
+                }
+
+                // Following the Android Phone model, we should only check for permissions for any
+                // newly defined perms.
+                if (existingPkgInfo.requestedPermissions != null) {
+                    for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+                        // If the permission is granted, then we will not ask to request it again.
+                        if ((existingPkgInfo.requestedPermissionsFlags[i] &
+                                PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+                                        " is already granted for " + packageName);
+                            }
+                            wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+                        }
+                    }
+                }
+            }
+
+            // Check that the wearable has all the features.
+            boolean hasAllFeatures = true;
+            for (FeatureInfo feature : pkgInfo.reqFeatures) {
+                if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+                        (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+                    Log.e(TAG, "Wearable does not have required feature: " + feature +
+                            " for " + packageName);
+                    hasAllFeatures = false;
+                }
+            }
+
+            if (!hasAllFeatures) {
+                return;
+            }
+
+            // Check permissions on both the new wearable package and also on the already installed
+            // wearable package.
+            // If the app is targeting API level 23, we will also start a service in ClockworkHome
+            // which will ultimately prompt the user to accept/reject permissions.
+            if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
+                    companionDeviceVersion, permUri, wearablePerms, tempFile)) {
+                Log.w(TAG, "Wearable does not have enough permissions.");
+                return;
+            }
+
+            // Finally install the package.
+            ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
+            PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
+                    new PackageInstallListener(this, lock, startId, packageName));
+
+            messageSent = true;
+            Log.i(TAG, "Sent installation request for " + packageName);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+        } finally {
+            if (!messageSent) {
+                // Some error happened. If the message has been sent, we can wait for the observer
+                // which will finish the service.
+                if (tempFile != null) {
+                    tempFile.delete();
+                }
+                finishService(lock, startId);
+            }
+        }
+    }
+
+    // TODO: This was left using the old PackageManager API due to the fact that this code is being
+    //       deprecated and that we need the bare minimum to continue working moving forward
+    //       If this code is used as reference, this logic should be reworked to use the new
+    //       PackageInstaller APIs similar to how installPackage was reworked
+    private void uninstallPackage(Bundle argsBundle) {
+        int startId = WearPackageArgs.getStartId(argsBundle);
+        final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+        UninstallParams params = new UninstallParams(packageName, lock);
+        mServiceIdToParams.put(startId, params);
+
+        final PackageManager pm = getPackageManager();
+        try {
+            PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+            getLabelAndUpdateNotification(packageName,
+                    getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+
+            int uninstallId = UninstallEventReceiver.addObserver(this,
+                    EventResultPersister.GENERATE_NEW_ID, this);
+
+            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+            broadcastIntent.setPackage(getPackageName());
+
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+                    broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                            | PendingIntent.FLAG_MUTABLE);
+
+            // Found package, send uninstall request.
+            pm.getPackageInstaller().uninstall(
+                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    PackageManager.DELETE_ALL_USERS,
+                    pendingIntent.getIntentSender());
+
+            Log.i(TAG, "Sent delete request for " + packageName);
+        } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
+            // Couldn't find the package, no need to call uninstall.
+            Log.w(TAG, "Could not find package, not deleting " + packageName, e);
+            finishService(lock, startId);
+        } catch (EventResultPersister.OutOfIdsException e) {
+            Log.e(TAG, "Fails to start uninstall", e);
+            finishService(lock, startId);
+        }
+    }
+
+    @Override
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+        if (mServiceIdToParams.containsKey(serviceId)) {
+            UninstallParams params = mServiceIdToParams.get(serviceId);
+            try {
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+                } else {
+                    Log.e(TAG, "Package uninstall failed " + params.mPackageName
+                            + ", returnCode " + legacyStatus);
+                }
+            } finally {
+                finishService(params.mLock, serviceId);
+            }
+        }
+    }
+
+    private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
+            int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
+            File apkFile) {
+        // Assumption: We are running on Android O.
+        // If the Phone App is targeting M, all permissions may not have been granted to the phone
+        // app. If the Wear App is then not targeting M, there may be permissions that are not
+        // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
+        // app.
+        if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+            // Install the app if Wear App is ready for the new perms model.
+            return true;
+        }
+
+        if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
+            // All permissions requested by the watch are already granted on the phone, no need
+            // to do anything.
+            return true;
+        }
+
+        // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
+        if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
+            Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
+                    + "phone app is targeting at least 23, will continue.");
+        }
+
+        return false;
+    }
+
+    /**
+     * Given a {@string packageName} corresponding to a phone app, query the provider for all the
+     * perms that are granted.
+     *
+     * @return true if the Wear App has any perms that have not been granted yet on the phone side.
+     * @return true if there is any error cases.
+     */
+    private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
+            List<String> wearablePermissions) {
+        if (permUri == null) {
+            Log.e(TAG, "Permission URI is null");
+            // Pretend there is an ungranted permission to avoid installing for error cases.
+            return true;
+        }
+        Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
+        if (permCursor == null) {
+            Log.e(TAG, "Could not get the cursor for the permissions");
+            // Pretend there is an ungranted permission to avoid installing for error cases.
+            return true;
+        }
+
+        Set<String> grantedPerms = new HashSet<>();
+        Set<String> ungrantedPerms = new HashSet<>();
+        while(permCursor.moveToNext()) {
+            // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+            // verify their types.
+            if (permCursor.getColumnCount() == 2
+                    && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+                    && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+                String perm = permCursor.getString(0);
+                Integer granted = permCursor.getInt(1);
+                if (granted == 1) {
+                    grantedPerms.add(perm);
+                } else {
+                    ungrantedPerms.add(perm);
+                }
+            }
+        }
+        permCursor.close();
+
+        boolean hasUngrantedPerm = false;
+        for (String wearablePerm : wearablePermissions) {
+            if (!grantedPerms.contains(wearablePerm)) {
+                hasUngrantedPerm = true;
+                if (!ungrantedPerms.contains(wearablePerm)) {
+                    // This is an error condition. This means that the wearable has permissions that
+                    // are not even declared in its host app. This is a developer error.
+                    Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+                            + "\" that is not defined in the host application's manifest.");
+                } else {
+                    Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+                            "\" that is not granted in the host application.");
+                }
+            }
+        }
+        return hasUngrantedPerm;
+    }
+
+    /** Finishes the service after fulfilling obligation to call startForeground. */
+    private void finishServiceEarly(int startId) {
+        Pair<Integer, Notification> notifPair = buildNotification(
+                getApplicationContext().getPackageName(), "");
+        startForeground(notifPair.first, notifPair.second);
+        finishService(null, startId);
+    }
+
+    private void finishService(PowerManager.WakeLock lock, int startId) {
+        if (lock != null && lock.isHeld()) {
+            lock.release();
+        }
+        stopSelf(startId);
+    }
+
+    private synchronized PowerManager.WakeLock getLock(Context context) {
+        if (lockStatic == null) {
+            PowerManager mgr =
+                    (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            lockStatic = mgr.newWakeLock(
+                    PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+            lockStatic.setReferenceCounted(true);
+        }
+        return lockStatic;
+    }
+
+    private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
+        private Context mContext;
+        private PowerManager.WakeLock mWakeLock;
+        private int mStartId;
+        private String mApplicationPackageName;
+        private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
+                int startId, String applicationPackageName) {
+            mContext = context;
+            mWakeLock = wakeLock;
+            mStartId = startId;
+            mApplicationPackageName = applicationPackageName;
+        }
+
+        @Override
+        public void installBeginning() {
+            Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
+        }
+
+        @Override
+        public void installSucceeded() {
+            try {
+                Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
+
+                // Delete tempFile from the file system.
+                File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
+                if (tempFile != null) {
+                    tempFile.delete();
+                }
+            } finally {
+                finishService(mWakeLock, mStartId);
+            }
+        }
+
+        @Override
+        public void installFailed(int errorCode, String errorDesc) {
+            Log.e(TAG, "Package install failed " + mApplicationPackageName
+                    + ", errorCode " + errorCode);
+            finishService(mWakeLock, mStartId);
+        }
+    }
+
+    private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
+            final String title) {
+        int notifId;
+        if (mNotifIdMap.containsKey(packageName)) {
+            notifId = mNotifIdMap.get(packageName);
+        } else {
+            notifId = mInstallNotificationId++;
+            mNotifIdMap.put(packageName, notifId);
+        }
+
+        if (mNotificationChannel == null) {
+            mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
+                    getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
+            NotificationManager notificationManager = getSystemService(NotificationManager.class);
+            notificationManager.createNotificationChannel(mNotificationChannel);
+        }
+        return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
+            .setSmallIcon(R.drawable.ic_file_download)
+            .setContentTitle(title)
+            .build());
+    }
+
+    private void getLabelAndUpdateNotification(String packageName, String title) {
+        // Update notification since we have a label now.
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
+        notificationManager.notify(notifPair.first, notifPair.second);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 0000000..6a9145d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WearPackageUtil {
+    private static final String TAG = "WearablePkgInstaller";
+
+    private static final String COMPRESSION_LZMA = "lzma";
+    private static final String COMPRESSION_XZ = "xz";
+
+    public static File getTemporaryFile(Context context, String packageName) {
+        try {
+            File newFileDir = new File(context.getFilesDir(), "tmp");
+            newFileDir.mkdirs();
+            Os.chmod(newFileDir.getAbsolutePath(), 0771);
+            File newFile = new File(newFileDir, packageName + ".apk");
+            return newFile;
+        }   catch (ErrnoException e) {
+            Log.e(TAG, "Failed to open.", e);
+            return null;
+        }
+    }
+
+    public static File getIconFile(final Context context, final String packageName) {
+        try {
+            File newFileDir = new File(context.getFilesDir(), "images/icons");
+            newFileDir.mkdirs();
+            Os.chmod(newFileDir.getAbsolutePath(), 0771);
+            return new File(newFileDir, packageName + ".icon");
+        }   catch (ErrnoException e) {
+            Log.e(TAG, "Failed to open.", e);
+            return null;
+        }
+    }
+
+    /**
+     * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+     * by the PackageManager, we will parse it before sending it to the PackageManager.
+     * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
+     * the fd to a File.
+     *
+     * @param context
+     * @param fd FileDescriptor to convert to File
+     * @param packageName Name of package, will define the name of the file
+     * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+     *                       decompress it here
+     */
+    public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+            String packageName, String compressionAlg) {
+        File newFile = getTemporaryFile(context, packageName);
+        if (fd == null || fd.getFileDescriptor() == null)  {
+            return null;
+        }
+        InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+        try {
+            if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+                fr = new XZInputStream(fr);
+            } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+                fr = new LZMAInputStream(fr);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+            return null;
+        }
+
+        int nRead;
+        byte[] data = new byte[1024];
+        try {
+            final FileOutputStream fo = new FileOutputStream(newFile);
+            while ((nRead = fr.read(data, 0, data.length)) != -1) {
+                fo.write(data, 0, nRead);
+            }
+            fo.flush();
+            fo.close();
+            Os.chmod(newFile.getAbsolutePath(), 0644);
+            return newFile;
+        } catch (IOException e) {
+            Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+            return null;
+        }   catch (ErrnoException e) {
+            Log.e(TAG, "Could not set permissions on file ", e);
+            return null;
+        } finally {
+            try {
+                fr.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to close the file from FD ", e);
+            }
+        }
+    }
+
+    /**
+     * @return com.google.com from expected formats like
+     * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+     */
+    public static String getSanitizedPackageName(Uri packageUri) {
+        String packageName = packageUri.getEncodedSchemeSpecificPart();
+        if (packageName != null) {
+            return packageName.replaceAll("^/+", "");
+        }
+        return packageName;
+    }
+}
diff --git a/packages/SettingsLib/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
new file mode 100644
index 0000000..e2ed1e4
--- /dev/null
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -0,0 +1,21 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "SettingsLibGraph-srcs",
+    srcs: ["src/**/*"],
+}
+
+android_library {
+    name: "SettingsLibGraph",
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+    srcs: [":SettingsLibGraph-srcs"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.preference_preference",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Graph/AndroidManifest.xml b/packages/SettingsLib/Graph/AndroidManifest.xml
new file mode 100644
index 0000000..93acb35
--- /dev/null
+++ b/packages/SettingsLib/Graph/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.graph">
+
+  <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
new file mode 100644
index 0000000..9231f40
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
@@ -0,0 +1,70 @@
+package com.android.settingslib.graph
+
+import androidx.annotation.StringRes
+import androidx.annotation.XmlRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+
+/** Manager to create and initialize preference screen. */
+class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
+    private val context = preferenceManager.context
+    // the map will preserve order
+    private val updaters = mutableMapOf<String, PreferenceUpdater>()
+    private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
+
+    /** Creates an empty [PreferenceScreen]. */
+    fun createPreferenceScreen(): PreferenceScreen =
+        preferenceManager.createPreferenceScreen(context)
+
+    /** Creates [PreferenceScreen] from resource. */
+    fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
+        preferenceManager.inflateFromResource(context, xmlRes, null)
+
+    /** Adds updater for given preference. */
+    fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
+        addPreferenceUpdater(context.getString(key), updater)
+
+    /** Adds updater for given preference. */
+    fun addPreferenceUpdater(
+        key: String,
+        updater: PreferenceUpdater,
+    ): PreferenceScreenManager {
+        updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
+        return this
+    }
+
+    /** Adds updater for preference screen. */
+    fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
+        screenUpdaters.add(updater)
+        return this
+    }
+
+    /** Adds a list of updaters for preference screen. */
+    fun addPreferenceScreenUpdater(
+        vararg updaters: PreferenceScreenUpdater,
+    ): PreferenceScreenManager {
+        screenUpdaters.addAll(updaters)
+        return this
+    }
+
+    /** Updates preference screen with registered updaters. */
+    fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
+        for ((key, updater) in updaters) {
+            preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
+        }
+        for (updater in screenUpdaters) {
+            updater.updatePreferenceScreen(preferenceScreen)
+        }
+    }
+}
+
+/** Updater of [Preference]. */
+interface PreferenceUpdater {
+    fun updatePreference(preference: Preference)
+}
+
+/** Updater of [PreferenceScreen]. */
+interface PreferenceScreenUpdater {
+    fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
new file mode 100644
index 0000000..9e4c1f6
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
@@ -0,0 +1,26 @@
+package com.android.settingslib.graph
+
+import android.content.Context
+import androidx.preference.PreferenceScreen
+
+/**
+ * Interface to provide [PreferenceScreen].
+ *
+ * It is expected to be implemented by Activity/Fragment and the implementation needs to use
+ * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
+ * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
+ * `onDestroy`, etc.) are not invoked.
+ */
+interface PreferenceScreenProvider {
+
+    /**
+     * Creates [PreferenceScreen].
+     *
+     * Preference screen creation could happen in background service. The implementation MUST use
+     * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
+     */
+    fun createPreferenceScreen(
+        context: Context,
+        preferenceScreenManager: PreferenceScreenManager,
+    ): PreferenceScreen?
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
new file mode 100644
index 0000000..4ced9f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+    <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+    <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+    <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
new file mode 100644
index 0000000..285ab73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:radius="?android:attr/dialogCornerRadius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
new file mode 100644
index 0000000..e417307
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:topLeftRadius="0dp"
+                android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+                android:topRightRadius="0dp"
+                android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
new file mode 100644
index 0000000..e964657
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:radius="1dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
new file mode 100644
index 0000000..a9d69c2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:topLeftRadius="?android:attr/dialogCornerRadius"
+                android:bottomLeftRadius="0dp"
+                android:topRightRadius="?android:attr/dialogCornerRadius"
+                android:bottomRightRadius="0dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b94e224..46bf494 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -934,6 +934,9 @@
     <!-- Permission required for Cts test - CtsSettingsTestCases -->
     <uses-permission android:name="android.permission.PREPARE_FACTORY_RESET" />
 
+    <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+    <uses-permission android:name="android.permission.SETUP_FSVERITY" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c88c373..5e11e1a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,7 @@
         "SystemUICustomizationTestUtils",
         "androidx.compose.runtime_runtime",
         "kosmos",
+        "androidx.test.rules",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c61002e..c2e4b82 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -793,4 +793,14 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
-}
\ No newline at end of file
+}
+
+flag {
+    name: "register_battery_controller_receivers_in_corestartable"
+    namespace: "systemui"
+    description: "Decide whether to register the receivers in battery controller impl in the BatteryControllerStartable corestartable."
+    bug: "307517093"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
new file mode 100644
index 0000000..ffa2b46
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 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.surfaceeffects.revealeffect
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.RenderEffect
+import androidx.core.graphics.ColorUtils
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.android.systemui.surfaceeffects.utils.MathUtils
+import kotlin.math.max
+import kotlin.math.min
+
+/** Creates a reveal effect with a circular ripple sparkles on top. */
+class RippleRevealEffect(
+    private val config: RippleRevealEffectConfig,
+    private val renderEffectCallback: RenderEffectDrawCallback,
+    private val stateChangedCallback: AnimationStateChangedCallback? = null
+) {
+    private val rippleRevealShader = RippleRevealShader().apply { applyConfig(config) }
+    private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+
+    fun play() {
+        if (animator.isRunning) {
+            return
+        }
+
+        animator.duration = config.duration.toLong()
+        animator.addUpdateListener { updateListener ->
+            val playTime = updateListener.currentPlayTime.toFloat()
+            rippleRevealShader.setTime(playTime * TIME_SCALE_FACTOR)
+
+            // Compute radius.
+            val progress = updateListener.animatedValue as Float
+            val innerRad = MathUtils.lerp(config.innerRadiusStart, config.innerRadiusEnd, progress)
+            val outerRad = MathUtils.lerp(config.outerRadiusStart, config.outerRadiusEnd, progress)
+            rippleRevealShader.setInnerRadius(innerRad)
+            rippleRevealShader.setOuterRadius(outerRad)
+
+            // Compute alphas.
+            val innerAlphaProgress =
+                MathUtils.constrainedMap(
+                    1f,
+                    0f,
+                    config.innerFadeOutStart,
+                    config.duration,
+                    playTime
+                )
+            val outerAlphaProgress =
+                MathUtils.constrainedMap(
+                    1f,
+                    0f,
+                    config.outerFadeOutStart,
+                    config.duration,
+                    playTime
+                )
+            val innerAlpha = MathUtils.lerp(0f, 255f, innerAlphaProgress)
+            val outerAlpha = MathUtils.lerp(0f, 255f, outerAlphaProgress)
+
+            val innerColor = ColorUtils.setAlphaComponent(config.innerColor, innerAlpha.toInt())
+            val outerColor = ColorUtils.setAlphaComponent(config.outerColor, outerAlpha.toInt())
+            rippleRevealShader.setInnerColor(innerColor)
+            rippleRevealShader.setOuterColor(outerColor)
+
+            // Pass in progresses since those functions take in normalized alpha values.
+            rippleRevealShader.setBackgroundAlpha(max(innerAlphaProgress, outerAlphaProgress))
+            rippleRevealShader.setSparkleAlpha(min(innerAlphaProgress, outerAlphaProgress))
+
+            // Trigger draw callback.
+            renderEffectCallback.onDraw(
+                RenderEffect.createRuntimeShaderEffect(
+                    rippleRevealShader,
+                    RippleRevealShader.BACKGROUND_UNIFORM
+                )
+            )
+        }
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    stateChangedCallback?.onAnimationEnd()
+                }
+            }
+        )
+        animator.start()
+        stateChangedCallback?.onAnimationStart()
+    }
+
+    interface AnimationStateChangedCallback {
+        fun onAnimationStart()
+        fun onAnimationEnd()
+    }
+
+    private companion object {
+        private const val TIME_SCALE_FACTOR = 0.00175f
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
new file mode 100644
index 0000000..9675f19
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.surfaceeffects.revealeffect
+
+import android.graphics.Color
+
+/** Defines parameters needed for [RippleRevealEffect]. */
+data class RippleRevealEffectConfig(
+    /** Total duration of the animation. */
+    val duration: Float = 0f,
+    /** Timestamp of when the inner mask starts fade out. (Linear fadeout) */
+    val innerFadeOutStart: Float = 0f,
+    /** Timestamp of when the outer mask starts fade out. (Linear fadeout) */
+    val outerFadeOutStart: Float = 0f,
+    /** Center x position of the effect. */
+    val centerX: Float = 0f,
+    /** Center y position of the effect. */
+    val centerY: Float = 0f,
+    /** Start radius of the inner circle. */
+    val innerRadiusStart: Float = 0f,
+    /** End radius of the inner circle. */
+    val innerRadiusEnd: Float = 0f,
+    /** Start radius of the outer circle. */
+    val outerRadiusStart: Float = 0f,
+    /** End radius of the outer circle. */
+    val outerRadiusEnd: Float = 0f,
+    /**
+     * Pixel density of the display. Do not pass a random value. The value must come from
+     * [context.resources.displayMetrics.density].
+     */
+    val pixelDensity: Float = 1f,
+    /**
+     * The amount the circle masks should be softened. Higher value will make the edge of the circle
+     * mask soft.
+     */
+    val blurAmount: Float = 0f,
+    /** Color of the inner circle mask. */
+    val innerColor: Int = Color.WHITE,
+    /** Color of the outer circle mask. */
+    val outerColor: Int = Color.WHITE,
+    /** Multiplier to make the sparkles visible. */
+    val sparkleStrength: Float = SPARKLE_STRENGTH,
+    /** Size of the sparkle. Expected range [0, 1]. */
+    val sparkleScale: Float = SPARKLE_SCALE
+) {
+    /** Default parameters. */
+    companion object {
+        const val SPARKLE_STRENGTH: Float = 0.3f
+        const val SPARKLE_SCALE: Float = 0.8f
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
new file mode 100644
index 0000000..a3f9795
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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.surfaceeffects.revealeffect
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/** Circular reveal effect with sparkles. */
+class RippleRevealShader : RuntimeShader(SHADER) {
+    // language=AGSL
+    companion object {
+        const val BACKGROUND_UNIFORM = "in_dst"
+        private const val MAIN =
+            """
+            uniform shader ${BACKGROUND_UNIFORM};
+            uniform half in_dstAlpha;
+            uniform half in_time;
+            uniform vec2 in_center;
+            uniform half in_innerRadius;
+            uniform half in_outerRadius;
+            uniform half in_sparkleStrength;
+            uniform half in_blur;
+            uniform half in_pixelDensity;
+            uniform half in_sparkleScale;
+            uniform half in_sparkleAlpha;
+            layout(color) uniform vec4 in_innerColor;
+            layout(color) uniform vec4 in_outerColor;
+
+            vec4 main(vec2 p) {
+                half innerMask = soften(sdCircle(p - in_center, in_innerRadius), in_blur);
+                half outerMask = soften(sdCircle(p - in_center, in_outerRadius), in_blur);
+
+                // Flip it since we are interested in the circle.
+                innerMask = 1.-innerMask;
+                outerMask = 1.-outerMask;
+
+                // Color two circles using the mask.
+                vec4 inColor = vec4(in_innerColor.rgb, 1.) * in_innerColor.a;
+                vec4 outColor = vec4(in_outerColor.rgb, 1.) * in_outerColor.a;
+                vec4 blend = mix(inColor, outColor, innerMask);
+
+                vec4 dst = vec4(in_dst.eval(p).rgb, 1.);
+                dst *= in_dstAlpha;
+
+                blend *= blend.a;
+                // Do normal blend with the background.
+                blend = blend + dst * (1. - blend.a);
+
+                half sparkle =
+                    sparkles(p - mod(p, in_pixelDensity * in_sparkleScale), in_time);
+                // Add sparkles using additive blending.
+                blend += sparkle * in_sparkleStrength * in_sparkleAlpha;
+
+                // Mask everything at the end.
+                blend *= outerMask;
+
+                return blend;
+            }
+        """
+
+        private const val SHADER =
+            ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.CIRCLE_SDF +
+                MAIN
+    }
+
+    fun applyConfig(config: RippleRevealEffectConfig) {
+        setCenter(config.centerX, config.centerY)
+        setInnerRadius(config.innerRadiusStart)
+        setOuterRadius(config.outerRadiusStart)
+        setBlurAmount(config.blurAmount)
+        setPixelDensity(config.pixelDensity)
+        setSparkleScale(config.sparkleScale)
+        setSparkleStrength(config.sparkleStrength)
+        setInnerColor(config.innerColor)
+        setOuterColor(config.outerColor)
+    }
+
+    fun setTime(time: Float) {
+        setFloatUniform("in_time", time)
+    }
+
+    fun setCenter(centerX: Float, centerY: Float) {
+        setFloatUniform("in_center", centerX, centerY)
+    }
+
+    fun setInnerRadius(radius: Float) {
+        setFloatUniform("in_innerRadius", radius)
+    }
+
+    fun setOuterRadius(radius: Float) {
+        setFloatUniform("in_outerRadius", radius)
+    }
+
+    fun setBlurAmount(blurAmount: Float) {
+        setFloatUniform("in_blur", blurAmount)
+    }
+
+    fun setPixelDensity(density: Float) {
+        setFloatUniform("in_pixelDensity", density)
+    }
+
+    fun setSparkleScale(scale: Float) {
+        setFloatUniform("in_sparkleScale", scale)
+    }
+
+    fun setSparkleStrength(strength: Float) {
+        setFloatUniform("in_sparkleStrength", strength)
+    }
+
+    fun setInnerColor(color: Int) {
+        setColorUniform("in_innerColor", color)
+    }
+
+    fun setOuterColor(color: Int) {
+        setColorUniform("in_outerColor", color)
+    }
+
+    /** Sets the background alpha. Range [0,1]. */
+    fun setBackgroundAlpha(alpha: Float) {
+        setFloatUniform("in_dstAlpha", alpha)
+    }
+
+    /** Sets the sparkle alpha. Range [0,1]. */
+    fun setSparkleAlpha(alpha: Float) {
+        setFloatUniform("in_sparkleAlpha", alpha)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
new file mode 100644
index 0000000..1411c32
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.surfaceeffects.utils
+
+/** Copied from android.utils.MathUtils */
+object MathUtils {
+    fun constrainedMap(
+        rangeMin: Float,
+        rangeMax: Float,
+        valueMin: Float,
+        valueMax: Float,
+        value: Float
+    ): Float {
+        return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value))
+    }
+
+    fun lerp(start: Float, stop: Float, amount: Float): Float {
+        return start + (stop - start) * amount
+    }
+
+    fun lerpInv(a: Float, b: Float, value: Float): Float {
+        return if (a != b) (value - a) / (b - a) else 0.0f
+    }
+
+    fun saturate(value: Float): Float {
+        return constrain(value, 0.0f, 1.0f)
+    }
+
+    fun lerpInvSat(a: Float, b: Float, value: Float): Float {
+        return saturate(lerpInv(a, b, value))
+    }
+
+    fun constrain(amount: Float, low: Float, high: Float): Float {
+        return if (amount < low) low else if (amount > high) high else amount
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 94515d3..ddfb5f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1141,7 +1141,7 @@
     val CardHeightHalf = 282.dp
     val CardHeightThird = 177.33.dp
     val CardOutlineWidth = 3.dp
-    val GridTopSpacing = 72.dp
+    val GridTopSpacing = 64.dp
     val GridHeight = CardHeightFull + GridTopSpacing
     val Spacing = 16.dp
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index dc3b612..418c6bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -44,10 +44,27 @@
     internal val scope = SceneScopeImpl(layoutImpl, this)
 
     var content by mutableStateOf(content)
-    var userActions by mutableStateOf(actions)
+    private var _userActions by mutableStateOf(checkValid(actions))
     var zIndex by mutableFloatStateOf(zIndex)
     var targetSize by mutableStateOf(IntSize.Zero)
 
+    var userActions
+        get() = _userActions
+        set(value) {
+            _userActions = checkValid(value)
+        }
+
+    private fun checkValid(
+        userActions: Map<UserAction, UserActionResult>
+    ): Map<UserAction, UserActionResult> {
+        userActions.forEach { (action, result) ->
+            if (key == result.toScene) {
+                error("Transition to the same scene is not supported. Scene $key, action $action")
+            }
+        }
+        return userActions
+    }
+
     @Composable
     @OptIn(ExperimentalComposeUiApi::class)
     fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 723a182..2eaccb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -48,10 +48,14 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,7 +66,7 @@
         private val LayoutSize = 300.dp
     }
 
-    private var currentScene by mutableStateOf(TestScenes.SceneA)
+    private var currentScene by mutableStateOf(SceneA)
     private lateinit var layoutState: SceneTransitionLayoutState
 
     // We use createAndroidComposeRule() here and not createComposeRule() because we need an
@@ -84,15 +88,15 @@
             modifier = Modifier.size(LayoutSize),
         ) {
             scene(
-                TestScenes.SceneA,
-                userActions = mapOf(Back to TestScenes.SceneB),
+                SceneA,
+                userActions = mapOf(Back to SceneB),
             ) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
                     Text("SceneA")
                 }
             }
-            scene(TestScenes.SceneB) {
+            scene(SceneB) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(
                         size = 100.dp,
@@ -102,7 +106,7 @@
                     Text("SceneB")
                 }
             }
-            scene(TestScenes.SceneC) {
+            scene(SceneC) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(
                         size = 150.dp,
@@ -144,42 +148,42 @@
         rule.onNodeWithText("SceneB").assertDoesNotExist()
         rule.onNodeWithText("SceneC").assertDoesNotExist()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         // Change to scene B. Only that scene is displayed.
-        currentScene = TestScenes.SceneB
+        currentScene = SceneB
         rule.onNodeWithText("SceneA").assertDoesNotExist()
         rule.onNodeWithText("SceneB").assertIsDisplayed()
         rule.onNodeWithText("SceneC").assertDoesNotExist()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
     }
 
     @Test
     fun testBack() {
         rule.setContent { TestContent() }
 
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         rule.activity.onBackPressed()
         rule.waitForIdle()
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
     }
 
     @Test
     fun testTransitionState() {
         rule.setContent { TestContent() }
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         // We will advance the clock manually.
         rule.mainClock.autoAdvance = false
 
         // Change the current scene. Until composition is triggered, this won't change the layout
         // state.
-        currentScene = TestScenes.SceneB
+        currentScene = SceneB
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         // On the next frame, we will recompose because currentScene changed, which will start the
         // transition (i.e. it will change the transitionState to be a Transition) in a
@@ -187,8 +191,8 @@
         rule.mainClock.advanceTimeByFrame()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
         val transition = layoutState.transitionState as TransitionState.Transition
-        assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.fromScene).isEqualTo(SceneA)
+        assertThat(transition.toScene).isEqualTo(SceneB)
         assertThat(transition.progress).isEqualTo(0f)
 
         // Then, on the next frame, the animator we started gets its initial value and clock
@@ -216,7 +220,7 @@
         // B.
         rule.mainClock.advanceTimeByFrame()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
     }
 
     @Test
@@ -242,7 +246,7 @@
         // Go to scene B and let the animation start. See [testLayoutState()] and
         // [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
         // by 2 frames to be at the start of the animation.
-        currentScene = TestScenes.SceneB
+        currentScene = SceneB
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
 
@@ -251,7 +255,7 @@
 
         // Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
         // that B has a higher zIndex than A.
-        sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
+        sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneB))
 
         // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -273,7 +277,7 @@
             .of(DpOffset(25.dp, 25.dp))
 
         // Animate to scene C, let the animation start then go to the middle of the transition.
-        currentScene = TestScenes.SceneC
+        currentScene = SceneC
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -285,7 +289,7 @@
         val expectedLeft = 0.dp
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
-        sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
+        sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
         assertThat((layoutState.transitionState as TransitionState.Transition).progress)
             .isEqualTo(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
@@ -302,15 +306,15 @@
         // Wait for the transition to C to finish.
         rule.mainClock.advanceTimeBy(TestTransitionDuration)
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
 
         // Go back to scene A. This should happen instantly (once the animation started, i.e. after
         // 2 frames) given that we use a snap() animation spec.
-        currentScene = TestScenes.SceneA
+        currentScene = SceneA
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
     }
 
     @Test
@@ -346,4 +350,28 @@
             )
         }
     }
+
+    @Test
+    fun userActionFromSceneAToSceneA_throwsNotSupported() {
+        val exception: IllegalStateException =
+            assertThrows(IllegalStateException::class.java) {
+                rule.setContent {
+                    SceneTransitionLayout(
+                        state =
+                            updateSceneTransitionLayoutState(
+                                currentScene = currentScene,
+                                onChangeScene = { currentScene = it },
+                                transitions = EmptyTestTransitions
+                            ),
+                        modifier = Modifier.size(LayoutSize),
+                    ) {
+                        // from SceneA to SceneA
+                        scene(SceneA, userActions = mapOf(Back to SceneA), content = {})
+                    }
+                }
+            }
+
+        assertThat(exception).hasMessageThat().contains(Back.toString())
+        assertThat(exception).hasMessageThat().contains(SceneA.debugName)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 3889703..e332656 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -18,9 +18,6 @@
 
 import android.os.VibrationEffect
 import android.testing.TestableLooper.RunWithLooper
-import android.view.MotionEvent
-import android.view.View
-import androidx.test.core.view.MotionEventBuilder
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -29,18 +26,15 @@
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
@@ -50,7 +44,6 @@
 class QSLongPressEffectTest : SysuiTestCase() {
 
     @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
-    @Mock private lateinit var testView: View
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
     private val kosmos = testKosmos()
     private val vibratorHelper = kosmos.vibratorHelper
@@ -73,7 +66,6 @@
             QSLongPressEffect(
                 vibratorHelper,
                 kosmos.keyguardInteractor,
-                CoroutineScope(kosmos.backgroundCoroutineContext),
             )
         longPressEffect.initializeEffect(effectDuration)
     }
@@ -133,8 +125,7 @@
     @Test
     fun onActionDown_whileIdle_startsWait() = testWithScope {
         // GIVEN an action down event occurs
-        val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
-        longPressEffect.onTouch(testView, downEvent)
+        longPressEffect.handleActionDown()
 
         // THEN the effect moves to the TIMEOUT_WAIT state
         val state by collectLastValue(longPressEffect.state)
@@ -144,8 +135,7 @@
     @Test
     fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
         // GIVEN an action cancel occurs
-        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
-        longPressEffect.onTouch(testView, cancelEvent)
+        longPressEffect.handleActionCancel()
 
         // THEN the effect goes back to idle and does not start
         val state by collectLastValue(longPressEffect.state)
@@ -159,8 +149,7 @@
         val action by collectLastValue(longPressEffect.actionType)
 
         // GIVEN an action up occurs
-        val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
-        longPressEffect.onTouch(testView, upEvent)
+        longPressEffect.handleActionUp()
 
         // THEN the action to invoke is the click action and the effect does not start
         assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
@@ -182,8 +171,7 @@
         animatorTestRule.advanceTimeBy(effectDuration / 2L)
 
         // WHEN an action up occurs
-        val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
-        longPressEffect.onTouch(testView, upEvent)
+        longPressEffect.handleActionUp()
 
         // THEN the effect gets reversed at 50% progress
         assertEffectReverses(0.5f)
@@ -195,8 +183,7 @@
         animatorTestRule.advanceTimeBy(effectDuration / 2L)
 
         // WHEN an action cancel occurs
-        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
-        longPressEffect.onTouch(testView, cancelEvent)
+        longPressEffect.handleActionCancel()
 
         // THEN the effect gets reversed at 50% progress
         assertEffectReverses(0.5f)
@@ -230,12 +217,10 @@
         animatorTestRule.advanceTimeBy(effectDuration / 2L)
 
         // GIVEN an action cancel occurs and the effect gets reversed
-        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
-        longPressEffect.onTouch(testView, cancelEvent)
+        longPressEffect.handleActionCancel()
 
         // GIVEN an action down occurs
-        val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
-        longPressEffect.onTouch(testView, downEvent)
+        longPressEffect.handleActionDown()
 
         // THEN the effect resets
         assertEffectResets()
@@ -247,8 +232,7 @@
         animatorTestRule.advanceTimeBy(effectDuration / 2L)
 
         // GIVEN an action cancel occurs and the effect gets reversed
-        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
-        longPressEffect.onTouch(testView, cancelEvent)
+        longPressEffect.handleActionCancel()
 
         // GIVEN that the animation completes after a sufficient amount of time
         animatorTestRule.advanceTimeBy(effectDuration.toLong())
@@ -258,9 +242,6 @@
         assertThat(state).isEqualTo(QSLongPressEffect.State.IDLE)
     }
 
-    private fun buildMotionEvent(action: Int): MotionEvent =
-        MotionEventBuilder.newBuilder().setAction(action).build()
-
     private fun testWithScope(test: suspend TestScope.() -> Unit) =
         with(kosmos) { testScope.runTest { test() } }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 106b548..97c8d5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index d06a6e2..a6fdd03 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -16,24 +16,28 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
-import android.net.ConnectivityManager
 import android.net.wifi.WifiManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.WifiPickerTracker
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,14 +63,21 @@
     private lateinit var demoImpl: DemoWifiRepository
 
     @Mock private lateinit var demoModeController: DemoModeController
-    @Mock private lateinit var logger: WifiInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
-    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
     @Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+    @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+    @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+
+    private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
     private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
     private val mainExecutor = FakeExecutor(FakeSystemClock())
+    private val featureFlags =
+        FakeFeatureFlagsClassic().also {
+            it.set(Flags.INSTANT_TETHER, true)
+            it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+        }
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -78,17 +89,18 @@
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
 
+        whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+
         realImpl =
             WifiRepositoryImpl(
-                fakeBroadcastDispatcher,
-                connectivityManager,
-                FakeConnectivityRepository(),
-                logger,
-                tableLogger,
+                featureFlags,
+                testScope.backgroundScope,
                 mainExecutor,
                 testDispatcher,
-                testScope.backgroundScope,
+                wifiPickerTrackerFactory,
                 wifiManager,
+                wifiLogBuffer,
+                tableLogger,
             )
 
         whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
deleted file mode 100644
index cf20ba8..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ /dev/null
@@ -1,1323 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
-
-import android.content.Intent
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_VPN
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.TransportInfo
-import android.net.VpnTransportInfo
-import android.net.vcn.VcnTransportInfo
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiInfo
-import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
-import android.net.wifi.WifiManager.UNKNOWN_SSID
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class WifiRepositoryImplTest : SysuiTestCase() {
-
-    private lateinit var underTest: WifiRepositoryImpl
-
-    @Mock private lateinit var logger: WifiInputLogger
-    @Mock private lateinit var tableLogger: TableLogBuffer
-    @Mock private lateinit var connectivityManager: ConnectivityManager
-    @Mock private lateinit var wifiManager: WifiManager
-    private lateinit var executor: Executor
-    private lateinit var connectivityRepository: ConnectivityRepository
-
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        executor = FakeExecutor(FakeSystemClock())
-
-        connectivityRepository =
-            ConnectivityRepositoryImpl(
-                connectivityManager,
-                ConnectivitySlots(context),
-                context,
-                mock(),
-                mock(),
-                testScope.backgroundScope,
-                mock(),
-            )
-
-        underTest = createRepo()
-    }
-
-    @Test
-    fun isWifiEnabled_initiallyGetsWifiManagerValue() =
-        testScope.runTest {
-            whenever(wifiManager.isWifiEnabled).thenReturn(true)
-
-            underTest = createRepo()
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiEnabled.value).isTrue()
-        }
-
-    @Test
-    fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-            val latest by collectLastValue(underTest.isWifiEnabled)
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
-            assertThat(latest).isTrue()
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
-            assertThat(latest).isFalse()
-        }
-
-    @Test
-    fun isWifiEnabled_networkLost_valueUpdated() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-            val latest by collectLastValue(underTest.isWifiEnabled)
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            getNetworkCallback().onLost(NETWORK)
-
-            assertThat(latest).isTrue()
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            getNetworkCallback().onLost(NETWORK)
-
-            assertThat(latest).isFalse()
-        }
-
-    @Test
-    fun isWifiEnabled_intentsReceived_valueUpdated() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiEnabled)
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
-            )
-
-            assertThat(latest).isTrue()
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
-            )
-
-            assertThat(latest).isFalse()
-        }
-
-    @Test
-    fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-            val latest by collectLastValue(underTest.isWifiEnabled)
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
-            )
-            assertThat(latest).isFalse()
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            getNetworkCallback().onLost(NETWORK)
-            assertThat(latest).isTrue()
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat(latest).isFalse()
-
-            whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
-            )
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_initiallyGetsDefault() =
-        testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
-
-    @Test
-    fun isWifiDefault_wifiNetwork_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
-
-            getDefaultNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
-            assertThat(latest).isTrue()
-        }
-
-    /** Regression test for b/266628069. */
-    @Test
-    fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val transportInfo =
-                VpnTransportInfo(
-                    /* type= */ 0,
-                    /* sessionId= */ "sessionId",
-                )
-            val networkCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
-                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
-                    whenever(it.transportInfo).thenReturn(transportInfo)
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
-            assertThat(latest).isFalse()
-        }
-
-    /** Regression test for b/266628069. */
-    @Test
-    fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val transportInfo =
-                VpnTransportInfo(
-                    /* type= */ 0,
-                    /* sessionId= */ "sessionId",
-                )
-            val networkCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
-                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
-                    whenever(it.transportInfo).thenReturn(transportInfo)
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_carrierMergedViaCellular_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
-                    whenever(this.transportInfo).thenReturn(carrierMergedInfo)
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
-                    whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_carrierMergedViaWifi_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
-                    whenever(this.transportInfo).thenReturn(carrierMergedInfo)
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
-                    whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.transportInfo).thenReturn(mock())
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest).isFalse()
-        }
-
-    @Test
-    fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val underlyingNetwork = mock<Network>()
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply {
-                    mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-                }
-            val underlyingWifiCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
-                }
-            whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
-                .thenReturn(underlyingWifiCapabilities)
-
-            // WHEN the main capabilities have an underlying carrier merged network via WIFI
-            // transport and WifiInfo
-            val mainCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(null)
-                    whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
-            // THEN the wifi network is carrier merged, so wifi is default
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            val underlyingCarrierMergedNetwork = mock<Network>()
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-            val underlyingCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
-                }
-            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
-                .thenReturn(underlyingCapabilities)
-
-            // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
-            // transport and VcnTransportInfo
-            val mainCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(null)
-                    whenever(it.underlyingNetworks)
-                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
-                }
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
-            // THEN the wifi network is carrier merged, so wifi is default
-            assertThat(latest).isTrue()
-        }
-
-    @Test
-    fun isWifiDefault_wifiNetworkLost_isFalse() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.isWifiDefault)
-
-            // First, add a network
-            getDefaultNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat(latest).isTrue()
-
-            // WHEN the network is lost
-            getDefaultNetworkCallback().onLost(NETWORK)
-
-            // THEN we update to false
-            assertThat(latest).isFalse()
-        }
-
-    @Test
-    fun wifiNetwork_initiallyGetsDefault() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
-        }
-
-    @Test
-    fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestActive.ssid).isEqualTo(SSID)
-        }
-
-    @Test
-    fun wifiNetwork_neverHasHotspot() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
-                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
-        }
-
-    @Test
-    fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
-            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val underlyingNetwork = mock<Network>()
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-            val underlyingWifiCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
-                }
-            whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
-                .thenReturn(underlyingWifiCapabilities)
-
-            // WHEN the main capabilities have an underlying carrier merged network via WIFI
-            // transport and WifiInfo
-            val mainCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(null)
-                    whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
-                }
-
-            getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
-            // THEN the wifi network is carrier merged
-            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val underlyingCarrierMergedNetwork = mock<Network>()
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-            val underlyingCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
-                }
-            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
-                .thenReturn(underlyingCapabilities)
-
-            // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
-            // transport and VcnTransportInfo
-            val mainCapabilities =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(null)
-                    whenever(it.underlyingNetworks)
-                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
-                }
-
-            getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
-            // THEN the wifi network is carrier merged
-            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                    whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(wifiInfo),
-                )
-
-            assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
-        }
-
-    @Test
-    fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val rssi = -57
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                    whenever(this.rssi).thenReturn(rssi)
-                    whenever(this.subscriptionId).thenReturn(567)
-                }
-
-            whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
-            whenever(wifiManager.maxSignalLevel).thenReturn(5)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(wifiInfo),
-                )
-
-            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-            val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
-            assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567)
-            assertThat(latestCarrierMerged.level).isEqualTo(2)
-            // numberOfLevels = maxSignalLevel + 1
-            assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
-        }
-
-    @Test
-    fun wifiNetwork_notValidated_networkNotValidated() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
-                )
-
-            assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
-        }
-
-    @Test
-    fun wifiNetwork_validated_networkValidated() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
-                )
-
-            assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    whenever(this.isPrimary).thenReturn(false)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    /** Regression test for b/266628069. */
-    @Test
-    fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val transportInfo =
-                VpnTransportInfo(
-                    /* type= */ 0,
-                    /* sessionId= */ "sessionId",
-                )
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
-
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
-                }
-
-            getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestActive.ssid).isEqualTo(SSID)
-        }
-
-    @Test
-    fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    whenever(this.isPrimary).thenReturn(false)
-                }
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
-                }
-
-            getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.transportInfo).thenReturn(mock())
-                }
-
-            getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val capabilities =
-                mock<NetworkCapabilities>().apply {
-                    whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
-                }
-
-            getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestActive.ssid).isEqualTo(SSID)
-        }
-
-    @Test
-    fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            // Start with the original network
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
-            // WHEN we update to a new primary network
-            val newNetworkId = 456
-            val newNetwork =
-                mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
-            val newSsid = "CD"
-            val newWifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(newSsid)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
-            // THEN we use the new network
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(newNetworkId)
-            assertThat(latestActive.ssid).isEqualTo(newSsid)
-        }
-
-    @Test
-    fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            // Start with the original network
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
-            // WHEN we notify of a new but non-primary network
-            val newNetworkId = 456
-            val newNetwork =
-                mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
-            val newSsid = "EF"
-            val newWifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(newSsid)
-                    whenever(this.isPrimary).thenReturn(false)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
-            // THEN we still use the original network
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestActive.ssid).isEqualTo(SSID)
-        }
-
-    @Test
-    fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-
-            // Start with the original network
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(wifiInfo, isValidated = true)
-                )
-
-            // WHEN we keep the same network ID but change the SSID
-            val newSsid = "CD"
-            val newWifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(newSsid)
-                    whenever(this.isPrimary).thenReturn(true)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
-                )
-
-            // THEN we've updated to the new SSID
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestActive.ssid).isEqualTo(newSsid)
-            assertThat(latestActive.isValidated).isFalse()
-        }
-
-    @Test
-    fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
-            getNetworkCallback().onLost(NETWORK)
-
-            // THEN there's no crash and we still have no network
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
-            // WHEN we lose our current network
-            getNetworkCallback().onLost(NETWORK)
-
-            // THEN we update to no network
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    /** Possible regression test for b/278618530. */
-    @Test
-    fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-            assertThat((latest as WifiNetworkModel.CarrierMerged).networkId).isEqualTo(NETWORK_ID)
-
-            // WHEN we lose our current network
-            getNetworkCallback().onLost(NETWORK)
-
-            // THEN we update to no network
-            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-        }
-
-    @Test
-    fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
-            // WHEN we lose an unknown network
-            val unknownNetwork = mock<Network>().apply { whenever(this.getNetId()).thenReturn(543) }
-            getNetworkCallback().onLost(unknownNetwork)
-
-            // THEN we still have our previous network
-            assertThat(latest is WifiNetworkModel.Active).isTrue()
-            val latestActive = latest as WifiNetworkModel.Active
-            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latestActive.ssid).isEqualTo(SSID)
-        }
-
-    @Test
-    fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
-            // WHEN we update to a new network...
-            val newNetworkId = 89
-            val newNetwork =
-                mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
-            getNetworkCallback()
-                .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            // ...and lose the old network
-            getNetworkCallback().onLost(NETWORK)
-
-            // THEN we still have the new network
-            assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
-        }
-
-    /** Regression test for b/244173280. */
-    @Test
-    fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
-        testScope.runTest {
-            val latest1 by collectLastValue(underTest.wifiNetwork)
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
-            assertThat(latest1 is WifiNetworkModel.Active).isTrue()
-            val latest1Active = latest1 as WifiNetworkModel.Active
-            assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latest1Active.ssid).isEqualTo(SSID)
-
-            // WHEN we add a second subscriber after having already emitted a value
-            val latest2 by collectLastValue(underTest.wifiNetwork)
-
-            // THEN the second subscribe receives the already-emitted value
-            assertThat(latest2 is WifiNetworkModel.Active).isTrue()
-            val latest2Active = latest2 as WifiNetworkModel.Active
-            assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
-            assertThat(latest2Active.ssid).isEqualTo(SSID)
-        }
-
-    @Test
-    fun secondaryNetworks_alwaysEmpty() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.secondaryNetworks)
-            collectLastValue(underTest.wifiNetwork)
-
-            // Even WHEN we do have non-primary wifi info
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    whenever(this.isPrimary).thenReturn(false)
-                }
-            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
-            // THEN the secondary networks list is empty because this repo doesn't support it
-            assertThat(latest).isEmpty()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.ssid).thenReturn(SSID)
-                    // A non-primary network is inactive
-                    whenever(this.isPrimary).thenReturn(false)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
-                    whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(wifiInfo),
-                )
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.ssid).thenReturn(null)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.ssid).thenReturn(UNKNOWN_SSID)
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.ssid).thenReturn("FakeSsid")
-                }
-
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-        }
-
-    @Test
-    fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
-        testScope.runTest {
-            collectLastValue(underTest.wifiNetwork)
-
-            // Start with active
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.ssid).thenReturn("FakeSsid")
-                }
-            getNetworkCallback()
-                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-            testScope.runCurrent()
-
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-
-            // WHEN the network is lost
-            getNetworkCallback().onLost(NETWORK)
-            testScope.runCurrent()
-
-            // THEN the isWifiConnected updates
-            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-        }
-
-    @Test
-    fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiActivity)
-
-            getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
-
-            assertThat(latest)
-                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
-        }
-
-    @Test
-    fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiActivity)
-
-            getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
-
-            assertThat(latest)
-                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
-        }
-
-    @Test
-    fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiActivity)
-
-            getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
-
-            assertThat(latest)
-                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
-        }
-
-    @Test
-    fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiActivity)
-
-            getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
-
-            assertThat(latest)
-                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
-        }
-
-    @Test
-    fun wifiScanResults_containsSsidList() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiScanResults)
-
-            val scanResults =
-                listOf(
-                    ScanResult().also { it.SSID = "ssid 1" },
-                    ScanResult().also { it.SSID = "ssid 2" },
-                    ScanResult().also { it.SSID = "ssid 3" },
-                    ScanResult().also { it.SSID = "ssid 4" },
-                    ScanResult().also { it.SSID = "ssid 5" },
-                )
-            whenever(wifiManager.scanResults).thenReturn(scanResults)
-            getScanResultsCallback().onScanResultsAvailable()
-
-            val expected =
-                listOf(
-                    WifiScanEntry(ssid = "ssid 1"),
-                    WifiScanEntry(ssid = "ssid 2"),
-                    WifiScanEntry(ssid = "ssid 3"),
-                    WifiScanEntry(ssid = "ssid 4"),
-                    WifiScanEntry(ssid = "ssid 5"),
-                )
-
-            assertThat(latest).isEqualTo(expected)
-        }
-
-    @Test
-    fun wifiScanResults_updates() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.wifiScanResults)
-
-            var scanResults =
-                listOf(
-                    ScanResult().also { it.SSID = "ssid 1" },
-                    ScanResult().also { it.SSID = "ssid 2" },
-                    ScanResult().also { it.SSID = "ssid 3" },
-                    ScanResult().also { it.SSID = "ssid 4" },
-                    ScanResult().also { it.SSID = "ssid 5" },
-                )
-            whenever(wifiManager.scanResults).thenReturn(scanResults)
-            getScanResultsCallback().onScanResultsAvailable()
-
-            // New scan representing no results
-            scanResults = emptyList()
-            whenever(wifiManager.scanResults).thenReturn(scanResults)
-            getScanResultsCallback().onScanResultsAvailable()
-
-            assertThat(latest).isEmpty()
-        }
-
-    private fun createRepo(): WifiRepositoryImpl {
-        return WifiRepositoryImpl(
-            fakeBroadcastDispatcher,
-            connectivityManager,
-            connectivityRepository,
-            logger,
-            tableLogger,
-            executor,
-            dispatcher,
-            testScope.backgroundScope,
-            wifiManager,
-        )
-    }
-
-    private fun getTrafficStateCallback(): TrafficStateCallback {
-        testScope.runCurrent()
-        val callbackCaptor = argumentCaptor<TrafficStateCallback>()
-        verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
-    }
-
-    private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
-        testScope.runCurrent()
-        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
-        verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
-    }
-
-    private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
-        testScope.runCurrent()
-        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
-        verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
-        return callbackCaptor.value!!
-    }
-
-    private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
-        testScope.runCurrent()
-        val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
-        verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
-    }
-
-    private fun createWifiNetworkCapabilities(
-        transportInfo: TransportInfo,
-        isValidated: Boolean = true,
-    ): NetworkCapabilities {
-        return mock<NetworkCapabilities>().also {
-            whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-            whenever(it.transportInfo).thenReturn(transportInfo)
-            whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
-        }
-    }
-
-    private companion object {
-        const val NETWORK_ID = 45
-        val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-        const val SSID = "AB"
-        val PRIMARY_WIFI_INFO: WifiInfo =
-            mock<WifiInfo>().apply {
-                whenever(this.ssid).thenReturn(SSID)
-                whenever(this.isPrimary).thenReturn(true)
-            }
-    }
-}
diff --git a/packages/SystemUI/res/color/notification_focus_overlay_color.xml b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
new file mode 100644
index 0000000..6a3c7a1
--- /dev/null
+++ b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_focused="true" android:color="?androidprv:attr/materialColorSecondary" />
+    <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 3f903ae..587a5a0 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -28,4 +28,9 @@
             <solid android:color="@color/notification_state_color_default" />
         </shape>
     </item>
+    <item>
+        <shape>
+            <stroke android:width="3dp" android:color="@color/notification_focus_overlay_color"/>
+        </shape>
+    </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt
new file mode 100644
index 0000000..349964b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.dagger.DeviceEntryIconLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val GENERIC_TAG = "DeviceEntryIconLogger"
+
+/** Helper class for logging for the DeviceEntryIcon */
+@SysUISingleton
+class DeviceEntryIconLogger
+@Inject
+constructor(@DeviceEntryIconLog private val logBuffer: LogBuffer) {
+    fun i(@CompileTimeConstant msg: String) = log(msg, INFO)
+    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+    fun log(@CompileTimeConstant msg: String, level: LogLevel) =
+        logBuffer.log(GENERIC_TAG, level, msg)
+
+    fun logDeviceEntryUdfpsTouchOverlayShouldHandleTouches(
+        shouldHandleTouches: Boolean,
+        canTouchDeviceEntryViewAlpha: Boolean,
+        alternateBouncerVisible: Boolean,
+        hideAffordancesRequest: Boolean,
+    ) {
+        logBuffer.log(
+            "DeviceEntryUdfpsTouchOverlay",
+            DEBUG,
+            {
+                bool1 = canTouchDeviceEntryViewAlpha
+                bool2 = alternateBouncerVisible
+                bool3 = hideAffordancesRequest
+                bool4 = shouldHandleTouches
+            },
+            {
+                "shouldHandleTouches=$bool4 canTouchDeviceEntryViewAlpha=$bool1 " +
+                    "alternateBouncerVisible=$bool2 hideAffordancesRequest=$bool3"
+            }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index 2797b7b..07e30ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import com.android.keyguard.logging.DeviceEntryIconLogger
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -24,6 +25,8 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 
 /**
  * View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
@@ -37,16 +40,30 @@
     deviceEntryIconViewModel: DeviceEntryIconViewModel,
     alternateBouncerInteractor: AlternateBouncerInteractor,
     systemUIDialogManager: SystemUIDialogManager,
+    logger: DeviceEntryIconLogger,
 ) : UdfpsTouchOverlayViewModel {
+    private val deviceEntryViewAlphaIsMostlyVisible: Flow<Boolean> =
+        deviceEntryIconViewModel.deviceEntryViewAlpha
+            .map { it > ALLOW_TOUCH_ALPHA_THRESHOLD }
+            .distinctUntilChanged()
     override val shouldHandleTouches: Flow<Boolean> =
         combine(
-            deviceEntryIconViewModel.deviceEntryViewAlpha,
-            alternateBouncerInteractor.isVisible,
-            systemUIDialogManager.hideAffordancesRequest
-        ) { deviceEntryViewAlpha, alternateBouncerVisible, hideAffordancesRequest ->
-            (deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !hideAffordancesRequest) ||
-                alternateBouncerVisible
-        }
+                deviceEntryViewAlphaIsMostlyVisible,
+                alternateBouncerInteractor.isVisible,
+                systemUIDialogManager.hideAffordancesRequest,
+            ) { canTouchDeviceEntryViewAlpha, alternateBouncerVisible, hideAffordancesRequest ->
+                val shouldHandleTouches =
+                    (canTouchDeviceEntryViewAlpha && !hideAffordancesRequest) ||
+                        alternateBouncerVisible
+                logger.logDeviceEntryUdfpsTouchOverlayShouldHandleTouches(
+                    shouldHandleTouches,
+                    canTouchDeviceEntryViewAlpha,
+                    alternateBouncerVisible,
+                    hideAffordancesRequest
+                )
+                shouldHandleTouches
+            }
+            .distinctUntilChanged()
 
     companion object {
         // only allow touches if the view is still mostly visible
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 21ee5bd..23fc8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
+import com.android.systemui.statusbar.policy.BatteryControllerStartable
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
@@ -343,4 +344,10 @@
     @IntoMap
     @ClassKey(HomeControlsDreamStartable::class)
     abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
+
+    /** Binds {@link BatteryControllerStartable} as a {@link CoreStartable}. */
+    @Binds
+    @IntoMap
+    @ClassKey(BatteryControllerStartable::class)
+    abstract fun bindsBatteryControllerStartable(impl: BatteryControllerStartable): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 9ada1ef..04f1ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -255,9 +255,6 @@
     val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS =
         releasedFlag("filter_provisioning_network_subscriptions")
 
-    // TODO(b/292533677): Tracking Bug
-    val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
-
     // TODO(b/293863612): Tracking Bug
     @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
         releasedFlag("incompatible_charging_battery_icon")
@@ -477,12 +474,6 @@
     @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
             unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
 
-    // 2900 - CentralSurfaces-related flags
-
-    // TODO(b/285174336): Tracking Bug
-    @JvmField
-    val USE_REPOS_FOR_BOUNCER_SHOWING = releasedFlag("use_repos_for_bouncer_showing")
-
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
     val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 4327d18..bccc3c5 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,9 +17,7 @@
 package com.android.systemui.haptics.qs
 
 import android.animation.ValueAnimator
-import android.annotation.SuppressLint
 import android.os.VibrationEffect
-import android.view.MotionEvent
 import android.view.View
 import android.view.ViewConfiguration
 import android.view.animation.AccelerateDecelerateInterpolator
@@ -27,18 +25,14 @@
 import androidx.core.animation.doOnCancel
 import androidx.core.animation.doOnEnd
 import androidx.core.animation.doOnStart
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 /**
  * A class that handles the long press visuo-haptic effect for a QS tile.
@@ -55,34 +49,32 @@
 @Inject
 constructor(
     private val vibratorHelper: VibratorHelper?,
-    val keyguardInteractor: KeyguardInteractor,
-    @Background bgScope: CoroutineScope,
-) : View.OnTouchListener {
+    keyguardInteractor: KeyguardInteractor,
+) {
 
     private var effectDuration = 0
 
     /** Current state */
     private var _state = MutableStateFlow(State.IDLE)
-    val state = _state.stateIn(bgScope, SharingStarted.Lazily, State.IDLE)
+    val state = _state.asStateFlow()
 
     /** Flows for view control and action */
     private val _effectProgress = MutableStateFlow<Float?>(null)
-    val effectProgress = _effectProgress.stateIn(bgScope, SharingStarted.Lazily, null)
+    val effectProgress = _effectProgress.asStateFlow()
 
     // Actions to perform
     private val _postedActionType = MutableStateFlow<ActionType?>(null)
-    val actionType: StateFlow<ActionType?> =
+    val actionType: Flow<ActionType?> =
         combine(
-                _postedActionType,
-                keyguardInteractor.isKeyguardDismissible,
-            ) { action, isDismissible ->
-                if (!isDismissible && action == ActionType.LONG_PRESS) {
-                    ActionType.RESET_AND_LONG_PRESS
-                } else {
-                    action
-                }
+            _postedActionType,
+            keyguardInteractor.isKeyguardDismissible,
+        ) { action, isDismissible ->
+            if (!isDismissible && action == ActionType.LONG_PRESS) {
+                ActionType.RESET_AND_LONG_PRESS
+            } else {
+                action
             }
-            .stateIn(bgScope, SharingStarted.Lazily, null)
+        }
 
     // Should a tap timeout countdown begin
     val shouldWaitForTapTimeout: Flow<Boolean> = state.map { it == State.TIMEOUT_WAIT }
@@ -129,23 +121,7 @@
         }
     }
 
-    /**
-     * Handle relevant touch events for the operation of a Tile.
-     *
-     * A click action is performed following the relevant logic that originates from the
-     * [MotionEvent.ACTION_UP] event depending on the current state.
-     */
-    @SuppressLint("ClickableViewAccessibility")
-    override fun onTouch(view: View?, event: MotionEvent?): Boolean {
-        when (event?.actionMasked) {
-            MotionEvent.ACTION_DOWN -> handleActionDown()
-            MotionEvent.ACTION_UP -> handleActionUp()
-            MotionEvent.ACTION_CANCEL -> handleActionCancel()
-        }
-        return true
-    }
-
-    private fun handleActionDown() {
+    fun handleActionDown() {
         when (_state.value) {
             State.IDLE -> {
                 setState(State.TIMEOUT_WAIT)
@@ -155,7 +131,7 @@
         }
     }
 
-    private fun handleActionUp() {
+    fun handleActionUp() {
         when (_state.value) {
             State.TIMEOUT_WAIT -> {
                 _postedActionType.value = ActionType.CLICK
@@ -169,7 +145,7 @@
         }
     }
 
-    private fun handleActionCancel() {
+    fun handleActionCancel() {
         when (_state.value) {
             State.TIMEOUT_WAIT -> {
                 setState(State.IDLE)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index ddb9f35..2ef901d 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.haptics.qs
 
+import android.annotation.SuppressLint
+import android.view.MotionEvent
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
@@ -25,10 +27,9 @@
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
 
-// TODO(b/332903800)
 object QSLongPressEffectViewBinder {
+
     fun bind(
         tile: QSTileViewImpl,
         qsLongPressEffect: QSLongPressEffect?,
@@ -36,11 +37,13 @@
     ): DisposableHandle? {
         if (qsLongPressEffect == null) return null
 
+        // Set the touch listener as the long-press effect
+        setTouchListener(tile, qsLongPressEffect)
+
         return tile.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
-                val tag = "${tileSpec ?: "unknownTileSpec"}#LongPressEffect"
                 // Progress of the effect
-                launch("$tag#progress") {
+                launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#progress" }) {
                     qsLongPressEffect.effectProgress.collect { progress ->
                         progress?.let {
                             if (it == 0f) {
@@ -53,7 +56,7 @@
                 }
 
                 // Action to perform
-                launch("$tag#action") {
+                launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
                     qsLongPressEffect.actionType.collect { action ->
                         action?.let {
                             when (it) {
@@ -70,7 +73,7 @@
                 }
 
                 // Tap timeout wait
-                launch("$tag#timeout") {
+                launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#timeout" }) {
                     qsLongPressEffect.shouldWaitForTapTimeout
                         .filter { it }
                         .collect {
@@ -85,4 +88,16 @@
             }
         }
     }
+
+    @SuppressLint("ClickableViewAccessibility")
+    private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
+        tile.setOnTouchListener { _, event ->
+            when (event.actionMasked) {
+                MotionEvent.ACTION_DOWN -> longPressEffect?.handleActionDown()
+                MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
+                MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
+            }
+            true
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
index b84b01e..f3414b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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,13 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.log.dagger
 
-package com.android.systemui.statusbar.pipeline.dagger
-
+import java.lang.annotation.Documented
 import javax.inject.Qualifier
 
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+/** A [com.android.systemui.log.LogBuffer] for DeviceEntryIcon state. */
+@Qualifier @Documented @Retention(AnnotationRetention.RUNTIME) annotation class DeviceEntryIconLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f2013be..5babc8b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -654,4 +654,13 @@
     public static LogBuffer provideNavbarOrientationTrackingLogBuffer(LogBufferFactory factory) {
         return factory.create("NavbarOrientationTrackingLog", 50);
     }
+
+    /** Provides a {@link LogBuffer} for use by the DeviceEntryIcon and related classes. */
+    @Provides
+    @SysUISingleton
+    @DeviceEntryIconLog
+    public static LogBuffer provideDeviceEntryIconLogBuffer(LogBufferFactory factory) {
+        return factory.create("DeviceEntryIconLog", 100);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 14a9179..8dd3379 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -16,12 +16,585 @@
 
 package com.android.systemui.media.controls.ui.binder
 
+import android.content.Context
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.TransitionDrawable
+import android.os.Trace
+import android.util.Pair
+import android.view.Gravity
+import android.view.View
 import android.widget.ImageButton
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.widget.AdaptiveIcon
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.animation.AnimationBindHandler
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
+import com.android.systemui.media.controls.ui.controller.MediaViewController
+import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT
+import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 object MediaControlViewBinder {
 
+    fun bind(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaControlViewModel,
+        viewController: MediaViewController,
+        falsingManager: FalsingManager,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        @Main mainDispatcher: CoroutineDispatcher,
+        mediaFlags: MediaFlags,
+    ) {
+        val mediaCard = viewHolder.player
+        mediaCard.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.player.collectLatest { playerViewModel ->
+                        playerViewModel?.let {
+                            bindMediaCard(
+                                viewHolder,
+                                viewController,
+                                it,
+                                falsingManager,
+                                backgroundDispatcher,
+                                mainDispatcher,
+                                mediaFlags
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private suspend fun bindMediaCard(
+        viewHolder: MediaViewHolder,
+        viewController: MediaViewController,
+        viewModel: MediaPlayerViewModel,
+        falsingManager: FalsingManager,
+        backgroundDispatcher: CoroutineDispatcher,
+        mainDispatcher: CoroutineDispatcher,
+        mediaFlags: MediaFlags,
+    ) {
+        with(viewHolder) {
+            // AlbumView uses a hardware layer so that clipping of the foreground is handled with
+            // clipping the album art. Otherwise album art shows through at the edges.
+            albumView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+            turbulenceNoiseView.setBlendMode(BlendMode.SCREEN)
+            loadingEffectView.setBlendMode(BlendMode.SCREEN)
+            loadingEffectView.visibility = View.INVISIBLE
+
+            player.contentDescription =
+                viewModel.contentDescription.invoke(viewController.isGutsVisible)
+            player.setOnClickListener {
+                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (!viewController.isGutsVisible) {
+                        viewModel.onClicked(Expandable.fromView(player))
+                    }
+                }
+            }
+            player.setOnLongClickListener {
+                if (!falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+                    if (!viewController.isGutsVisible) {
+                        openGuts(viewHolder, viewController, viewModel)
+                    } else {
+                        closeGuts(viewHolder, viewController, viewModel)
+                    }
+                }
+                return@setOnLongClickListener true
+            }
+        }
+
+        viewController.bindSeekBar(viewModel.onSeek, viewModel.onBindSeekbar)
+        bindOutputSwitcherModel(
+            viewHolder,
+            viewModel.outputSwitcher,
+            viewController,
+            falsingManager
+        )
+        bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager)
+        bindActionButtons(viewHolder, viewModel, viewController, falsingManager)
+        bindScrubbingTime(viewHolder, viewModel, viewController)
+
+        val isSongUpdated = bindSongMetadata(viewHolder, viewModel, viewController)
+
+        bindArtworkAndColor(
+            viewHolder,
+            viewModel,
+            viewController,
+            backgroundDispatcher,
+            mainDispatcher,
+            mediaFlags,
+            isSongUpdated
+        )
+
+        // TODO: We don't need to refresh this state constantly, only if the
+        // state actually changed to something which might impact the
+        // measurement. State refresh interferes with the translation
+        // animation, only run it if it's not running.
+        if (!viewController.metadataAnimationHandler.isRunning) {
+            // Don't refresh in scene framework, because it will calculate
+            // with invalid layout sizes
+            if (!mediaFlags.isSceneContainerEnabled()) {
+                viewController.refreshState()
+            }
+        }
+
+        if (viewModel.playTurbulenceNoise) {
+            viewController.setUpTurbulenceNoise()
+        }
+    }
+
+    private fun bindOutputSwitcherModel(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaOutputSwitcherViewModel,
+        viewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        with(viewHolder.seamless) {
+            visibility = View.VISIBLE
+            isEnabled = viewModel.isTapEnabled
+            contentDescription = viewModel.deviceString
+            setOnClickListener {
+                if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
+                    viewModel.onClicked.invoke(Expandable.fromView(viewHolder.seamlessButton))
+                }
+            }
+        }
+        when (viewModel.deviceIcon) {
+            is Icon.Loaded -> {
+                val icon = viewModel.deviceIcon.drawable
+                if (icon is AdaptiveIcon) {
+                    icon.setBackgroundColor(viewController.colorSchemeTransition.bgColor)
+                }
+                viewHolder.seamlessIcon.setImageDrawable(icon)
+            }
+            is Icon.Resource -> viewHolder.seamlessIcon.setImageResource(viewModel.deviceIcon.res)
+        }
+        viewHolder.seamlessButton.alpha = viewModel.alpha
+        viewHolder.seamlessText.text = viewModel.deviceString
+    }
+
+    private fun bindGutsViewModel(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaPlayerViewModel,
+        viewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        val gutsViewHolder = viewHolder.gutsViewHolder
+        val model = viewModel.gutsMenu
+        with(gutsViewHolder) {
+            gutsText.text = model.gutsText
+            dismissText.visibility = if (model.isDismissEnabled) View.VISIBLE else View.GONE
+            dismiss.isEnabled = model.isDismissEnabled
+            dismiss.setOnClickListener {
+                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    model.onDismissClicked.invoke()
+                }
+            }
+            cancelText.background = model.cancelTextBackground
+            cancel.setOnClickListener {
+                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    closeGuts(viewHolder, viewController, viewModel)
+                }
+            }
+            settings.setOnClickListener {
+                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    model.onSettingsClicked.invoke()
+                }
+            }
+            setDismissible(model.isDismissEnabled)
+            setTextPrimaryColor(model.textPrimaryColor)
+            setAccentPrimaryColor(model.accentPrimaryColor)
+            setSurfaceColor(model.surfaceColor)
+        }
+    }
+
+    private fun bindActionButtons(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaPlayerViewModel,
+        viewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        val genericButtons = MediaViewHolder.genericButtonIds.map { viewHolder.getAction(it) }
+        val expandedSet = viewController.expandedLayout
+        val collapsedSet = viewController.collapsedLayout
+        if (viewModel.useSemanticActions) {
+            // Hide all generic buttons
+            genericButtons.forEach {
+                setVisibleAndAlpha(expandedSet, it.id, false)
+                setVisibleAndAlpha(collapsedSet, it.id, false)
+            }
+
+            SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id ->
+                val button = viewHolder.getAction(id)
+                val actionViewModel = viewModel.actionButtons[index]
+                if (button.id == R.id.actionPrev) {
+                    actionViewModel?.let {
+                        viewController.setUpPrevButtonInfo(true, it.notVisibleValue)
+                    }
+                } else if (button.id == R.id.actionNext) {
+                    actionViewModel?.let {
+                        viewController.setUpNextButtonInfo(true, it.notVisibleValue)
+                    }
+                }
+                actionViewModel?.let { action ->
+                    val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler
+                    animHandler.tryExecute {
+                        if (animHandler.updateRebindId(action.rebindId)) {
+                            animHandler.unregisterAll()
+                            animHandler.tryRegister(action.icon)
+                            animHandler.tryRegister(action.background)
+                            bindButtonCommon(
+                                button,
+                                viewHolder.multiRippleView,
+                                action,
+                                viewController,
+                                falsingManager,
+                            )
+                        }
+                        val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing
+                        setSemanticButtonVisibleAndAlpha(
+                            viewHolder.getAction(id),
+                            viewController.expandedLayout,
+                            viewController.collapsedLayout,
+                            visible,
+                            action.notVisibleValue,
+                            action.showInCollapsed
+                        )
+                    }
+                }
+                    ?: clearButton(button)
+            }
+        } else {
+            // Hide buttons that only appear for semantic actions
+            SEMANTIC_ACTIONS_COMPACT.forEach { buttonId ->
+                setVisibleAndAlpha(expandedSet, buttonId, visible = false)
+                setVisibleAndAlpha(expandedSet, buttonId, visible = false)
+            }
+
+            // Set all generic buttons
+            genericButtons.forEachIndexed { index, button ->
+                if (index < viewModel.actionButtons.size) {
+                    viewModel.actionButtons[index]?.let { action ->
+                        bindButtonCommon(
+                            button,
+                            viewHolder.multiRippleView,
+                            action,
+                            viewController,
+                            falsingManager,
+                        )
+                        setVisibleAndAlpha(expandedSet, button.id, visible = true)
+                        setVisibleAndAlpha(
+                            collapsedSet,
+                            button.id,
+                            visible = action.showInCollapsed
+                        )
+                    }
+                        ?: clearButton(button)
+                } else {
+                    // Hide any unused buttons
+                    clearButton(button)
+                    setVisibleAndAlpha(expandedSet, button.id, visible = false)
+                    setVisibleAndAlpha(collapsedSet, button.id, visible = false)
+                }
+            }
+        }
+        updateSeekBarVisibility(viewController.expandedLayout, isSeekBarEnabled = false)
+    }
+
+    private fun bindButtonCommon(
+        button: ImageButton,
+        multiRippleView: MultiRippleView,
+        actionViewModel: MediaActionViewModel,
+        viewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        button.setImageDrawable(actionViewModel.icon)
+        button.background = actionViewModel.background
+        button.contentDescription = actionViewModel.contentDescription
+        button.isEnabled = actionViewModel.isEnabled
+        if (actionViewModel.isEnabled) {
+            button.setOnClickListener {
+                if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
+                    actionViewModel.onClicked.invoke(it.id)
+
+                    viewController.multiRippleController.play(
+                        createTouchRippleAnimation(
+                            button,
+                            viewController.colorSchemeTransition,
+                            multiRippleView
+                        )
+                    )
+
+                    if (actionViewModel.icon is Animatable) {
+                        actionViewModel.icon.start()
+                    }
+
+                    if (actionViewModel.background is Animatable) {
+                        actionViewModel.background.start()
+                    }
+                }
+            }
+        }
+    }
+
+    private fun bindSongMetadata(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaPlayerViewModel,
+        viewController: MediaViewController,
+    ): Boolean {
+        val expandedSet = viewController.expandedLayout
+        val collapsedSet = viewController.collapsedLayout
+
+        return viewController.metadataAnimationHandler.setNext(
+            Triple(viewModel.titleName, viewModel.artistName, viewModel.isExplicitVisible),
+            {
+                viewHolder.titleText.text = viewModel.titleName
+                viewHolder.artistText.text = viewModel.artistName
+                setVisibleAndAlpha(
+                    expandedSet,
+                    R.id.media_explicit_indicator,
+                    viewModel.isExplicitVisible
+                )
+                setVisibleAndAlpha(
+                    collapsedSet,
+                    R.id.media_explicit_indicator,
+                    viewModel.isExplicitVisible
+                )
+
+                // refreshState is required here to resize the text views (and prevent ellipsis)
+                viewController.refreshState()
+            },
+            {
+                // After finishing the enter animation, we refresh state. This could pop if
+                // something is incorrectly bound, but needs to be run if other elements were
+                // updated while the enter animation was running
+                viewController.refreshState()
+            }
+        )
+    }
+
+    private suspend fun bindArtworkAndColor(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaPlayerViewModel,
+        viewController: MediaViewController,
+        backgroundDispatcher: CoroutineDispatcher,
+        mainDispatcher: CoroutineDispatcher,
+        mediaFlags: MediaFlags,
+        updateBackground: Boolean,
+    ) {
+        val traceCookie = viewHolder.hashCode()
+        val traceName = "MediaControlViewBinder#bindArtworkAndColor"
+        Trace.beginAsyncSection(traceName, traceCookie)
+        if (updateBackground) {
+            viewController.isArtworkBound = false
+        }
+        // Capture width & height from views in foreground for artwork scaling in background
+        var width = viewHolder.albumView.measuredWidth
+        var height = viewHolder.albumView.measuredHeight
+        if (mediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+            // TODO(b/312714128): ensure we have a valid size before setting background
+            width = viewController.widthInSceneContainerPx
+            height = viewController.heightInSceneContainerPx
+        }
+        withContext(backgroundDispatcher) {
+            val artwork =
+                if (viewModel.shouldAddGradient) {
+                    addGradientToPlayerAlbum(
+                        viewHolder.albumView.context,
+                        viewModel.backgroundCover!!,
+                        viewModel.colorScheme,
+                        width,
+                        height
+                    )
+                } else {
+                    ColorDrawable(Color.TRANSPARENT)
+                }
+            withContext(mainDispatcher) {
+                // Transition Colors to current color scheme
+                val colorSchemeChanged =
+                    viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme)
+                val albumView = viewHolder.albumView
+                albumView.setPadding(0, 0, 0, 0)
+                if (
+                    updateBackground ||
+                        colorSchemeChanged ||
+                        (!viewController.isArtworkBound && viewModel.shouldAddGradient)
+                ) {
+                    viewController.prevArtwork?.let {
+                        // Since we throw away the last transition, this will pop if your
+                        // backgrounds are cycled too fast (or the correct background arrives very
+                        // soon after the metadata changes).
+                        val transitionDrawable = TransitionDrawable(arrayOf(it, artwork))
+
+                        scaleTransitionDrawableLayer(transitionDrawable, 0, width, height)
+                        scaleTransitionDrawableLayer(transitionDrawable, 1, width, height)
+                        transitionDrawable.setLayerGravity(0, Gravity.CENTER)
+                        transitionDrawable.setLayerGravity(1, Gravity.CENTER)
+                        transitionDrawable.isCrossFadeEnabled = true
+
+                        albumView.setImageDrawable(transitionDrawable)
+                        transitionDrawable.startTransition(
+                            if (viewModel.shouldAddGradient) 333 else 80
+                        )
+                    }
+                }
+                viewController.isArtworkBound = viewModel.shouldAddGradient
+                viewController.prevArtwork = artwork
+
+                if (viewModel.useGrayColorFilter) {
+                    // Used for resume players to use launcher icon
+                    viewHolder.appIcon.colorFilter = getGrayscaleFilter()
+                    when (viewModel.launcherIcon) {
+                        is Icon.Loaded ->
+                            viewHolder.appIcon.setImageDrawable(viewModel.launcherIcon.drawable)
+                        is Icon.Resource ->
+                            viewHolder.appIcon.setImageResource(viewModel.launcherIcon.res)
+                    }
+                } else {
+                    viewHolder.appIcon.setColorFilter(
+                        viewController.colorSchemeTransition.accentPrimary.targetColor
+                    )
+                    viewHolder.appIcon.setImageIcon(viewModel.appIcon)
+                }
+                Trace.endAsyncSection(traceName, traceCookie)
+            }
+        }
+    }
+
+    private fun scaleTransitionDrawableLayer(
+        transitionDrawable: TransitionDrawable,
+        layer: Int,
+        targetWidth: Int,
+        targetHeight: Int
+    ) {
+        val drawable = transitionDrawable.getDrawable(layer) ?: return
+        val width = drawable.intrinsicWidth
+        val height = drawable.intrinsicHeight
+        val scale =
+            MediaDataUtils.getScaleFactor(Pair(width, height), Pair(targetWidth, targetHeight))
+        if (scale == 0f) return
+        transitionDrawable.setLayerSize(layer, (scale * width).toInt(), (scale * height).toInt())
+    }
+
+    private fun addGradientToPlayerAlbum(
+        context: Context,
+        artworkIcon: android.graphics.drawable.Icon,
+        mutableColorScheme: ColorScheme,
+        width: Int,
+        height: Int
+    ): LayerDrawable {
+        val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
+        return MediaArtworkHelper.setUpGradientColorOnDrawable(
+            albumArt,
+            context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
+            mutableColorScheme,
+            MEDIA_PLAYER_SCRIM_START_ALPHA,
+            MEDIA_PLAYER_SCRIM_END_ALPHA
+        )
+    }
+
+    private fun clearButton(button: ImageButton) {
+        button.setImageDrawable(null)
+        button.contentDescription = null
+        button.isEnabled = false
+        button.background = null
+    }
+
+    private fun bindScrubbingTime(
+        viewHolder: MediaViewHolder,
+        viewModel: MediaPlayerViewModel,
+        viewController: MediaViewController,
+    ) {
+        val expandedSet = viewController.expandedLayout
+        val visible = viewModel.canShowTime && viewController.isScrubbing
+        viewController.canShowScrubbingTime = viewModel.canShowTime
+        setVisibleAndAlpha(expandedSet, viewHolder.scrubbingElapsedTimeView.id, visible)
+        setVisibleAndAlpha(expandedSet, viewHolder.scrubbingTotalTimeView.id, visible)
+        // Collapsed view is always GONE as set in XML, so doesn't need to be updated dynamically.
+    }
+
+    private fun createTouchRippleAnimation(
+        button: ImageButton,
+        colorSchemeTransition: ColorSchemeTransition,
+        multiRippleView: MultiRippleView
+    ): RippleAnimation {
+        val maxSize = (multiRippleView.width * 2).toFloat()
+        return RippleAnimation(
+            RippleAnimationConfig(
+                RippleShader.RippleShape.CIRCLE,
+                duration = 1500L,
+                centerX = button.x + button.width * 0.5f,
+                centerY = button.y + button.height * 0.5f,
+                maxSize,
+                maxSize,
+                button.context.resources.displayMetrics.density,
+                colorSchemeTransition.accentPrimary.currentColor,
+                opacity = 100,
+                sparkleStrength = 0f,
+                baseRingFadeParams = null,
+                sparkleRingFadeParams = null,
+                centerFillFadeParams = null,
+                shouldDistort = false
+            )
+        )
+    }
+
+    private fun openGuts(
+        viewHolder: MediaViewHolder,
+        viewController: MediaViewController,
+        viewModel: MediaPlayerViewModel,
+    ) {
+        viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
+        viewController.openGuts()
+        viewHolder.player.contentDescription = viewModel.contentDescription.invoke(true)
+        viewModel.onLongClicked.invoke()
+    }
+
+    private fun closeGuts(
+        viewHolder: MediaViewHolder,
+        viewController: MediaViewController,
+        viewModel: MediaPlayerViewModel,
+    ) {
+        viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
+        viewController.closeGuts(false)
+        viewHolder.player.contentDescription = viewModel.contentDescription.invoke(false)
+    }
+
     fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
         setVisibleAndAlpha(set, resId, visible, ConstraintSet.GONE)
     }
@@ -62,4 +635,10 @@
         setVisibleAndAlpha(expandedSet, button.id, visible, notVisibleValue)
         setVisibleAndAlpha(collapsedSet, button.id, visible = visible && showInCollapsed)
     }
+
+    private fun getGrayscaleFilter(): ColorMatrixColorFilter {
+        val matrix = ColorMatrix()
+        matrix.setSaturation(0f)
+        return ColorMatrixColorFilter(matrix)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index ca71870..40cf4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -185,8 +185,9 @@
     private val colorEvaluator = ArgbEvaluator.getInstance()
     val isLongPressEffectInitialized: Boolean
         get() = longPressEffect?.hasInitialized == true
-    @VisibleForTesting
-    var longPressEffectHandle: DisposableHandle? = null
+    private var longPressEffectHandle: DisposableHandle? = null
+    val isLongPressEffectBound: Boolean
+        get() = longPressEffectHandle != null
 
     init {
         val typedValue = TypedValue()
@@ -621,11 +622,14 @@
         // Long-press effects
         if (state.handlesLongClick &&
             longPressEffect?.initializeEffect(longPressEffectDuration) == true) {
-            // set the valid long-press effect as the touch listener
-            if (longPressEffectHandle == null) {
+            // bind the long-press effect and set it as the touch listener
+            if (!isLongPressEffectBound) {
                 longPressEffectHandle =
-                    QSLongPressEffectViewBinder.bind(this, longPressEffect, state.spec)
-                setOnTouchListener(longPressEffect)
+                    QSLongPressEffectViewBinder.bind(
+                        this,
+                        longPressEffect,
+                        state.spec,
+                    )
             }
             showRippleEffect = false
             initializeLongPressProperties()
@@ -634,8 +638,7 @@
             // handle a long-press. In this case, we go back to the behaviour of a regular tile
             // and clean-up the resources
             setOnTouchListener(null)
-            longPressEffectHandle?.dispose()
-            longPressEffectHandle = null
+            unbindLongPressEffect()
             showRippleEffect = isClickable
             initialLongPressProperties = null
             finalLongPressProperties = null
@@ -827,6 +830,11 @@
         changeCornerRadius(newRadius)
     }
 
+    private fun unbindLongPressEffect() {
+        longPressEffectHandle?.dispose()
+        longPressEffectHandle = null
+    }
+
     private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
         start + fraction * (end - start)
 
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4d34a86..4e290e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -47,6 +47,7 @@
 import java.util.zip.ZipEntry
 import java.util.zip.ZipOutputStream
 import javax.inject.Inject
+import kotlin.jvm.optionals.getOrElse
 
 class IssueRecordingService
 @Inject
@@ -140,15 +141,25 @@
     }
 
     private fun shareRecording(screenRecording: Uri?) {
-        val sharableUri: Uri =
-            zipAndPackageRecordings(
-                TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
-                screenRecording
-            )
-                ?: return
+        val traces =
+            TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse {
+                Log.v(
+                    TAG,
+                    "Traces were not present. This can happen if users double" +
+                        "click on share notification. Traces are cleaned up after sharing" +
+                        "so they won't be present for the 2nd share attempt."
+                )
+                return
+            }
+        val perfetto = FileProvider.getUriForFile(this, AUTHORITY, traces.first())
+        val urisToShare = mutableListOf(perfetto)
+        traces.removeFirst()
+
+        getZipWinscopeFileUri(traces)?.let { urisToShare.add(it) }
+        screenRecording?.let { urisToShare.add(it) }
+
         val sendIntent =
-            FileSender.buildSendIntent(this, listOf(sharableUri))
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            FileSender.buildSendIntent(this, urisToShare).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
         // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
         mKeyguardDismissUtil.executeWhenUnlocked(
@@ -161,7 +172,7 @@
         )
     }
 
-    private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
+    private fun getZipWinscopeFileUri(traceFiles: List<File>): Uri? {
         try {
             externalCacheDir?.mkdirs()
             val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -171,13 +182,6 @@
                     Files.copy(file.toPath(), os)
                     os.closeEntry()
                 }
-                if (screenRecording != null) {
-                    contentResolver.openInputStream(screenRecording)?.use {
-                        os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
-                        it.transferTo(os)
-                        os.closeEntry()
-                    }
-                }
             }
             return FileProvider.getUriForFile(this, AUTHORITY, outZip)
         } catch (e: Exception) {
@@ -192,8 +196,7 @@
         private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
         private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
         private const val ZIP_SUFFIX = ".zip"
-        private const val TEMP_FILE_PREFIX = "issue_recording"
-        private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
+        private const val TEMP_FILE_PREFIX = "winscope_recordings"
 
         private val DEFAULT_TRACE_TAGS = listOf<String>()
         private const val DEFAULT_BUFFER_SIZE = 16384
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0c8518f..bdeaabf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2069,6 +2069,8 @@
         // Remove views that don't translate
         mTranslateableViews.remove(mChildrenContainerStub);
         mTranslateableViews.remove(mGutsStub);
+        // We don't handle focus highlight in this view, it's done in background drawable instead
+        setDefaultFocusHighlightEnabled(false);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index ea72c9b..5e719b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -68,8 +68,6 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -210,11 +208,9 @@
     @Nullable
     private Boolean mHistoryEnabled;
     private int mBarState;
-    private boolean mIsBouncerShowingFromCentralSurfaces;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private boolean mIsInTransitionToAod = false;
 
-    private final FeatureFlagsClassic mFeatureFlags;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -745,7 +741,6 @@
             StackStateLogger stackLogger,
             NotificationStackScrollLogger logger,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
-            FeatureFlagsClassic featureFlags,
             NotificationTargetsHelper notificationTargetsHelper,
             SecureSettings secureSettings,
             NotificationDismissibilityProvider dismissibilityProvider,
@@ -793,7 +788,6 @@
         mSeenNotificationsInteractor = seenNotificationsInteractor;
         mShadeController = shadeController;
         mWindowRootView = windowRootView;
-        mFeatureFlags = featureFlags;
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
@@ -1391,14 +1385,6 @@
     }
 
     /**
-     * Sets whether the bouncer is currently showing. Should only be called from
-     * {@link CentralSurfaces}.
-     */
-    public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) {
-        mIsBouncerShowingFromCentralSurfaces = bouncerShowing;
-    }
-
-    /**
      * Set the visibility of the view, and propagate it to specific children.
      *
      * @param visible either the view is visible or not.
@@ -1435,7 +1421,7 @@
                 // For more details, see: b/228790482
                 && !mIsInTransitionToAod
                 // Don't show any notification content if the bouncer is showing. See b/267060171.
-                && !isBouncerShowing();
+                && !mPrimaryBouncerInteractor.isBouncerShowing();
 
         mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
 
@@ -1443,24 +1429,6 @@
     }
 
     /**
-     * Returns whether the bouncer is currently showing.
-     *
-     * There's a possible timing difference between when CentralSurfaces marks the bouncer as not
-     * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces
-     * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.)
-     *
-     * This timing difference could be load bearing, which is why we have a feature flag protecting
-     * where we fetch the value from. This flag is intended to be short-lived.
-     */
-    private boolean isBouncerShowing() {
-        if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) {
-            return mPrimaryBouncerInteractor.isBouncerShowing();
-        } else {
-            return mIsBouncerShowingFromCentralSurfaces;
-        }
-    }
-
-    /**
      * Update the importantForAccessibility of NotificationStackScrollLayout.
      * <p>
      * We want the NSSL to be unimportant for accessibility when there's no
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index cb3c03e..e9aa7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2416,7 +2416,6 @@
         mBouncerShowing = bouncerShowing;
         mKeyguardBypassController.setBouncerShowing(bouncerShowing);
         mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
-        mStackScrollerController.setBouncerShowingFromCentralSurfaces(bouncerShowing);
         setBouncerShowingForStatusBarComponents(bouncerShowing);
         mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2b90e64..6e24412 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,8 +19,6 @@
 import android.net.wifi.WifiManager
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.table.TableLogBuffer
@@ -52,12 +50,9 @@
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -131,19 +126,15 @@
         impl: CollapsedStatusBarViewBinderImpl
     ): CollapsedStatusBarViewBinder
 
-    @Binds
-    @IntoMap
-    @ClassKey(WifiRepositoryDagger::class)
-    abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable
-
     companion object {
+
         @Provides
         @SysUISingleton
-        fun provideWifiRepositoryDagger(
+        fun provideRealWifiRepository(
             wifiManager: WifiManager?,
             disabledWifiRepository: DisabledWifiRepository,
             wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
-        ): WifiRepositoryDagger {
+        ): RealWifiRepository {
             // If we have a null [WifiManager], then the wifi repository should be permanently
             // disabled.
             return if (wifiManager == null) {
@@ -155,36 +146,6 @@
 
         @Provides
         @SysUISingleton
-        fun provideWifiRepositoryViaTrackerLibDagger(
-            wifiManager: WifiManager?,
-            disabledWifiRepository: DisabledWifiRepository,
-            wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory,
-        ): WifiRepositoryViaTrackerLibDagger {
-            // If we have a null [WifiManager], then the wifi repository should be permanently
-            // disabled.
-            return if (wifiManager == null) {
-                disabledWifiRepository
-            } else {
-                wifiRepositoryFromTrackerLibFactory.create(wifiManager)
-            }
-        }
-
-        @Provides
-        @SysUISingleton
-        fun provideRealWifiRepository(
-            wifiRepository: WifiRepositoryDagger,
-            wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger,
-            flags: FeatureFlags,
-        ): RealWifiRepository {
-            return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) {
-                wifiRepositoryFromTrackerLib
-            } else {
-                wifiRepository
-            }
-        }
-
-        @Provides
-        @SysUISingleton
         @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
         fun provideFirstMobileSubShowingNetworkTypeIconProvider(
             mobileIconsViewModel: MobileIconsViewModel,
@@ -197,16 +158,8 @@
         @Provides
         @SysUISingleton
         @WifiInputLog
-        fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer {
-            return factory.create("WifiInputLog", 50)
-        }
-
-        @Provides
-        @SysUISingleton
-        @WifiTrackerLibInputLog
-        fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer {
-            // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog.
-            return factory.create("WifiTrackerLibInputLog", 200)
+        fun provideWifiLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create("WifiInputLog", 200)
         }
 
         @Provides
@@ -218,13 +171,6 @@
 
         @Provides
         @SysUISingleton
-        @WifiTrackerLibTableLog
-        fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
-            return factory.create("WifiTrackerLibTableLog", 100)
-        }
-
-        @Provides
-        @SysUISingleton
         @AirplaneTableLog
         fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
             return factory.create("AirplaneTableLog", 30)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
index 6db6944..9ba802c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
@@ -18,8 +18,8 @@
 
 import javax.inject.Qualifier
 
-/** Wifi logs for inputs into the wifi pipeline. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiInputLog
+/**
+ * Wifi logs for inputs into
+ * [com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl].
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class WifiInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
deleted file mode 100644
index 7ca7030..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2023 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.pipeline.dagger
-
-import javax.inject.Qualifier
-
-/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b22e09e..fc7a672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
-import com.android.systemui.CoreStartable
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
@@ -80,8 +79,3 @@
  * repository.
  */
 interface RealWifiRepository : WifiRepository
-
-/** Used only by Dagger to bind [WifiRepositoryImpl]. */
-interface WifiRepositoryDagger : RealWifiRepository, CoreStartable
-/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */
-interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index ca042e2..af6e8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index cfdbe4a..b79fb9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,8 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import javax.inject.Inject
@@ -34,10 +33,7 @@
  * wifi information.
  */
 @SysUISingleton
-class DisabledWifiRepository @Inject constructor() :
-    WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger {
-    override fun start() {}
-
+class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
     override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
 
     override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
deleted file mode 100644
index 67dd32f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2023 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.pipeline.wifi.data.repository.prod
-
-import android.annotation.SuppressLint
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiManager
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Object to provide shared helper functions between [WifiRepositoryImpl] and
- * [WifiRepositoryViaTrackerLib].
- */
-object WifiRepositoryHelper {
-    /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
-    fun createActivityFlow(
-        wifiManager: WifiManager,
-        @Main mainExecutor: Executor,
-        scope: CoroutineScope,
-        tableLogBuffer: TableLogBuffer,
-        inputLogger: (String) -> Unit,
-    ): StateFlow<DataActivityModel> {
-        return conflatedCallbackFlow {
-                val callback =
-                    WifiManager.TrafficStateCallback { state ->
-                        inputLogger.invoke(prettyPrintActivity(state))
-                        trySend(state.toWifiDataActivityModel())
-                    }
-                wifiManager.registerTrafficStateCallback(mainExecutor, callback)
-                awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
-            }
-            .logDiffsForTable(
-                tableLogBuffer,
-                columnPrefix = ACTIVITY_PREFIX,
-                initialValue = ACTIVITY_DEFAULT,
-            )
-            .stateIn(
-                scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ACTIVITY_DEFAULT,
-            )
-    }
-
-    /**
-     * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
-     */
-    fun createNetworkScanFlow(
-        wifiManager: WifiManager,
-        scope: CoroutineScope,
-        @Background dispatcher: CoroutineDispatcher,
-        inputLogger: () -> Unit,
-    ): StateFlow<List<WifiScanEntry>> {
-        return conflatedCallbackFlow {
-                val callback =
-                    object : WifiManager.ScanResultsCallback() {
-                        @SuppressLint("MissingPermission")
-                        override fun onScanResultsAvailable() {
-                            inputLogger.invoke()
-                            trySend(wifiManager.scanResults.toModel())
-                        }
-                    }
-
-                wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
-
-                awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
-            }
-            .stateIn(scope, SharingStarted.Eagerly, emptyList())
-    }
-
-    private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
-
-    // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
-    private fun prettyPrintActivity(activity: Int): String {
-        return when (activity) {
-            WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
-            WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
-            WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
-            WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
-            else -> "INVALID"
-        }
-    }
-
-    private const val ACTIVITY_PREFIX = "wifiActivity"
-    val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 59ef884..20e44e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -17,309 +17,447 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
 import android.annotation.SuppressLint
-import android.content.IntentFilter
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkRequest
-import android.net.wifi.WifiInfo
+import android.net.wifi.ScanResult
 import android.net.wifi.WifiManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import com.android.systemui.broadcast.BroadcastDispatcher
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
+import com.android.wifitrackerlib.WifiPickerTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 
-/** Real implementation of [WifiRepository]. */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+/**
+ * A real implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
+ * truth for wifi information.
+ */
 @SysUISingleton
-@SuppressLint("MissingPermission")
 class WifiRepositoryImpl
 @Inject
 constructor(
-    broadcastDispatcher: BroadcastDispatcher,
-    connectivityManager: ConnectivityManager,
-    connectivityRepository: ConnectivityRepository,
-    logger: WifiInputLogger,
-    @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
-    @Main mainExecutor: Executor,
-    @Background private val bgDispatcher: CoroutineDispatcher,
+    featureFlags: FeatureFlags,
     @Application private val scope: CoroutineScope,
+    @Main private val mainExecutor: Executor,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
     private val wifiManager: WifiManager,
-) : WifiRepositoryDagger {
+    @WifiInputLog private val inputLogger: LogBuffer,
+    @WifiTableLog private val tableLogger: TableLogBuffer,
+) : RealWifiRepository, LifecycleOwner {
 
-    override fun start() {
-        // There are two possible [WifiRepository] implementations: This class (old) and
-        // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old
-        // class to still be running in the background so that we can collect logs and compare
-        // discrepancies. This #start method collects on the flows to ensure that the logs are
-        // collected.
-        scope.launch { isWifiEnabled.collect {} }
-        scope.launch { isWifiDefault.collect {} }
-        scope.launch { wifiNetwork.collect {} }
-        scope.launch { wifiActivity.collect {} }
+    override val lifecycle =
+        LifecycleRegistry(this).also {
+            mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+        }
+
+    private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
+
+    private var wifiPickerTracker: WifiPickerTracker? = null
+
+    private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
+        var current =
+            WifiPickerTrackerInfo(
+                state = WIFI_STATE_DEFAULT,
+                isDefault = false,
+                primaryNetwork = WIFI_NETWORK_DEFAULT,
+                secondaryNetworks = emptyList(),
+            )
+        callbackFlow {
+                val callback =
+                    object : WifiPickerTracker.WifiPickerTrackerCallback {
+                        override fun onWifiEntriesChanged() {
+                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+                            logOnWifiEntriesChanged(connectedEntry)
+
+                            val secondaryNetworks =
+                                if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+                                    val activeNetworks =
+                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
+                                    activeNetworks
+                                        .filter { it != connectedEntry && !it.isPrimaryNetwork }
+                                        .map { it.toWifiNetworkModel() }
+                                } else {
+                                    emptyList()
+                                }
+
+                            // [WifiPickerTracker.connectedWifiEntry] will return the same instance
+                            // but with updated internals. For example, when its validation status
+                            // changes from false to true, the same instance is re-used but with the
+                            // validated field updated.
+                            //
+                            // Because it's the same instance, the flow won't re-emit the value
+                            // (even though the internals have changed). So, we need to transform it
+                            // into our internal model immediately. [toWifiNetworkModel] always
+                            // returns a new instance, so the flow is guaranteed to emit.
+                            send(
+                                newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
+                                        ?: WIFI_NETWORK_DEFAULT,
+                                newSecondaryNetworks = secondaryNetworks,
+                                newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+                            )
+                        }
+
+                        override fun onWifiStateChanged() {
+                            val state = wifiPickerTracker?.wifiState
+                            logOnWifiStateChanged(state)
+                            send(newState = state ?: WIFI_STATE_DEFAULT)
+                        }
+
+                        override fun onNumSavedNetworksChanged() {}
+
+                        override fun onNumSavedSubscriptionsChanged() {}
+
+                        private fun send(
+                            newState: Int = current.state,
+                            newIsDefault: Boolean = current.isDefault,
+                            newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+                            newSecondaryNetworks: List<WifiNetworkModel> =
+                                current.secondaryNetworks,
+                        ) {
+                            val new =
+                                WifiPickerTrackerInfo(
+                                    newState,
+                                    newIsDefault,
+                                    newPrimaryNetwork,
+                                    newSecondaryNetworks,
+                                )
+                            current = new
+                            trySend(new)
+                        }
+                    }
+
+                wifiPickerTracker =
+                    wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
+                        // By default, [WifiPickerTracker] will scan to see all available wifi
+                        // networks in the area. Because SysUI only needs to display the
+                        // **connected** network, we don't need scans to be running (and in fact,
+                        // running scans is costly and should be avoided whenever possible).
+                        this?.disableScanning()
+                    }
+                // The lifecycle must be STARTED in order for the callback to receive events.
+                mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+                awaitClose {
+                    mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+                }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, current)
     }
 
-    private val wifiStateChangeEvents: Flow<Unit> =
-        broadcastDispatcher
-            .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
-            .onEach { logger.logIntent("WIFI_STATE_CHANGED_ACTION") }
-
-    private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
-        MutableSharedFlow(extraBufferCapacity = 1)
-
-    // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
-    // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
-    // have changed.
     override val isWifiEnabled: StateFlow<Boolean> =
-        merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
-            .onStart { emit(Unit) }
-            .mapLatest { isWifiEnabled() }
+        wifiPickerTrackerInfo
+            .map { it.state == WifiManager.WIFI_STATE_ENABLED }
             .distinctUntilChanged()
             .logDiffsForTable(
-                wifiTableLogBuffer,
+                tableLogger,
                 columnPrefix = "",
                 columnName = COL_NAME_IS_ENABLED,
                 initialValue = false,
             )
-            .stateIn(
-                scope = scope,
-                started = SharingStarted.Eagerly,
-                initialValue = false,
-            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
-    // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
-    private suspend fun isWifiEnabled(): Boolean =
-        withContext(bgDispatcher) { wifiManager.isWifiEnabled }
-
-    override val isWifiDefault: StateFlow<Boolean> =
-        connectivityRepository.defaultConnections
-            // TODO(b/274493701): Should wifi be considered default if it's carrier merged?
-            .map { it.wifi.isDefault || it.carrierMerged.isDefault }
+    override val wifiNetwork: StateFlow<WifiNetworkModel> =
+        wifiPickerTrackerInfo
+            .map { it.primaryNetwork }
             .distinctUntilChanged()
             .logDiffsForTable(
-                wifiTableLogBuffer,
+                tableLogger,
+                columnPrefix = "",
+                initialValue = WIFI_NETWORK_DEFAULT,
+            )
+            .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
+
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        wifiPickerTrackerInfo
+            .map { it.secondaryNetworks }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
+                columnPrefix = "",
+                columnName = "secondaryNetworks",
+                initialValue = emptyList(),
+            )
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    /**
+     * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+     * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+     * if it exists, falling back on the connected entry if null
+     */
+    private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+        get() {
+            val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+            return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+                mergedEntry
+            } else {
+                this?.connectedWifiEntry
+            }
+        }
+
+    /**
+     * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+     * primary network. Returns an inactive network if it's not primary.
+     */
+    private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+        return if (!this.isPrimaryNetwork) {
+            WIFI_NETWORK_DEFAULT
+        } else {
+            this.toWifiNetworkModel()
+        }
+    }
+
+    /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
+    private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
+        return if (this is MergedCarrierEntry) {
+            this.convertCarrierMergedToModel()
+        } else {
+            this.convertNormalToModel()
+        }
+    }
+
+    private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+        return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+            WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+        } else {
+            WifiNetworkModel.CarrierMerged(
+                networkId = NETWORK_ID,
+                subscriptionId = this.subscriptionId,
+                level = this.level,
+                // WifiManager APIs to calculate the signal level start from 0, so
+                // maxSignalLevel + 1 represents the total level buckets count.
+                numberOfLevels = wifiManager.maxSignalLevel + 1,
+            )
+        }
+    }
+
+    private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+        if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+            // If our level means the network is unreachable or the level is otherwise invalid, we
+            // don't have an active network.
+            return WifiNetworkModel.Inactive
+        }
+
+        val hotspotDeviceType =
+            if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+                this.deviceType.toHotspotDeviceType()
+            } else {
+                WifiNetworkModel.HotspotDeviceType.NONE
+            }
+
+        return WifiNetworkModel.Active(
+            networkId = NETWORK_ID,
+            isValidated = this.hasInternetAccess(),
+            level = this.level,
+            ssid = this.title,
+            hotspotDeviceType = hotspotDeviceType,
+            // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the  SSID for
+            // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+            // always be false/null in this repository.
+            // TODO(b/292534484): Remove these fields from the wifi network model once this
+            //  repository is fully enabled.
+            isPasspointAccessPoint = false,
+            isOnlineSignUpForPasspointAccessPoint = false,
+            passpointProviderFriendlyName = null,
+        )
+    }
+
+    override val isWifiDefault: StateFlow<Boolean> =
+        wifiPickerTrackerInfo
+            .map { it.isDefault }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
                 columnPrefix = "",
                 columnName = COL_NAME_IS_DEFAULT,
                 initialValue = false,
             )
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
-    override val wifiNetwork: StateFlow<WifiNetworkModel> =
+    override val wifiActivity: StateFlow<DataActivityModel> =
         conflatedCallbackFlow {
-                var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
-
                 val callback =
-                    object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
-                        override fun onCapabilitiesChanged(
-                            network: Network,
-                            networkCapabilities: NetworkCapabilities
-                        ) {
-                            logger.logOnCapabilitiesChanged(
-                                network,
-                                networkCapabilities,
-                                isDefaultNetworkCallback = false,
-                            )
-
-                            wifiNetworkChangeEvents.tryEmit(Unit)
-
-                            val wifiInfo =
-                                networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
-                            if (wifiInfo?.isPrimary == true) {
-                                val wifiNetworkModel =
-                                    createWifiNetworkModel(
-                                        wifiInfo,
-                                        network,
-                                        networkCapabilities,
-                                        wifiManager,
-                                    )
-                                currentWifi = wifiNetworkModel
-                                trySend(wifiNetworkModel)
-                            }
-                        }
-
-                        override fun onLost(network: Network) {
-                            logger.logOnLost(network, isDefaultNetworkCallback = false)
-
-                            wifiNetworkChangeEvents.tryEmit(Unit)
-
-                            val wifi = currentWifi
-                            if (
-                                (wifi is WifiNetworkModel.Active &&
-                                    wifi.networkId == network.getNetId()) ||
-                                    (wifi is WifiNetworkModel.CarrierMerged &&
-                                        wifi.networkId == network.getNetId())
-                            ) {
-                                val newNetworkModel = WifiNetworkModel.Inactive
-                                currentWifi = newNetworkModel
-                                trySend(newNetworkModel)
-                            }
-                        }
+                    WifiManager.TrafficStateCallback { state ->
+                        logActivity(state)
+                        trySend(state.toWifiDataActivityModel())
                     }
-
-                connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
-
-                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+                wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+                awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
             }
-            .distinctUntilChanged()
             .logDiffsForTable(
-                wifiTableLogBuffer,
-                columnPrefix = "",
-                initialValue = WIFI_NETWORK_DEFAULT,
+                tableLogger,
+                columnPrefix = ACTIVITY_PREFIX,
+                initialValue = ACTIVITY_DEFAULT,
             )
-            // There will be multiple wifi icons in different places that will frequently
-            // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
-            // that new subscribes will get the latest value immediately upon subscription.
-            // Otherwise, the views could show stale data. See b/244173280.
             .stateIn(
                 scope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = WIFI_NETWORK_DEFAULT,
+                initialValue = ACTIVITY_DEFAULT,
             )
 
-    // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
-    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
-        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
-
-    override val wifiActivity: StateFlow<DataActivityModel> =
-        WifiRepositoryHelper.createActivityFlow(
-            wifiManager,
-            mainExecutor,
-            scope,
-            wifiTableLogBuffer,
-            logger::logActivity,
-        )
-
     override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
-        WifiRepositoryHelper.createNetworkScanFlow(
-            wifiManager,
-            scope,
-            bgDispatcher,
-            logger::logScanResults
-        )
+        conflatedCallbackFlow {
+                val callback =
+                    object : WifiManager.ScanResultsCallback() {
+                        @SuppressLint("MissingPermission")
+                        override fun onScanResultsAvailable() {
+                            logScanResults()
+                            trySend(wifiManager.scanResults.toModel())
+                        }
+                    }
 
-    companion object {
-        // Start out with no known wifi network.
-        // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
-        // initial fetch to get a starting wifi network. But, it uses a deprecated API
-        // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
-        // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
-        // NetworkCallback inside [wifiNetwork] for our wifi network information.
-        val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+                wifiManager.registerScanResultsCallback(bgDispatcher.asExecutor(), callback)
 
-        const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
-
-        private fun createWifiNetworkModel(
-            wifiInfo: WifiInfo,
-            network: Network,
-            networkCapabilities: NetworkCapabilities,
-            wifiManager: WifiManager,
-        ): WifiNetworkModel {
-            return if (wifiInfo.isCarrierMerged) {
-                if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
-                    WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
-                } else {
-                    WifiNetworkModel.CarrierMerged(
-                        networkId = network.getNetId(),
-                        subscriptionId = wifiInfo.subscriptionId,
-                        level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
-                        // The WiFi signal level returned by WifiManager#calculateSignalLevel start
-                        // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
-                        // buckets count.
-                        numberOfLevels = wifiManager.maxSignalLevel + 1,
-                    )
-                }
-            } else {
-                WifiNetworkModel.Active(
-                    network.getNetId(),
-                    isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
-                    level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
-                    wifiInfo.ssid,
-                    // This repository doesn't support any hotspot information.
-                    WifiNetworkModel.HotspotDeviceType.NONE,
-                    wifiInfo.isPasspointAp,
-                    wifiInfo.isOsuAp,
-                    wifiInfo.passpointProviderFriendlyName
-                )
+                awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
             }
-        }
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
-        private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
-            NetworkRequest.Builder()
-                .clearCapabilities()
-                .addCapability(NET_CAPABILITY_NOT_VPN)
-                .addTransportType(TRANSPORT_WIFI)
-                .addTransportType(TRANSPORT_CELLULAR)
-                .build()
+    private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
+
+    private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
+        inputLogger.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = connectedEntry.toString() },
+            { "onWifiEntriesChanged. ConnectedEntry=$str1" },
+        )
     }
 
+    private fun logOnWifiStateChanged(state: Int?) {
+        inputLogger.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = state ?: -1 },
+            { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
+        )
+    }
+
+    private fun logActivity(activity: Int) {
+        inputLogger.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = prettyPrintActivity(activity) },
+            { "onActivityChanged: $str1" }
+        )
+    }
+
+    // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+    private fun prettyPrintActivity(activity: Int): String {
+        return when (activity) {
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+            else -> "INVALID"
+        }
+    }
+
+    private fun logScanResults() =
+        inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
+
+    /**
+     * Data class storing all the information fetched from [WifiPickerTracker].
+     *
+     * Used so that we only register a single callback on [WifiPickerTracker].
+     */
+    data class WifiPickerTrackerInfo(
+        /** The current wifi state. See [WifiManager.getWifiState]. */
+        val state: Int,
+        /** True if wifi is currently the default connection and false otherwise. */
+        val isDefault: Boolean,
+        /** The currently primary wifi network. */
+        val primaryNetwork: WifiNetworkModel,
+        /** The current secondary network(s), if any. Specifically excludes the primary network. */
+        val secondaryNetworks: List<WifiNetworkModel>
+    )
+
     @SysUISingleton
     class Factory
     @Inject
     constructor(
-        private val broadcastDispatcher: BroadcastDispatcher,
-        private val connectivityManager: ConnectivityManager,
-        private val connectivityRepository: ConnectivityRepository,
-        private val logger: WifiInputLogger,
-        @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
+        private val featureFlags: FeatureFlags,
+        @Application private val scope: CoroutineScope,
         @Main private val mainExecutor: Executor,
         @Background private val bgDispatcher: CoroutineDispatcher,
-        @Application private val scope: CoroutineScope,
+        private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+        @WifiInputLog private val inputLogger: LogBuffer,
+        @WifiTableLog private val tableLogger: TableLogBuffer,
     ) {
         fun create(wifiManager: WifiManager): WifiRepositoryImpl {
             return WifiRepositoryImpl(
-                broadcastDispatcher,
-                connectivityManager,
-                connectivityRepository,
-                logger,
-                wifiTableLogBuffer,
+                featureFlags,
+                scope,
                 mainExecutor,
                 bgDispatcher,
-                scope,
+                wifiPickerTrackerFactory,
                 wifiManager,
+                inputLogger,
+                tableLogger,
             )
         }
     }
+
+    companion object {
+        // Start out with no known wifi network.
+        @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+
+        private const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
+
+        private const val ACTIVITY_PREFIX = "wifiActivity"
+        val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+        private const val TAG = "WifiTrackerLibInputLog"
+
+        /**
+         * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
+         * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
+         * callbacks within the repository.
+         *
+         * Since this class does not need to manually apply framework callbacks and since the
+         * network ID is not used beyond the repository, it's safe to use an invalid ID in this
+         * repository.
+         *
+         * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
+         * to [WifiRepositoryImpl].
+         */
+        private const val NETWORK_ID = -1
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
deleted file mode 100644
index 1670dd3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2023 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.pipeline.wifi.data.repository.prod
-
-import android.net.wifi.WifiManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.wifitrackerlib.HotspotNetworkEntry
-import com.android.wifitrackerlib.MergedCarrierEntry
-import com.android.wifitrackerlib.WifiEntry
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
-import com.android.wifitrackerlib.WifiPickerTracker
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
- * truth for wifi information.
- *
- * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484.
- */
-@SysUISingleton
-class WifiRepositoryViaTrackerLib
-@Inject
-constructor(
-    featureFlags: FeatureFlags,
-    @Application private val scope: CoroutineScope,
-    @Main private val mainExecutor: Executor,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
-    private val wifiManager: WifiManager,
-    @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
-    @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
-) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner {
-
-    override val lifecycle =
-        LifecycleRegistry(this).also {
-            mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
-        }
-
-    private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
-
-    private var wifiPickerTracker: WifiPickerTracker? = null
-
-    private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
-        var current =
-            WifiPickerTrackerInfo(
-                state = WIFI_STATE_DEFAULT,
-                isDefault = false,
-                primaryNetwork = WIFI_NETWORK_DEFAULT,
-                secondaryNetworks = emptyList(),
-            )
-        callbackFlow {
-                val callback =
-                    object : WifiPickerTracker.WifiPickerTrackerCallback {
-                        override fun onWifiEntriesChanged() {
-                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
-                            logOnWifiEntriesChanged(connectedEntry)
-
-                            val secondaryNetworks =
-                                if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
-                                    val activeNetworks =
-                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
-                                    activeNetworks
-                                        .filter { it != connectedEntry && !it.isPrimaryNetwork }
-                                        .map { it.toWifiNetworkModel() }
-                                } else {
-                                    emptyList()
-                                }
-
-                            // [WifiPickerTracker.connectedWifiEntry] will return the same instance
-                            // but with updated internals. For example, when its validation status
-                            // changes from false to true, the same instance is re-used but with the
-                            // validated field updated.
-                            //
-                            // Because it's the same instance, the flow won't re-emit the value
-                            // (even though the internals have changed). So, we need to transform it
-                            // into our internal model immediately. [toWifiNetworkModel] always
-                            // returns a new instance, so the flow is guaranteed to emit.
-                            send(
-                                newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
-                                        ?: WIFI_NETWORK_DEFAULT,
-                                newSecondaryNetworks = secondaryNetworks,
-                                newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
-                            )
-                        }
-
-                        override fun onWifiStateChanged() {
-                            val state = wifiPickerTracker?.wifiState
-                            logOnWifiStateChanged(state)
-                            send(newState = state ?: WIFI_STATE_DEFAULT)
-                        }
-
-                        override fun onNumSavedNetworksChanged() {}
-
-                        override fun onNumSavedSubscriptionsChanged() {}
-
-                        private fun send(
-                            newState: Int = current.state,
-                            newIsDefault: Boolean = current.isDefault,
-                            newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
-                            newSecondaryNetworks: List<WifiNetworkModel> =
-                                current.secondaryNetworks,
-                        ) {
-                            val new =
-                                WifiPickerTrackerInfo(
-                                    newState,
-                                    newIsDefault,
-                                    newPrimaryNetwork,
-                                    newSecondaryNetworks,
-                                )
-                            current = new
-                            trySend(new)
-                        }
-                    }
-
-                wifiPickerTracker =
-                    wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
-                        // By default, [WifiPickerTracker] will scan to see all available wifi
-                        // networks in the area. Because SysUI only needs to display the
-                        // **connected** network, we don't need scans to be running (and in fact,
-                        // running scans is costly and should be avoided whenever possible).
-                        this?.disableScanning()
-                    }
-                // The lifecycle must be STARTED in order for the callback to receive events.
-                mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
-                awaitClose {
-                    mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
-                }
-            }
-            .stateIn(scope, SharingStarted.Eagerly, current)
-    }
-
-    override val isWifiEnabled: StateFlow<Boolean> =
-        wifiPickerTrackerInfo
-            .map { it.state == WifiManager.WIFI_STATE_ENABLED }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                wifiTrackerLibTableLogBuffer,
-                columnPrefix = "",
-                columnName = COL_NAME_IS_ENABLED,
-                initialValue = false,
-            )
-            .stateIn(scope, SharingStarted.Eagerly, false)
-
-    override val wifiNetwork: StateFlow<WifiNetworkModel> =
-        wifiPickerTrackerInfo
-            .map { it.primaryNetwork }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                wifiTrackerLibTableLogBuffer,
-                columnPrefix = "",
-                initialValue = WIFI_NETWORK_DEFAULT,
-            )
-            .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
-
-    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
-        wifiPickerTrackerInfo
-            .map { it.secondaryNetworks }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                wifiTrackerLibTableLogBuffer,
-                columnPrefix = "",
-                columnName = "secondaryNetworks",
-                initialValue = emptyList(),
-            )
-            .stateIn(scope, SharingStarted.Eagerly, emptyList())
-
-    /**
-     * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
-     * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
-     * if it exists, falling back on the connected entry if null
-     */
-    private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
-        get() {
-            val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
-            return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
-                mergedEntry
-            } else {
-                this?.connectedWifiEntry
-            }
-        }
-
-    /**
-     * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
-     * primary network. Returns an inactive network if it's not primary.
-     */
-    private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
-        return if (!this.isPrimaryNetwork) {
-            WIFI_NETWORK_DEFAULT
-        } else {
-            this.toWifiNetworkModel()
-        }
-    }
-
-    /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
-    private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
-        return if (this is MergedCarrierEntry) {
-            this.convertCarrierMergedToModel()
-        } else {
-            this.convertNormalToModel()
-        }
-    }
-
-    private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
-        return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
-            WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
-        } else {
-            WifiNetworkModel.CarrierMerged(
-                networkId = NETWORK_ID,
-                subscriptionId = this.subscriptionId,
-                level = this.level,
-                // WifiManager APIs to calculate the signal level start from 0, so
-                // maxSignalLevel + 1 represents the total level buckets count.
-                numberOfLevels = wifiManager.maxSignalLevel + 1,
-            )
-        }
-    }
-
-    private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
-        if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
-            // If our level means the network is unreachable or the level is otherwise invalid, we
-            // don't have an active network.
-            return WifiNetworkModel.Inactive
-        }
-
-        val hotspotDeviceType =
-            if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
-                this.deviceType.toHotspotDeviceType()
-            } else {
-                WifiNetworkModel.HotspotDeviceType.NONE
-            }
-
-        return WifiNetworkModel.Active(
-            networkId = NETWORK_ID,
-            isValidated = this.hasInternetAccess(),
-            level = this.level,
-            ssid = this.title,
-            hotspotDeviceType = hotspotDeviceType,
-            // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the  SSID for
-            // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
-            // always be false/null in this repository.
-            // TODO(b/292534484): Remove these fields from the wifi network model once this
-            //  repository is fully enabled.
-            isPasspointAccessPoint = false,
-            isOnlineSignUpForPasspointAccessPoint = false,
-            passpointProviderFriendlyName = null,
-        )
-    }
-
-    override val isWifiDefault: StateFlow<Boolean> =
-        wifiPickerTrackerInfo
-            .map { it.isDefault }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                wifiTrackerLibTableLogBuffer,
-                columnPrefix = "",
-                columnName = COL_NAME_IS_DEFAULT,
-                initialValue = false,
-            )
-            .stateIn(scope, SharingStarted.Eagerly, false)
-
-    override val wifiActivity: StateFlow<DataActivityModel> =
-        WifiRepositoryHelper.createActivityFlow(
-            wifiManager,
-            mainExecutor,
-            scope,
-            wifiTrackerLibTableLogBuffer,
-            this::logActivity,
-        )
-
-    override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
-        WifiRepositoryHelper.createNetworkScanFlow(
-            wifiManager,
-            scope,
-            bgDispatcher,
-            this::logScanResults,
-        )
-
-    private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
-        inputLogger.log(
-            TAG,
-            LogLevel.DEBUG,
-            { str1 = connectedEntry.toString() },
-            { "onWifiEntriesChanged. ConnectedEntry=$str1" },
-        )
-    }
-
-    private fun logOnWifiStateChanged(state: Int?) {
-        inputLogger.log(
-            TAG,
-            LogLevel.DEBUG,
-            { int1 = state ?: -1 },
-            { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
-        )
-    }
-
-    private fun logActivity(activity: String) {
-        inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
-    }
-
-    private fun logScanResults() =
-        inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-
-    /**
-     * Data class storing all the information fetched from [WifiPickerTracker].
-     *
-     * Used so that we only register a single callback on [WifiPickerTracker].
-     */
-    data class WifiPickerTrackerInfo(
-        /** The current wifi state. See [WifiManager.getWifiState]. */
-        val state: Int,
-        /** True if wifi is currently the default connection and false otherwise. */
-        val isDefault: Boolean,
-        /** The currently primary wifi network. */
-        val primaryNetwork: WifiNetworkModel,
-        /** The current secondary network(s), if any. Specifically excludes the primary network. */
-        val secondaryNetworks: List<WifiNetworkModel>
-    )
-
-    @SysUISingleton
-    class Factory
-    @Inject
-    constructor(
-        private val featureFlags: FeatureFlags,
-        @Application private val scope: CoroutineScope,
-        @Main private val mainExecutor: Executor,
-        @Background private val bgDispatcher: CoroutineDispatcher,
-        private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
-        @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
-        @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
-    ) {
-        fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
-            return WifiRepositoryViaTrackerLib(
-                featureFlags,
-                scope,
-                mainExecutor,
-                bgDispatcher,
-                wifiPickerTrackerFactory,
-                wifiManager,
-                inputLogger,
-                wifiTrackerLibTableLogBuffer,
-            )
-        }
-    }
-
-    companion object {
-        private const val TAG = "WifiTrackerLibInputLog"
-
-        /**
-         * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
-         * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
-         * callbacks within the repository.
-         *
-         * Since this class does not need to manually apply framework callbacks and since the
-         * network ID is not used beyond the repository, it's safe to use an invalid ID in this
-         * repository.
-         *
-         * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
-         * to [WifiRepositoryViaTrackerLib].
-         */
-        private const val NETWORK_ID = -1
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
deleted file mode 100644
index b76bb51..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2023 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.pipeline.wifi.shared
-
-import android.net.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
-import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
-import javax.inject.Inject
-
-/**
- * Logger for all the wifi-related inputs (intents, callbacks, etc.) that the wifi repo receives.
- */
-@SysUISingleton
-class WifiInputLogger
-@Inject
-constructor(
-    @WifiInputLog val buffer: LogBuffer,
-) {
-    fun logOnCapabilitiesChanged(
-        network: Network,
-        networkCapabilities: NetworkCapabilities,
-        isDefaultNetworkCallback: Boolean,
-    ) {
-        LoggerHelper.logOnCapabilitiesChanged(
-            buffer,
-            TAG,
-            network,
-            networkCapabilities,
-            isDefaultNetworkCallback,
-        )
-    }
-
-    fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
-        LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
-    }
-
-    fun logIntent(intentName: String) {
-        buffer.log(TAG, LogLevel.DEBUG, { str1 = intentName }, { "Intent received: $str1" })
-    }
-
-    fun logActivity(activity: String) {
-        buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" })
-    }
-
-    fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-}
-
-private const val TAG = "WifiInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 45078e3..46ca6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,6 +22,7 @@
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 
 import android.annotation.WorkerThread;
@@ -151,7 +152,9 @@
     @Override
     public void init() {
         mLogger.logBatteryControllerInit(this, mHasReceivedBattery);
-        registerReceiver();
+        if (!registerBatteryControllerReceiversInCorestartable()) {
+            registerReceiver();
+        }
         if (!mHasReceivedBattery) {
             // Get initial state. Relying on Sticky behavior until API for getting info.
             Intent intent = mContext.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
new file mode 100644
index 0000000..7f601c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.os.PowerManager;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** A {@link CoreStartable} responsible for registering the receivers for
+ * {@link BatteryControllerImpl}.
+ */
+@SysUISingleton
+public class BatteryControllerStartable implements CoreStartable {
+
+    private final BatteryController mBatteryController;
+    private final Executor mBackgroundExecutor;
+
+    private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+    protected final BroadcastDispatcher mBroadcastDispatcher;
+    @Inject
+    public BatteryControllerStartable(
+            BatteryController batteryController,
+            BroadcastDispatcher broadcastDispatcher,
+            @Background Executor backgroundExecutor) {
+        mBatteryController = batteryController;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mBackgroundExecutor = backgroundExecutor;
+    }
+
+    private void registerReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+        filter.addAction(ACTION_LEVEL_TEST);
+        filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+        mBroadcastDispatcher.registerReceiver((BroadcastReceiver) mBatteryController, filter);
+    }
+
+    @Override
+    public void start() {
+        if (registerBatteryControllerReceiversInCorestartable()
+                && mBatteryController instanceof BatteryControllerImpl) {
+            mBackgroundExecutor.execute(() -> registerReceiver());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 6b237f8e..f19fa20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -34,6 +35,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -53,32 +55,40 @@
     private val uiEventLogger: UiEventLogger,
 ) {
 
-    private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+    private val sessionWithPlayback: StateFlow<Result<SessionWithPlayback?>> =
         interactor.defaultActiveMediaSession
             .flatMapLatest { session ->
                 if (session == null) {
-                    flowOf(null)
+                    flowOf(Result.Data<SessionWithPlayback?>(null))
                 } else {
-                    mediaDeviceSessionInteractor.playbackState(session).map { playback ->
-                        playback?.let { SessionWithPlayback(session, it) }
-                    }
+                    mediaDeviceSessionInteractor
+                        .playbackState(session)
+                        .map { playback ->
+                            playback?.let {
+                                Result.Data<SessionWithPlayback?>(SessionWithPlayback(session, it))
+                            }
+                        }
+                        .filterNotNull()
                 }
             }
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
-                null,
+                Result.Loading(),
             )
 
     val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
         combine(sessionWithPlayback, interactor.currentConnectedDevice) {
                 mediaDeviceSession,
                 currentConnectedDevice ->
+                if (mediaDeviceSession !is Result.Data) {
+                    return@combine null
+                }
                 ConnectedDeviceViewModel(
-                    if (mediaDeviceSession?.playback?.isActive == true) {
+                    if (mediaDeviceSession.data?.playback?.isActive == true) {
                         context.getString(
                             R.string.media_output_label_title,
-                            mediaDeviceSession.session.appLabel
+                            mediaDeviceSession.data.session.appLabel
                         )
                     } else {
                         context.getString(R.string.media_output_title_without_playing)
@@ -96,7 +106,10 @@
         combine(sessionWithPlayback, interactor.currentConnectedDevice) {
                 mediaDeviceSession,
                 currentConnectedDevice ->
-                if (mediaDeviceSession?.playback?.isActive == true) {
+                if (mediaDeviceSession !is Result.Data) {
+                    return@combine null
+                }
+                if (mediaDeviceSession.data?.playback?.isActive == true) {
                     val icon =
                         currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
                             ?: Icon.Resource(
@@ -130,6 +143,7 @@
 
     fun onBarClick(expandable: Expandable) {
         uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
-        actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
+        val result = sessionWithPlayback.value
+        actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
new file mode 100644
index 0000000..ac8092c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.volume.panel.component.volume.domain.interactor
+
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides volume sliders to show in the Volume Panel. */
+@VolumePanelScope
+class AudioSlidersInteractor
+@Inject
+constructor(
+    @VolumePanelScope scope: CoroutineScope,
+    mediaOutputInteractor: MediaOutputInteractor,
+    audioRepository: AudioRepository,
+) {
+
+    val volumePanelSliders: StateFlow<List<SliderType>> =
+        combineTransform(
+                mediaOutputInteractor.activeMediaDeviceSessions,
+                mediaOutputInteractor.defaultActiveMediaSession,
+                audioRepository.communicationDevice,
+            ) { activeSessions, defaultSession, communicationDevice ->
+                coroutineScope {
+                    val viewModels = buildList {
+                        if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
+                            addSession(activeSessions.remote)
+                            addStream(AudioManager.STREAM_MUSIC)
+                        } else {
+                            addStream(AudioManager.STREAM_MUSIC)
+                            addSession(activeSessions.remote)
+                        }
+
+                        if (communicationDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
+                            addStream(AudioManager.STREAM_BLUETOOTH_SCO)
+                        } else {
+                            addStream(AudioManager.STREAM_VOICE_CALL)
+                        }
+                        addStream(AudioManager.STREAM_RING)
+                        addStream(AudioManager.STREAM_NOTIFICATION)
+                        addStream(AudioManager.STREAM_ALARM)
+                    }
+                    emit(viewModels)
+                }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
+        if (remoteMediaDeviceSession?.canAdjustVolume == true) {
+            add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
+        }
+    }
+
+    private fun MutableList<SliderType>.addStream(stream: Int) {
+        add(SliderType.Stream(AudioStream(stream)))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
index b97123b..6129ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.volume.domain.model
 
 import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
 
 /** The type of volume slider that can be shown at the UI. */
 sealed interface SliderType {
@@ -25,5 +26,5 @@
     data class Stream(val stream: AudioStream) : SliderType
 
     /** The represents media device casting volume. */
-    data object MediaDeviceCast : SliderType
+    data class MediaDeviceCast(val session: MediaDeviceSession) : SliderType
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c8cd6fd..ee642a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -53,6 +53,7 @@
         mapOf(
             AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
             AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
+            AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.drawable.ic_call,
             AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
             AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
             AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
@@ -61,6 +62,7 @@
         mapOf(
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
             AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
+            AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.string.stream_voice_call,
             AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
             AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
             AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
@@ -78,6 +80,8 @@
                 VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
             AudioStream(AudioManager.STREAM_VOICE_CALL) to
                 VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+            AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to
+                VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
             AudioStream(AudioManager.STREAM_RING) to
                 VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
             AudioStream(AudioManager.STREAM_NOTIFICATION) to
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 09e56c1..741f5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.volume.panel.component.volume.ui.viewmodel
 
-import android.media.AudioManager
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.interactor.AudioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -33,12 +33,12 @@
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 
 /**
@@ -55,28 +55,21 @@
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
     private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+    streamsInteractor: AudioSlidersInteractor,
 ) {
 
     val sliderViewModels: StateFlow<List<SliderViewModel>> =
-        combineTransform(
-                mediaOutputInteractor.activeMediaDeviceSessions,
-                mediaOutputInteractor.defaultActiveMediaSession,
-            ) { activeSessions, defaultSession ->
+        streamsInteractor.volumePanelSliders
+            .transformLatest { sliderTypes ->
                 coroutineScope {
-                    val viewModels = buildList {
-                        if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
-                            addRemoteViewModelIfNeeded(this, activeSessions.remote)
-                            addStreamViewModel(this, AudioManager.STREAM_MUSIC)
-                        } else {
-                            addStreamViewModel(this, AudioManager.STREAM_MUSIC)
-                            addRemoteViewModelIfNeeded(this, activeSessions.remote)
+                    val viewModels =
+                        sliderTypes.map { type ->
+                            when (type) {
+                                is SliderType.Stream -> createStreamViewModel(type.stream)
+                                is SliderType.MediaDeviceCast ->
+                                    createSessionViewModel(type.session)
+                            }
                         }
-
-                        addStreamViewModel(this, AudioManager.STREAM_VOICE_CALL)
-                        addStreamViewModel(this, AudioManager.STREAM_RING)
-                        addStreamViewModel(this, AudioManager.STREAM_NOTIFICATION)
-                        addStreamViewModel(this, AudioManager.STREAM_ALARM)
-                    }
                     emit(viewModels)
                 }
             }
@@ -98,29 +91,18 @@
         scope.launch { mutableIsExpanded.emit(isExpanded) }
     }
 
-    private fun CoroutineScope.addRemoteViewModelIfNeeded(
-        list: MutableList<SliderViewModel>,
-        remoteMediaDeviceSession: MediaDeviceSession?
-    ) {
-        if (remoteMediaDeviceSession?.canAdjustVolume == true) {
-            val viewModel =
-                castVolumeSliderViewModelFactory.create(
-                    remoteMediaDeviceSession,
-                    this,
-                )
-            list.add(viewModel)
-        }
+    private fun CoroutineScope.createSessionViewModel(
+        session: MediaDeviceSession
+    ): CastVolumeSliderViewModel {
+        return castVolumeSliderViewModelFactory.create(session, this)
     }
 
-    private fun CoroutineScope.addStreamViewModel(
-        list: MutableList<SliderViewModel>,
-        stream: Int,
-    ) {
-        val viewModel =
-            streamSliderViewModelFactory.create(
-                AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
-                this,
-            )
-        list.add(viewModel)
+    private fun CoroutineScope.createStreamViewModel(
+        stream: AudioStream,
+    ): AudioStreamSliderViewModel {
+        return streamSliderViewModelFactory.create(
+            AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
+            this,
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
index b84b01e..8793538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.volume.panel.shared.model
 
-import javax.inject.Qualifier
+/** Models a loadable result */
+sealed interface Result<T> {
 
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+    /** The data is still loading */
+    class Loading<T> : Result<T>
+
+    /** The data is loaded successfully */
+    data class Data<T>(val data: T) : Result<T>
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index ffedb30..52af8fb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -18,11 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
index f8fdd8d..6512e70 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.keyguard
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 08c1de1..303ae97 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -50,10 +50,11 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.text.TextUtils;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.keyguard.logging.CarrierTextManagerLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index b4a9d40..e2063d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -30,7 +30,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.AttributeSet;
@@ -40,6 +39,8 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.clocks.ClockController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 93e7602..6228ff8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -27,12 +27,13 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.biometrics.BiometricSourceType;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.Editable;
 import android.text.TextWatcher;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index edb910a..c566826 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -23,12 +23,13 @@
 
 import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 6654a6c..a0e8065 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -17,7 +17,6 @@
 
 import android.graphics.Color;
 import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
@@ -27,10 +26,11 @@
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.ListBuilder;
 import androidx.slice.widget.RowContent;
+import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.res.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
index e6b6964..ed61ee12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
@@ -1,8 +1,8 @@
 package com.android.keyguard
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import org.junit.Assert.assertEquals
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index b2828a4..0696a4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -31,11 +31,12 @@
 
 import android.animation.AnimatorTestRule;
 import android.platform.test.annotations.DisableFlags;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.app.animation.Interpolators;
 import com.android.systemui.Flags;
 import com.android.systemui.animation.ViewHierarchyAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
index 17f77aa..3b57d8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.keyguard
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
 import com.android.systemui.power.shared.model.ScreenPowerState
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 86439e5..afd2034 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,6 +1,6 @@
 package com.android.keyguard
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fde45d3..68b6d9d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -106,11 +106,12 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 532c59a..d6a5b4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -28,8 +28,8 @@
 
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.settingslib.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
index b843fda..516b665 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -23,11 +23,12 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Size;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.FakeSharedPreferences;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index c0e108e..5e7adb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
 import android.view.MotionEvent
 import android.view.Surface
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index f5990be..b7ed27f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -21,7 +21,7 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index eb6e517..2c17181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -24,7 +24,7 @@
 import android.os.Looper
 import android.os.PatternMatcher
 import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 39e4467..582f301 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -21,7 +21,7 @@
 import android.content.IntentFilter
 import android.os.Handler
 import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
index feaedc5..1e522fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.camera
 
 import android.content.Intent
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 6a79ee8..6cc3ef19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.controls.ui
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.view.HapticFeedbackConstants
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 523127e0..dbe59e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -23,7 +23,7 @@
 import android.content.res.Resources.NotFoundException
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
index 70d6dd9..943e212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
@@ -17,7 +17,7 @@
 
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 7c1325e..d500dd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.flags
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import java.io.PrintWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 303aaa1..5e87a6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -19,7 +19,7 @@
 import android.database.ContentObserver
 import android.net.Uri
 import android.os.Handler
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
index db6f85f..755cc46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.flags
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 7d7abab..0fdda08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.flags
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index e287f19..3c965ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.flags
 
 import android.os.PowerManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 1f04828..0116e53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.flags
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.power.domain.interactor.PowerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 1d1949d..2daa86b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.flags
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 4ba1bc6..8a29217 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -2,7 +2,7 @@
 
 import android.app.Fragment
 import android.os.Looper
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index eb885fd..4fcd3bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -18,9 +18,9 @@
 
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.Drawable
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index 711669e..bb95ba3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.media.controls.ui.animation
 
 import android.animation.Animator
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import junit.framework.Assert.fail
 import org.junit.After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 37dea11..791563a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -16,12 +16,13 @@
 
 package com.android.systemui.media.controls.ui.controller
 
-import android.test.suitebuilder.annotation.SmallTest
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.controls.ui.view.MediaHost
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
index b4f5528..4101c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.notetask
 
 import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index e09c804..2c86a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -19,7 +19,7 @@
 import android.app.role.RoleManager
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index ebd34de..231b333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -25,8 +25,8 @@
 import android.hardware.input.InputSettings
 import android.os.UserHandle
 import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 4547bff..9429725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -42,11 +42,12 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 2bdad2b..cae170f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -38,18 +38,19 @@
 import android.provider.Settings;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.settingslib.fuelgauge.Estimate;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.power.PowerUI.WarningsUI;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 0275643..e50320d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -1,7 +1,7 @@
 package com.android.systemui.qs
 
 import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableResources
 import android.view.ContextThemeWrapper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 718e302f..0abcc64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -42,7 +42,6 @@
 import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -52,6 +51,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 0eae5aa..98adbb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -31,12 +31,12 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
 import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
index c43c3e6..29e2a8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
@@ -16,9 +16,9 @@
 
 import static junit.framework.Assert.assertEquals;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 33f8f1f..ef979d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -31,7 +31,7 @@
 import android.os.Parcel
 import android.service.quicksettings.IQSTileService
 import android.service.quicksettings.Tile
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.IWindowManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
index b8e6403..eb013c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
@@ -19,12 +19,12 @@
 
 import android.content.res.ColorStateList;
 import android.service.quicksettings.Tile;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 8142456..0a36ae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -56,9 +56,9 @@
 import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.TileService;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 28331bb..0ff29db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -34,8 +34,8 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index d011821..248af1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -34,11 +34,12 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.quicksettings.IQSTileService;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.QSHost;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 512ca53..ecbd0f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -385,7 +385,7 @@
     }
 
     @Test
-    fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotCreateEffect() {
+    fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
         val state = QSTile.State() // A state that handles longPress
 
         // GIVEN an invalid long-press effect duration
@@ -399,7 +399,7 @@
     }
 
     @Test
-    fun onStateChange_longPressEffectActive_withValidDuration_createsEffect() {
+    fun onStateChange_longPressEffectActive_withValidDuration_initializesEffect() {
         // GIVEN a test state that handles long-press and a valid long-press effect duration
         val state = QSTile.State()
 
@@ -420,7 +420,7 @@
         tileView.changeState(state)
 
         // THEN the view binder no longer binds the view to the long-press effect
-        assertThat(tileView.longPressEffectHandle).isNull()
+        assertThat(tileView.isLongPressEffectBound).isFalse()
     }
 
     @Test
@@ -435,7 +435,7 @@
         tileView.changeState(state)
 
         // THEN the view is bounded to the long-press effect
-        assertThat(tileView.longPressEffectHandle).isNotNull()
+        assertThat(tileView.isLongPressEffectBound).isTrue()
     }
 
     @Test
@@ -451,7 +451,7 @@
         tileView.changeState(state)
 
         // THEN the view binder does not bind the view and no effect is initialized
-        assertThat(tileView.longPressEffectHandle).isNull()
+        assertThat(tileView.isLongPressEffectBound).isFalse()
         assertThat(tileView.isLongPressEffectInitialized).isFalse()
     }
 
@@ -470,7 +470,7 @@
         tileView.changeState(state)
 
         // THEN the view binder does not bind the view and no effect is initialized
-        assertThat(tileView.longPressEffectHandle).isNull()
+        assertThat(tileView.isLongPressEffectBound).isFalse()
         assertThat(tileView.isLongPressEffectInitialized).isFalse()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 440270b..c02fca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -27,13 +27,13 @@
 import android.content.pm.PackageManager;
 import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +43,7 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
 import com.android.systemui.statusbar.policy.RotationLockController;
@@ -57,7 +58,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index 8841f48..3cb48d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -15,7 +15,7 @@
 package com.android.systemui.shared.animation
 
 import android.graphics.Point
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.view.Display
 import android.view.Surface.ROTATION_0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index c39b29f..e9222c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -37,8 +37,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 02954b8..7ddf7a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -26,9 +26,9 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
index 16eb1d9..b18b7f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.commandline
 
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 5bc75e8..7e88ae0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.connectivity
 
 import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.Lifecycle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 44e3bb4..7bd77a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -22,14 +22,14 @@
 
 import android.os.HandlerThread;
 import android.telephony.SubscriptionInfo;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
index a226ded..7aed4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.connectivity
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
 import junit.framework.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index f667b83..461d804 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -39,11 +39,12 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.settingslib.SignalIcon.MobileIconGroup;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index f6f939a..3bbf06d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -19,10 +19,11 @@
 import static junit.framework.Assert.assertEquals;
 
 import android.net.NetworkCapabilities;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 375ca063..35609a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -33,17 +33,18 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.res.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
index 9e73487..5bf0a94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.connectivity
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 2b94561..6b2ee76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -19,12 +19,13 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.widget.FrameLayout;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index fda8f51..fc4702c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -42,10 +42,11 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index 0e24ed4..eafa78e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -25,13 +25,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index a6381d1..5b72ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -25,10 +25,11 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -38,7 +39,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-
 @SmallTest
 @org.junit.runner.RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index 2ef4374..2d8e692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,8 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 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.anyBoolean;
@@ -30,7 +30,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.UiThreadTest;
 import android.util.FloatProperty;
@@ -38,6 +37,8 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.app.animation.Interpolators;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 9c20e54..ffb8646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -45,7 +45,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.UiThreadTest;
 import android.view.LayoutInflater;
@@ -53,6 +52,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
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 3c1f559..97cb11e2 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
@@ -65,7 +65,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
@@ -73,11 +72,13 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index f31b1c4..13ced92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -55,20 +55,20 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.testing.UiThreadTest;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -82,8 +82,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.HashSet;
-import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 0a15f0d..4a91cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -16,16 +16,23 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableResources;
 import android.testing.UiThreadTest;
 import android.util.KeyValueListParser;
 
-import com.android.systemui.res.R;
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.res.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -33,12 +40,6 @@
 
 import java.util.ArrayList;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.mock;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @UiThreadTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index ccedd36..51665d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -41,7 +41,6 @@
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.SpannableString;
@@ -50,10 +49,12 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
@@ -65,8 +66,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.HashSet;
-import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 912ecb3..3a427f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -22,6 +22,8 @@
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,8 +38,6 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import android.metrics.LogMaker;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
@@ -56,8 +56,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -126,7 +124,6 @@
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
     protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock private NotificationGutsManager mNotificationGutsManager;
     @Mock private NotificationsController mNotificationsController;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -193,8 +190,6 @@
         allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
-
         when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
         when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
     }
@@ -299,36 +294,11 @@
 
     @Test
     @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
+    public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
 
-        // WHEN the flag is off and *only* CentralSurfaces has bouncer as showing
-        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
-        mController.setBouncerShowingFromCentralSurfaces(true);
-        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-
-        setupShowEmptyShadeViewState(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-
-        // THEN the CentralSurfaces value is used. Since the bouncer is showing, we hide the empty
-        // view.
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ false,
-                /* areNotificationsHiddenInShade= */ false);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-
-        // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as showing
-        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
         when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-        mController.setBouncerShowingFromCentralSurfaces(false);
 
         setupShowEmptyShadeViewState(true);
         reset(mNotificationStackScrollLayout);
@@ -343,36 +313,11 @@
 
     @Test
     @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
+    public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
 
-        // WHEN the flag is off and *only* CentralSurfaces has bouncer as not showing
-        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
-        mController.setBouncerShowingFromCentralSurfaces(false);
-        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-
-        setupShowEmptyShadeViewState(true);
-        reset(mNotificationStackScrollLayout);
-        mController.updateShowEmptyShadeView();
-
-        // THEN the CentralSurfaces value is used. Since the bouncer isn't showing, we can show the
-        // empty view.
-        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                /* visible= */ true,
-                /* areNotificationsHiddenInShade= */ false);
-    }
-
-    @Test
-    @DisableFlags(FooterViewRefactor.FLAG_NAME)
-    public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        initController(/* viewIsAttached= */ true);
-
-        // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as not showing
-        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
         when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-        mController.setBouncerShowingFromCentralSurfaces(true);
 
         setupShowEmptyShadeViewState(true);
         reset(mNotificationStackScrollLayout);
@@ -1018,7 +963,6 @@
                 mStackLogger,
                 mLogger,
                 mNotificationStackSizeCalculator,
-                mFeatureFlags,
                 mNotificationTargetsHelper,
                 mSecureSettings,
                 mock(NotificationDismissibilityProvider.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e9ec323..f49dc98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -37,12 +37,13 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
 import android.view.ViewRootImpl;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 6fecbb0..7cb41f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -34,8 +34,8 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 6150253..4dd97bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.statusbar.phone
 
 import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b7a3b30..b5525b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -26,6 +26,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
 import android.os.ParcelUuid
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
@@ -46,8 +47,10 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -64,10 +67,14 @@
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -97,7 +104,11 @@
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
 
     private val flags =
-        FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+        FakeFeatureFlagsClassic().also {
+            it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+            it.set(Flags.INSTANT_TETHER, true)
+            it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+        }
 
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -114,9 +125,17 @@
     @Mock private lateinit var summaryLogger: TableLogBuffer
     @Mock private lateinit var logBufferFactory: TableLogBufferFactory
     @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var wifiManager: WifiManager
+    @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+    @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+    @Mock private lateinit var wifiTableLogBuffer: TableLogBuffer
 
     private val mobileMappings = FakeMobileMappingsProxy()
     private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+    private val mainExecutor = FakeExecutor(FakeSystemClock())
+    private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
+    private val wifiPickerTrackerCallback =
+        argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
 
     private val dispatcher = StandardTestDispatcher()
     private val testScope = TestScope(dispatcher)
@@ -139,6 +158,9 @@
             mock<TableLogBuffer>()
         }
 
+        whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+            .thenReturn(wifiPickerTracker)
+
         // For convenience, set up the subscription info callbacks
         whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
             when (invocation.getArgument(0) as Int) {
@@ -165,15 +187,14 @@
 
         wifiRepository =
             WifiRepositoryImpl(
-                fakeBroadcastDispatcher,
-                connectivityManager,
-                connectivityRepository,
-                mock(),
-                mock(),
-                FakeExecutor(FakeSystemClock()),
-                dispatcher,
+                flags,
                 testScope.backgroundScope,
-                mock(),
+                mainExecutor,
+                dispatcher,
+                wifiPickerTrackerFactory,
+                wifiManager,
+                wifiLogBuffer,
+                wifiTableLogBuffer,
             )
 
         carrierConfigRepository =
@@ -278,7 +299,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.subscriptions)
 
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -291,7 +312,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.subscriptions)
 
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -445,7 +466,7 @@
             collectLastValue(underTest.subscriptions)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -462,7 +483,7 @@
             collectLastValue(underTest.subscriptions)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -479,7 +500,7 @@
             collectLastValue(underTest.subscriptions)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -491,7 +512,7 @@
 
             // WHEN the wifi network updates to be not carrier merged
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            setWifiState(isCarrierMerged = false)
             runCurrent()
 
             // THEN the repos update
@@ -507,7 +528,7 @@
             collectLastValue(underTest.subscriptions)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            setWifiState(isCarrierMerged = false)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -520,7 +541,7 @@
 
             // WHEN the wifi network updates to be carrier merged
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             runCurrent()
 
             // THEN the repos update
@@ -562,7 +583,7 @@
             collectLastValue(underTest.subscriptions)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -845,7 +866,7 @@
             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            setWifiState(isCarrierMerged = true)
 
             assertThat(latest).isTrue()
         }
@@ -867,7 +888,7 @@
             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            setWifiState(isCarrierMerged = true)
 
             assertThat(latest).isTrue()
         }
@@ -890,7 +911,7 @@
             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            setWifiState(isCarrierMerged = true)
 
             assertThat(latest).isTrue()
         }
@@ -912,7 +933,7 @@
             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            setWifiState(isCarrierMerged = true)
 
             assertThat(latest).isTrue()
         }
@@ -946,7 +967,7 @@
                 }
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+            setWifiState(isCarrierMerged = true)
 
             // THEN there's a carrier merged connection
             assertThat(latest).isTrue()
@@ -982,7 +1003,7 @@
                 }
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+            setWifiState(isCarrierMerged = true)
 
             // THEN there's a carrier merged connection
             assertThat(latest).isTrue()
@@ -1005,7 +1026,7 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             // BUT the wifi repo has gotten updates that it *is* carrier merged
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
 
             // THEN hasCarrierMergedConnection is true
             assertThat(latest).isTrue()
@@ -1026,7 +1047,7 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             // BUT the wifi repo has gotten updates that it *is* carrier merged
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
 
             // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
             // takes precedence over the wifi network being carrier merged.)
@@ -1048,7 +1069,7 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             // BUT the wifi repo has gotten updates that it *is* carrier merged
-            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
             // AND we're in airplane mode
             airplaneModeRepository.setIsAirplaneMode(true)
 
@@ -1277,12 +1298,26 @@
         return callbackCaptor.value!!
     }
 
-    // Note: This is used to update the [WifiRepository].
-    private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback {
-        runCurrent()
-        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
-        verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
+    private fun setWifiState(isCarrierMerged: Boolean) {
+        if (isCarrierMerged) {
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                    whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+        } else {
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
+        }
+        wifiPickerTrackerCallback.value.onWifiEntriesChanged()
     }
 
     private fun TestScope.getSubscriptionCallback():
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 3126362..f8d50f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -68,14 +68,14 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
+class WifiRepositoryImplTest : SysuiTestCase() {
 
     // Using lazy means that the class will only be constructed once it's fetched. Because the
     // repository internally sets some values on construction, we need to set up some test
     // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
     // inside each test case without needing to manually recreate the repository.
-    private val underTest: WifiRepositoryViaTrackerLib by lazy {
-        WifiRepositoryViaTrackerLib(
+    private val underTest: WifiRepositoryImpl by lazy {
+        WifiRepositoryImpl(
             featureFlags,
             testScope.backgroundScope,
             executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
new file mode 100644
index 0000000..f1dbee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BatteryControllerStartableTest extends SysuiTestCase {
+
+    private BatteryController mBatteryController;
+    private BatteryControllerStartable mBatteryControllerStartable;
+    private MockitoSession mMockitoSession;
+    private FakeExecutor mExecutor;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+
+    @Before
+    public void setUp() throws IllegalStateException {
+        MockitoAnnotations.initMocks(this);
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(BatterySaverUtils.class)
+                .startMocking();
+
+        mExecutor = new FakeExecutor(new FakeSystemClock());
+
+        mBatteryController = new BatteryControllerImpl(getContext(),
+                mock(EnhancedEstimates.class),
+                mock(PowerManager.class),
+                mock(BroadcastDispatcher.class),
+                mock(DemoModeController.class),
+                mock(DumpManager.class),
+                mock(BatteryControllerLogger.class),
+                new Handler(Looper.getMainLooper()),
+                new Handler(Looper.getMainLooper()));
+        mBatteryController.init();
+
+        mBatteryControllerStartable = new BatteryControllerStartable(mBatteryController,
+                mBroadcastDispatcher, mExecutor);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+    public void start_flagEnabled_registersListeners() {
+        mBatteryControllerStartable.start();
+        mExecutor.runAllReady();
+
+        verify(mBroadcastDispatcher).registerReceiver(any(), any());
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+    public void start_flagDisabled_doesNotRegistersListeners() {
+        mBatteryControllerStartable.start();
+        mExecutor.runAllReady();
+
+        verifyZeroInteractions(mBroadcastDispatcher);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index a5c766d..9d4f1fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -36,11 +36,12 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.dx.mockito.inline.extended.StaticInOrder;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index 777fa28..1c54263 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -20,7 +20,7 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraManager
 import android.hardware.camera2.impl.CameraMetadataNative
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index cb6ce68..9bb7607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -44,8 +44,8 @@
 import android.os.Handler;
 import android.os.UserManager;
 import android.security.IKeyChainService;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index dc0d07c..b9557d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -23,14 +23,15 @@
 import android.app.RemoteInput;
 import android.os.Handler;
 import android.provider.DeviceConfig;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
new file mode 100644
index 0000000..b1df159
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 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.surfaceeffects.revealeffect
+
+import android.graphics.RenderEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.model.SysUiStateTest
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RippleRevealEffectTest : SysUiStateTest() {
+
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+    @Test
+    fun play_triggersDrawCallback() {
+        var effectFromCallback: RenderEffect? = null
+        val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+        val drawCallback =
+            object : RenderEffectDrawCallback {
+                override fun onDraw(renderEffect: RenderEffect) {
+                    effectFromCallback = renderEffect
+                }
+            }
+        val revealEffect = RippleRevealEffect(revealEffectConfig, drawCallback)
+        assertThat(effectFromCallback).isNull()
+
+        revealEffect.play()
+
+        animatorTestRule.advanceTimeBy(500L)
+
+        assertThat(effectFromCallback).isNotNull()
+    }
+
+    @Test
+    fun play_triggersStateChangedCallback() {
+        val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+        val drawCallback =
+            object : RenderEffectDrawCallback {
+                override fun onDraw(renderEffect: RenderEffect) {}
+            }
+        var animationStartedCalled = false
+        var animationEndedCalled = false
+        val stateChangedCallback =
+            object : RippleRevealEffect.AnimationStateChangedCallback {
+                override fun onAnimationStart() {
+                    animationStartedCalled = true
+                }
+
+                override fun onAnimationEnd() {
+                    animationEndedCalled = true
+                }
+            }
+        val revealEffect =
+            RippleRevealEffect(revealEffectConfig, drawCallback, stateChangedCallback)
+
+        assertThat(animationStartedCalled).isFalse()
+        assertThat(animationEndedCalled).isFalse()
+
+        revealEffect.play()
+
+        assertThat(animationStartedCalled).isTrue()
+
+        animatorTestRule.advanceTimeBy(revealEffectConfig.duration.toLong())
+
+        assertThat(animationEndedCalled).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
index a85ae7df..35f9c41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -3,7 +3,7 @@
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
index 900d792..2436725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
@@ -22,9 +22,9 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index b10f16c..ab52c34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -25,9 +25,10 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.Sensor;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index b0bd83e..741b2e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -27,10 +27,11 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.media.AudioManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 8b1a1d9..e2386a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import com.android.keyguard.logging.DeviceEntryIconLogger
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
@@ -29,5 +31,6 @@
         deviceEntryIconViewModel = deviceEntryIconViewModel,
         alternateBouncerInteractor = alternateBouncerInteractor,
         systemUIDialogManager = systemUIDialogManager,
+        logger = mock<DeviceEntryIconLogger>(),
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 636d509..24603ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
 
 val Kosmos.qsLongPressEffect by
-    Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor, testScope) }
+    Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor) }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 7ceb3bb..c96688c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -28,7 +28,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
@@ -43,6 +42,7 @@
 import android.service.autofill.SaveRequest;
 import android.text.format.DateUtils;
 import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
 
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.ServiceConnector;
@@ -283,7 +283,8 @@
         return callback;
     }
 
-    public void onFillCredentialRequest(@NonNull FillRequest request, IBinder autofillCallback) {
+    public void onFillCredentialRequest(@NonNull FillRequest request,
+            IAutoFillManagerClient autofillCallback) {
         if (sVerbose) {
             Slog.v(TAG, "onFillRequest:" + request);
         }
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 044a064..ce9d180 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -21,11 +21,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentSender;
-import android.os.IBinder;
 import android.service.autofill.ConvertCredentialResponse;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
 
 /**
  * Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -105,7 +105,8 @@
     /**
      * Requests a new fill response.
      */
-    public void onFillRequest(FillRequest pendingFillRequest, int flag, IBinder client) {
+    public void onFillRequest(FillRequest pendingFillRequest, int flag,
+            IAutoFillManagerClient client) {
         Slog.v(TAG, "Requesting fill response to secondary provider.");
         mLastFlag = flag;
         if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cd1ef88..3a38406 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -757,14 +757,13 @@
                             mPendingInlineSuggestionsRequest, id);
                 }
                 mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
-                        mPendingFillRequest.getFlags(), mClient.asBinder());
+                        mPendingFillRequest.getFlags(), mClient);
             } else if (mRemoteFillService != null) {
                 if (mIsPrimaryCredential) {
                     mPendingFillRequest = addCredentialManagerDataToClientState(
                             mPendingFillRequest,
                             mPendingInlineSuggestionsRequest, id);
-                    mRemoteFillService.onFillCredentialRequest(mPendingFillRequest,
-                            mClient.asBinder());
+                    mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
                 } else {
                     mRemoteFillService.onFillRequest(mPendingFillRequest);
                 }
@@ -2898,7 +2897,7 @@
                     + ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
         }
         if (Flags.autofillCredmanDevIntegration() && exception != null
-                && !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) {
+                && exception instanceof GetCredentialException) {
             if (dataset != null && dataset.getFieldIds().size() == 1) {
                 if (sDebug) {
                     Slog.d(TAG, "setAuthenticationResultLocked(): result returns with"
@@ -6495,15 +6494,21 @@
                     }
                 }
                 if (exception != null) {
-                    if (viewId.isVirtualInt()) {
-                        sendResponseToViewNode(viewId, /*response=*/ null, exception);
-                    } else {
-                        mClient.onGetCredentialException(id, viewId, exception.getType(),
-                                exception.getMessage());
-                    }
+                    mClient.onGetCredentialException(id, viewId, exception.getType(),
+                            exception.getMessage());
                 } else if (response != null) {
                     if (viewId.isVirtualInt()) {
-                        sendResponseToViewNode(viewId, response, /*exception=*/ null);
+                        ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+                        if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
+                            Bundle resultData = new Bundle();
+                            resultData.putParcelable(
+                                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                    response);
+                            viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
+                                        resultData);
+                        } else {
+                            Slog.w(TAG, "View node not found after GetCredentialResponse");
+                        }
                     } else {
                         mClient.onGetCredentialResponse(id, viewId, response);
                     }
@@ -6517,30 +6522,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response,
-            GetCredentialException exception) {
-        ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
-        if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
-            Bundle resultData = new Bundle();
-            if (response != null) {
-                resultData.putParcelable(
-                        CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
-                        response);
-                viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
-                        resultData);
-            } else if (exception != null) {
-                resultData.putStringArray(
-                        CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
-                        new String[] {exception.getType(), exception.getMessage()});
-                viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR,
-                        resultData);
-            }
-        } else {
-            Slog.w(TAG, "View node not found after GetCredentialResponse");
-        }
-    }
-
     void autoFillApp(Dataset dataset) {
         synchronized (mLock) {
             if (mDestroyed) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 1f09d4d..d09d7e6 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -130,7 +130,7 @@
     private final @NonNull PackageManagerInternal mPackageManagerInternal;
     private final @NonNull AssociationStore mAssociationStore;
     @NonNull
-    private final ComponentName mCompanionDeviceActivity;
+    private final ComponentName mCompanionAssociationActivity;
 
     public AssociationRequestsProcessor(@NonNull Context context,
             @NonNull PackageManagerInternal packageManagerInternal,
@@ -138,9 +138,9 @@
         mContext = context;
         mPackageManagerInternal = packageManagerInternal;
         mAssociationStore = associationStore;
-        mCompanionDeviceActivity = createRelative(
+        mCompanionAssociationActivity = createRelative(
                 mContext.getString(R.string.config_companionDeviceManagerPackage),
-                ".CompanionDeviceActivity");
+                ".CompanionAssociationActivity");
     }
 
     /**
@@ -204,7 +204,7 @@
         extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
 
         final Intent intent = new Intent();
-        intent.setComponent(mCompanionDeviceActivity);
+        intent.setComponent(mCompanionAssociationActivity);
         intent.putExtras(extras);
 
         // 2b.3. Create a PendingIntent.
@@ -232,7 +232,7 @@
         extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
 
         final Intent intent = new Intent();
-        intent.setComponent(mCompanionDeviceActivity);
+        intent.setComponent(mCompanionAssociationActivity);
         intent.putExtras(extras);
 
         return createPendingIntent(packageUid, intent);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 392c0c7..9922c5f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -145,6 +145,7 @@
     defaults: [
         "platform_service_defaults",
         "android.hardware.power-java_shared",
+        "latest_android_hardware_broadcastradio_java_static",
     ],
     srcs: [
         ":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -207,7 +208,6 @@
         "android.hardware.boot-V1.2-java", // HIDL
         "android.hardware.boot-V1-java", // AIDL
         "android.hardware.broadcastradio-V2.0-java", // HIDL
-        "android.hardware.broadcastradio-V2-java", // AIDL
         "android.hardware.health-V1.0-java", // HIDL
         "android.hardware.health-V2.0-java", // HIDL
         "android.hardware.health-V2.1-java", // HIDL
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c11fbe1..add8491 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -176,6 +176,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.HwBinder;
 import android.os.IBinder;
 import android.os.Looper;
@@ -686,6 +687,9 @@
     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
 
+    // Handler for broadcast receiver
+    // TODO(b/335513647) combine handlers
+    private final HandlerThread mBroadcastHandlerThread;
     // Broadcast receiver for device connections intent broadcasts
     private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
 
@@ -1121,6 +1125,9 @@
         mAudioPolicy = audioPolicy;
         mPlatformType = AudioSystem.getPlatformType(context);
 
+        mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
+        mBroadcastHandlerThread.start();
+
         mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
 
         mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1507,7 +1514,8 @@
         intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
         intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
+        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null,
+                mBroadcastHandlerThread.getThreadHandler(),
                 Context.RECEIVER_EXPORTED);
 
         SubscriptionManager subscriptionManager = mContext.getSystemService(
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 7a9491e..92fd9cb 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -9,8 +9,8 @@
 }
 
 flag {
-    name: "de_hidl"
-    namespace: "biometrics_framework"
-    description: "feature flag for biometrics de-hidl"
-    bug: "287332354"
-}
\ No newline at end of file
+  name: "use_vhal_for_testing"
+  namespace: "biometrics_framework"
+  description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
+  bug: "294254230"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index beb3f2f..9c8d98d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IVirtualHal;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
@@ -59,6 +60,7 @@
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.BiometricHandlerProvider;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -133,6 +135,8 @@
     @Nullable private ISidefpsController mSidefpsController;
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    @Nullable private IVirtualHal mVhal;
+    @Nullable private String mHalInstanceNameCurrent;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -293,10 +297,29 @@
     @VisibleForTesting
     synchronized IFingerprint getHalInstance() {
         if (mTestHalEnabled) {
-            // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
-            // the test HAL for all sensors under that HAL. This can be updated in the future if
-            // necessary.
-            return new TestHal();
+            if (Flags.useVhalForTesting()) {
+                if (!mHalInstanceNameCurrent.contains("virtual")) {
+                    Slog.i(getTag(), "Switching fingerprint hal from " + mHalInstanceName
+                            + " to virtual hal");
+                    mHalInstanceNameCurrent = "virtual";
+                    mDaemon = null;
+                }
+            } else {
+                // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+                // the test HAL for all sensors under that HAL. This can be updated in the future if
+                // necessary.
+                return new TestHal();
+            }
+        } else {
+            if (mHalInstanceNameCurrent == null) {
+                mHalInstanceNameCurrent = mHalInstanceName;
+            } else if (mHalInstanceNameCurrent.contains("virtual")
+                    && mHalInstanceNameCurrent != mHalInstanceName) {
+                Slog.i(getTag(), "Switching fingerprint from virtual hal " + "to "
+                        + mHalInstanceName);
+                mHalInstanceNameCurrent = mHalInstanceName;
+                mDaemon = null;
+            }
         }
 
         if (mDaemon != null) {
@@ -308,7 +331,7 @@
         mDaemon = IFingerprint.Stub.asInterface(
                 Binder.allowBlocking(
                         ServiceManager.waitForDeclaredService(
-                                IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)));
+                                IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent)));
         if (mDaemon == null) {
             Slog.e(getTag(), "Unable to get daemon");
             return null;
@@ -952,4 +975,26 @@
     public void sendFingerprintReEnrollNotification() {
         mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
     }
+
+    /**
+     * Return virtual hal AIDL interface if it is used for testing
+     *
+     */
+    public IVirtualHal getVhal() throws RemoteException {
+        if (mVhal == null && useVhalForTesting()) {
+            mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
+            if (mVhal == null) {
+                Slog.e(getTag(), "Unable to get virtual hal interface");
+            }
+        }
+
+        return mVhal;
+    }
+
+    /**
+     * Return true if vhal_for_testing feature is enabled and test is active
+     */
+    public boolean useVhalForTesting() {
+        return (Flags.useVhalForTesting() && mTestHalEnabled);
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index cfdb75f..896670e4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1406,42 +1406,47 @@
             }
             setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         }
-        // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
-        if (!mFlags.isRefactorDisplayPowerControllerEnabled() && (Float.isNaN(brightnessState)
-                || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD)) {
-            if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
-                brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
-                        mTempBrightnessEvent);
-                if (BrightnessUtils.isValidBrightnessValue(brightnessState)
-                        || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
-                    rawBrightnessState = mAutomaticBrightnessController
-                            .getRawAutomaticScreenBrightness();
-                    brightnessState = clampScreenBrightness(brightnessState);
-                    // slowly adapt to auto-brightness
-                    // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
-                    slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
-                            && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
-                    brightnessAdjustmentFlags =
-                            mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
-                    updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
-                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
-                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
-                    if (mScreenOffBrightnessSensorController != null) {
-                        mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+
+        if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+            // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+            if (Float.isNaN(brightnessState)
+                    || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
+                if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+                    brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+                            mTempBrightnessEvent);
+                    if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+                            || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+                        rawBrightnessState = mAutomaticBrightnessController
+                                .getRawAutomaticScreenBrightness();
+                        // slowly adapt to auto-brightness
+                        // TODO(b/253226419): slowChange should be decided by
+                        // strategy.updateBrightness
+                        slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+                                && !mAutomaticBrightnessStrategy
+                                .getAutoBrightnessAdjustmentChanged();
+                        brightnessAdjustmentFlags =
+                                mAutomaticBrightnessStrategy
+                                        .getAutoBrightnessAdjustmentReasonsFlags();
+                        updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+                        mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+                        if (mScreenOffBrightnessSensorController != null) {
+                            mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+                        }
+                        setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+                    } else {
+                        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+                        // Restore the lower-priority brightness strategy
+                        brightnessState = displayBrightnessState.getBrightness();
                     }
-                    setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-                } else {
-                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
-                    // Restore the lower-priority brightness strategy
-                    brightnessState = displayBrightnessState.getBrightness();
                 }
+            } else {
+                mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
             }
-        } else {
-            // Any non-auto-brightness values such as override or temporary should still be subject
-            // to clamping so that they don't go beyond the current max as specified by Brightness
-            // Range Controller.
+        }
+
+        if (!Float.isNaN(brightnessState)) {
             brightnessState = clampScreenBrightness(brightnessState);
-            mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
 
         // If there's an offload session, we need to set the initial doze brightness before
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index aa18175..def6d6c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5856,6 +5856,7 @@
             @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
                 p.println("  " + c + ":");
                 p.println("    client=" + c.mClient);
+
                 p.println("    fallbackInputConnection="
                         + c.mFallbackInputConnection);
                 p.println("    sessionRequested="
@@ -5864,6 +5865,9 @@
                         "    sessionRequestedForAccessibility="
                                 + c.mSessionRequestedForAccessibility);
                 p.println("    curSession=" + c.mCurSession);
+                p.println("    selfReportedDisplayId=" + c.mSelfReportedDisplayId);
+                p.println("    uid=" + c.mUid);
+                p.println("    pid=" + c.mPid);
             };
             mClientController.forAllClients(clientControllerDump);
             p.println("  mCurrentUserId=" + mCurrentUserId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b48cad2..ebea05d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2514,7 +2514,8 @@
                 mNotificationChannelLogger,
                 mAppOps,
                 mUserProfiles,
-                mShowReviewPermissionsNotification);
+                mShowReviewPermissionsNotification,
+                Clock.systemUTC());
         mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
                 mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
         mSnoozeHelper = snoozeHelper;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 461bd9c..1f2ad07e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -93,6 +93,8 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -113,6 +115,8 @@
     private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
     @VisibleForTesting
     static final int UNKNOWN_UID = UserHandle.USER_NULL;
+    // The amount of time pacakage preferences can exist without the app being installed.
+    private static final long PREF_GRACE_PERIOD_MS = Duration.ofDays(2).toMillis();
 
     @VisibleForTesting
     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
@@ -149,6 +153,8 @@
     private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
     private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
 
+    private static final String ATT_CREATION_TIME = "creation_time";
+
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -208,11 +214,13 @@
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
     private final boolean mShowReviewPermissionsNotification;
 
+    Clock mClock;
+
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
             ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
             NotificationChannelLogger notificationChannelLogger,
             AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
-            boolean showReviewPermissionsNotification) {
+            boolean showReviewPermissionsNotification, Clock clock) {
         mContext = context;
         mZenModeHelper = zenHelper;
         mRankingHandler = rankingHandler;
@@ -225,7 +233,7 @@
         mShowReviewPermissionsNotification = showReviewPermissionsNotification;
         mIsMediaNotificationFilteringEnabled = context.getResources()
                 .getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
-
+        mClock = clock;
         XML_VERSION = 4;
 
         updateBadgingEnabled();
@@ -309,7 +317,7 @@
                     parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
                     parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
                     parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
-                    bubblePref);
+                    bubblePref, parser.getAttributeLong(null, ATT_CREATION_TIME, mClock.millis()));
             r.bubblePreference = bubblePref;
             r.priority = parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY);
             r.visibility = parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY);
@@ -463,12 +471,12 @@
         // TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE
         return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
-                DEFAULT_BUBBLE_PREFERENCE);
+                DEFAULT_BUBBLE_PREFERENCE, mClock.millis());
     }
 
     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
             @UserIdInt int userId, int uid, int importance, int priority, int visibility,
-            boolean showBadge, int bubblePreference) {
+            boolean showBadge, int bubblePreference, long creationTime) {
         final String key = packagePreferencesKey(pkg, uid);
         PackagePreferences
                 r = (uid == UNKNOWN_UID)
@@ -483,6 +491,11 @@
             r.visibility = visibility;
             r.showBadge = showBadge;
             r.bubblePreference = bubblePreference;
+            if (Flags.persistIncompleteRestoreData()) {
+                if (r.uid == UNKNOWN_UID) {
+                    r.creationTime = creationTime;
+                }
+            }
 
             try {
                 createDefaultChannelIfNeededLocked(r);
@@ -496,6 +509,12 @@
                 mPackagePreferences.put(key, r);
             }
         }
+        if (r.uid == UNKNOWN_UID) {
+            if (Flags.persistIncompleteRestoreData()
+                    && PREF_GRACE_PERIOD_MS < (mClock.millis() - r.creationTime)) {
+                mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
+            }
+        }
         return r;
     }
 
@@ -590,70 +609,16 @@
                 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
                     continue;
                 }
-                out.startTag(null, TAG_PACKAGE);
-                out.attribute(null, ATT_NAME, r.pkg);
-                if (!notifPermissions.isEmpty()) {
-                    Pair<Integer, String> app = new Pair(r.uid, r.pkg);
-                    final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
-                    out.attributeInt(null, ATT_IMPORTANCE,
-                            permission != null && permission.first ? IMPORTANCE_DEFAULT
-                                    : IMPORTANCE_NONE);
-                    notifPermissions.remove(app);
-                } else {
-                    if (r.importance != DEFAULT_IMPORTANCE) {
-                        out.attributeInt(null, ATT_IMPORTANCE, r.importance);
-                    }
+                writePackageXml(r, out, notifPermissions, forBackup);
+            }
+        }
+        if (Flags.persistIncompleteRestoreData() && !forBackup) {
+            synchronized (mRestoredWithoutUids) {
+                final int N = mRestoredWithoutUids.size();
+                for (int i = 0; i < N; i++) {
+                    final PackagePreferences r = mRestoredWithoutUids.valueAt(i);
+                    writePackageXml(r, out, notifPermissions, false);
                 }
-                if (r.priority != DEFAULT_PRIORITY) {
-                    out.attributeInt(null, ATT_PRIORITY, r.priority);
-                }
-                if (r.visibility != DEFAULT_VISIBILITY) {
-                    out.attributeInt(null, ATT_VISIBILITY, r.visibility);
-                }
-                if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
-                    out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
-                }
-                out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
-                out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
-                        r.lockedAppFields);
-                out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
-                        r.hasSentInvalidMessage);
-                out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
-                        r.hasSentValidMessage);
-                out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
-                        r.userDemotedMsgApp);
-                out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
-
-                if (!forBackup) {
-                    out.attributeInt(null, ATT_UID, r.uid);
-                }
-
-                if (r.delegate != null) {
-                    out.startTag(null, TAG_DELEGATE);
-
-                    out.attribute(null, ATT_NAME, r.delegate.mPkg);
-                    out.attributeInt(null, ATT_UID, r.delegate.mUid);
-                    if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
-                        out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
-                    }
-                    out.endTag(null, TAG_DELEGATE);
-                }
-
-                for (NotificationChannelGroup group : r.groups.values()) {
-                    group.writeXml(out);
-                }
-
-                for (NotificationChannel channel : r.channels.values()) {
-                    if (forBackup) {
-                        if (!channel.isDeleted()) {
-                            channel.writeXmlForBackup(out, mContext);
-                        }
-                    } else {
-                        channel.writeXml(out);
-                    }
-                }
-
-                out.endTag(null, TAG_PACKAGE);
             }
         }
         // Some apps have permissions set but don't have expanded notification settings
@@ -669,6 +634,80 @@
         out.endTag(null, TAG_RANKING);
     }
 
+    public void writePackageXml(PackagePreferences r, TypedXmlSerializer out,
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions,
+            boolean forBackup) throws
+            IOException {
+        out.startTag(null, TAG_PACKAGE);
+        out.attribute(null, ATT_NAME, r.pkg);
+        if (!notifPermissions.isEmpty()) {
+            Pair<Integer, String> app = new Pair(r.uid, r.pkg);
+            final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
+            out.attributeInt(null, ATT_IMPORTANCE,
+                    permission != null && permission.first ? IMPORTANCE_DEFAULT
+                            : IMPORTANCE_NONE);
+            notifPermissions.remove(app);
+        } else {
+            if (r.importance != DEFAULT_IMPORTANCE) {
+                out.attributeInt(null, ATT_IMPORTANCE, r.importance);
+            }
+        }
+        if (r.priority != DEFAULT_PRIORITY) {
+            out.attributeInt(null, ATT_PRIORITY, r.priority);
+        }
+        if (r.visibility != DEFAULT_VISIBILITY) {
+            out.attributeInt(null, ATT_VISIBILITY, r.visibility);
+        }
+        if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
+            out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
+        }
+        out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
+        out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
+                r.lockedAppFields);
+        out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
+                r.hasSentInvalidMessage);
+        out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
+                r.hasSentValidMessage);
+        out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
+                r.userDemotedMsgApp);
+        out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
+
+        if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
+            out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
+        }
+
+        if (!forBackup) {
+            out.attributeInt(null, ATT_UID, r.uid);
+        }
+
+        if (r.delegate != null) {
+            out.startTag(null, TAG_DELEGATE);
+
+            out.attribute(null, ATT_NAME, r.delegate.mPkg);
+            out.attributeInt(null, ATT_UID, r.delegate.mUid);
+            if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
+                out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
+            }
+            out.endTag(null, TAG_DELEGATE);
+        }
+
+        for (NotificationChannelGroup group : r.groups.values()) {
+            group.writeXml(out);
+        }
+
+        for (NotificationChannel channel : r.channels.values()) {
+            if (forBackup) {
+                if (!channel.isDeleted()) {
+                    channel.writeXmlForBackup(out, mContext);
+                }
+            } else {
+                channel.writeXml(out);
+            }
+        }
+
+        out.endTag(null, TAG_PACKAGE);
+    }
+
     /**
      * Sets whether bubbles are allowed.
      *
@@ -2906,6 +2945,7 @@
         boolean hasSentValidBubble = false;
 
         boolean migrateToPm = false;
+        long creationTime;
 
         Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 077ed5a..2859871 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -95,3 +95,9 @@
   bug: "331967355"
 }
 
+flag {
+  name: "persist_incomplete_restore_data"
+  namespace: "systemui"
+  description: "Stores restore data for not-yet-installed pkgs for 48 hours"
+  bug: "334999659"
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b5df30f..e0247d0 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -61,6 +61,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -355,7 +356,17 @@
     private TelephonyManager mTelephony;
     private UwbManager mUwbManager;
     private SubscriptionManager mSubscriptionManager;
-    private NetworkStatsManager mNetworkStatsManager;
+
+    /**
+     * NetworkStatsManager initialization happens from one thread before any worker thread
+     * is going to access the networkStatsManager instance:
+     * - @initNetworkStatsManager() - initialization happens no worker thread to access are
+     *   active yet
+     * - @initAndRegisterNetworkStatsPullers Network stats dependant pullers can only be
+     *   initialized after service is ready. Worker thread is spawn here only after the
+     *   initialization is completed in a thread safe way (no async access expected)
+     */
+    private NetworkStatsManager mNetworkStatsManager = null;
 
     @GuardedBy("mKernelWakelockLock")
     private KernelWakelockReader mKernelWakelockReader;
@@ -420,6 +431,12 @@
     public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
                 addMobileBytesTransferByProcStatePuller();
 
+    /**
+     * Whether or not to enable the mNetworkStatsManager initialization order fix
+     */
+    private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX =
+                statsPullNetworkStatsManagerInitOrderFix();
+
     // Puller locks
     private final Object mDataBytesTransferLock = new Object();
     private final Object mBluetoothBytesTransferLock = new Object();
@@ -824,6 +841,9 @@
             });
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             BackgroundThread.getHandler().post(() -> {
+                if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+                    initNetworkStatsManager();
+                }
                 // Network stats related pullers can only be initialized after service is ready.
                 initAndRegisterNetworkStatsPullers();
                 // For services that are not ready at boot phase PHASE_SYSTEM_SERVICES_READY
@@ -843,7 +863,9 @@
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
-        mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+        if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+            initNetworkStatsManager();
+        }
 
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1019,6 +1041,24 @@
         }
     }
 
+    /**
+     * Calling getNetworkStatsManager() before PHASE_THIRD_PARTY_APPS_CAN_START is unexpected
+     * Callers use before PHASE_THIRD_PARTY_APPS_CAN_START stage is not legit
+     */
+    @NonNull
+    private NetworkStatsManager getNetworkStatsManager() {
+        if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+            if (mNetworkStatsManager == null) {
+                throw new IllegalStateException("NetworkStatsManager is not ready");
+            }
+        }
+        return mNetworkStatsManager;
+    }
+
+    private void initNetworkStatsManager() {
+        mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+    }
+
     private void initAndRegisterNetworkStatsPullers() {
         if (DEBUG) {
             Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1514,11 +1554,11 @@
         //  I/O and also block main thread when polling.
         //  Consider making perfd queries NetworkStatsService directly.
         if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
-            mNetworkStatsManager.forceUpdate();
+            getNetworkStatsManager().forceUpdate();
         }
 
         final android.app.usage.NetworkStats queryNonTaggedStats =
-                mNetworkStatsManager.querySummary(
+                getNetworkStatsManager().querySummary(
                         template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
                         currentTimeInMillis);
 
@@ -1528,7 +1568,7 @@
         if (!includeTags) return nonTaggedStats;
 
         final android.app.usage.NetworkStats queryTaggedStats =
-                mNetworkStatsManager.queryTaggedSummary(template,
+                getNetworkStatsManager().queryTaggedSummary(template,
                         currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
                         currentTimeInMillis);
         final NetworkStats taggedStats =
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 101b98e..c479c6d 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -7,4 +7,12 @@
     description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
     bug: "309512867"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "stats_pull_network_stats_manager_init_order_fix"
+    namespace: "statsd"
+    description: "Fix the mNetworkStatsManager initialization order"
+    bug: "331989853"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 06003e4..54e932a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -821,6 +821,12 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
+    // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+    // aspect ratio. If not null, they are used as parent container in
+    // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+    @Nullable
+    private Rect mLetterboxBoundsForAspectRatio;
+
     // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
     // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
     // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -8442,10 +8448,14 @@
                     fullConfig.windowConfiguration.getRotation());
         }
 
+        final Rect letterboxedContainerBounds =
+                mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+                        ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+                        : mLetterboxBoundsForAspectRatio;
         // The role of CompatDisplayInsets is like the override bounds.
         mCompatDisplayInsets =
                 new CompatDisplayInsets(
-                        mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio,
+                        mDisplayContent, this, letterboxedContainerBounds,
                         mResolveConfigHint.mUseLegacyInsetsForStableBounds);
     }
 
@@ -8518,6 +8528,7 @@
         mIsAspectRatioApplied = false;
         mIsEligibleForFixedOrientationLetterbox = false;
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+        mLetterboxBoundsForAspectRatio = null;
 
         // Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
         // different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -8556,9 +8567,11 @@
                 computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
             }
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
-        // are already calculated in resolveFixedOrientationConfiguration.
+        // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+        // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+        }
+        if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
                 && !mLetterboxUiController.hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
@@ -9056,7 +9069,8 @@
         }
         final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
 
-        if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
+        if (compatDisplayInsets != null
+                && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
             // App prefers to keep its original size.
             // If the size compat is from previous fixed orientation letterboxing, we may want to
             // have fixed orientation letterbox again, otherwise it will show the size compat
@@ -9190,6 +9204,7 @@
             // restrict, the bounds should be the requested override bounds.
             mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
             computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+            mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
         }
     }
 
@@ -10753,10 +10768,10 @@
         /** Whether the {@link Task} windowingMode represents a floating window*/
         final boolean mIsFloating;
         /**
-         * Whether is letterboxed because of fixed orientation when the unresizable activity is
-         * first shown.
+         * Whether is letterboxed because of fixed orientation or aspect ratio when
+         * the unresizable activity is first shown.
          */
-        final boolean mIsInFixedOrientationLetterbox;
+        final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
         /**
          * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
          * is used to compute the appBounds.
@@ -10771,7 +10786,7 @@
 
         /** Constructs the environment to simulate the bounds behavior of the given container. */
         CompatDisplayInsets(DisplayContent display, ActivityRecord container,
-                @Nullable Rect fixedOrientationBounds, boolean useOverrideInsets) {
+                @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
             mOriginalRotation = display.getRotation();
             mIsFloating = container.getWindowConfiguration().tasksAreFloating();
             mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
@@ -10786,22 +10801,22 @@
                     mNonDecorInsets[rotation] = emptyRect;
                     mStableInsets[rotation] = emptyRect;
                 }
-                mIsInFixedOrientationLetterbox = false;
+                mIsInFixedOrientationOrAspectRatioLetterbox = false;
                 return;
             }
 
             final Task task = container.getTask();
 
-            mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
+            mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
 
             // Store the bounds of the Task for the non-resizable activity to use in size compat
             // mode so that the activity will not be resized regardless the windowing mode it is
             // currently in.
-            // When an activity needs to be letterboxed because of fixed orientation, use fixed
-            // orientation bounds instead of task bounds since the activity will be displayed
-            // within these even if it is in size compat mode.
-            final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
-                    ? fixedOrientationBounds
+            // When an activity needs to be letterboxed because of fixed orientation or aspect
+            // ratio, use resolved bounds instead of task bounds since the activity will be
+            // displayed within these even if it is in size compat mode.
+            final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+                    ? letterboxedContainerBounds
                     : task != null ? task.getBounds() : display.getBounds();
             final int filledContainerRotation = task != null
                     ? task.getConfiguration().windowConfiguration.getRotation()
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2c39c58..08baf3b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2524,7 +2524,9 @@
         // If the caller has asked not to resume at this point, we make note
         // of this in the record so that we can skip it when trying to find
         // the top running activity.
-        if (!r.showToCurrentUser() || mLaunchTaskBehind) {
+        final boolean canShowActivity = r.showToCurrentUser();
+        if (!canShowActivity) Slog.w(TAG, "Can't resume non-current user r=" + r);
+        if (!canShowActivity || mLaunchTaskBehind) {
             r.delayedResume = true;
             mDoResume = false;
         } else {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f220c9d..cb5ad91 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1308,7 +1308,8 @@
         }
 
         final boolean shouldShowLetterboxUi =
-                (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+                (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+                        || mActivityRecord.isVisibleRequested())
                 && mainWindow.areAppWindowBoundsLetterboxed()
                 // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
                 // WindowContainer#showWallpaper because the later will return true when this
@@ -1320,12 +1321,6 @@
         return shouldShowLetterboxUi;
     }
 
-    @VisibleForTesting
-    boolean isSurfaceVisible(WindowState mainWindow) {
-        return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
-                || mActivityRecord.isVisibleRequested());
-    }
-
     private Color getLetterboxBackgroundColor() {
         final WindowState w = mActivityRecord.findMainWindow();
         if (w == null || w.isLetterboxedForDisplayCutout()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e1ad979d..7e083ba 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3935,7 +3935,7 @@
         if (policy.mPasswordOwner == oldAdminUid) {
             policy.mPasswordOwner = adminToTransfer.getUid();
         }
-
+        transferSubscriptionOwnership(outgoingReceiver, incomingReceiver);
         saveSettingsLocked(userHandle);
         sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
                 null, null);
@@ -19471,6 +19471,21 @@
                 .write();
     }
 
+    private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
+        if (Flags.esimManagementEnabled()) {
+            SubscriptionManager subscriptionManager = mContext.getSystemService(
+                    SubscriptionManager.class);
+            for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+                try {
+                    subscriptionManager.setGroupOwner(subId, target.getPackageName());
+                } catch (Exception e) {
+                    // Shouldn't happen.
+                    Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
+                }
+            }
+        }
+    }
+
     private void prepareTransfer(ComponentName admin, ComponentName target,
             PersistableBundle bundle, int callingUserId, String adminType) {
         saveTransferOwnershipBundleLocked(bundle, callingUserId);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6df4907..584fd62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -24,6 +24,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -75,6 +76,9 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
 import android.util.SparseBooleanArray;
@@ -98,6 +102,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -154,6 +159,10 @@
     @Mock
     private UsageStatsManagerInternal mUsageStatsManager;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private JobStore mJobStore;
 
     @Before
@@ -1978,7 +1987,7 @@
     }
 
     @Test
-    public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+    public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2030,7 @@
     }
 
     @Test
-    public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+    public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,6 +2176,74 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_COUNT_QUOTA_FIX)
+    public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
+        setDischarging();
+
+        JobStatus jobRunning = createJobStatus(
+                "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
+        JobStatus jobPending = createJobStatus(
+                "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
+        setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
+
+        setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
+
+        long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
+
+        final ExecutionStats stats;
+        synchronized (mQuotaController.mLock) {
+            stats = mQuotaController.getExecutionStatsLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+            assertTrue(mQuotaController
+                    .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+            assertEquals(10, stats.jobCountLimit);
+            assertEquals(9, stats.bgJobCountInWindow);
+        }
+
+        when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
+
+        InOrder inOrder = inOrder(mJobSchedulerService);
+        trackJobs(jobRunning, jobPending);
+        // UID in the background.
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        // Start the job.
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(jobRunning);
+        }
+
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+        // Wait for some extra time to allow for job processing.
+        ArraySet<JobStatus> expected = new ArraySet<>();
+        expected.add(jobPending);
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(eq(expected));
+
+        synchronized (mQuotaController.mLock) {
+            assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
+            assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+            assertTrue(jobRunning.isReady());
+            assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
+            assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+            assertFalse(jobPending.isReady());
+            assertEquals(10, stats.bgJobCountInWindow);
+        }
+
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
+        }
+
+        synchronized (mQuotaController.mLock) {
+            assertFalse(mQuotaController
+                    .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+            assertEquals(10, stats.bgJobCountInWindow);
+        }
+    }
+
+    @Test
     public void testIsWithinQuotaLocked_TimingSession() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4651,7 +4728,7 @@
         // Handler is told to check when the quota will be consumed, not when the initial
         // remaining time is over.
         verify(handler, atLeast(1)).sendMessageDelayed(
-                argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
+                argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
                 eq(10 * SECOND_IN_MILLIS));
         verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
 
@@ -6618,7 +6695,7 @@
         // Handler is told to check when the quota will be consumed, not when the initial
         // remaining time is over.
         verify(handler, atLeast(1)).sendMessageDelayed(
-                argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
+                argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
                 eq(10 * SECOND_IN_MILLIS));
         verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5033a380..9a58594 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,10 +46,13 @@
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_SYSTEM;
 
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
 import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
 import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
@@ -110,6 +113,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.PermissionManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
@@ -140,6 +146,9 @@
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.PermissionHelper.PackagePermission;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -148,6 +157,7 @@
 import org.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -160,6 +170,8 @@
 import java.io.FileNotFoundException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.time.Clock;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -171,7 +183,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 public class PreferencesHelperTest extends UiServiceTestCase {
     private static final int UID_HEADLESS = 1000000;
     private static final UserHandle USER = UserHandle.of(0);
@@ -212,6 +224,22 @@
     private AudioAttributes mAudioAttributes;
     private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+    @Mock
+    Clock mClock;
+
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                FLAG_PERSIST_INCOMPLETE_RESTORE_DATA);
+    }
+
+    public PreferencesHelperTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -326,13 +354,14 @@
             currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
         }
         when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
+        when(mClock.millis()).thenReturn(System.currentTimeMillis());
 
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                false);
+                false, mClock);
         mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                false);
+                false, mClock);
         resetZenModeHelper();
 
         mAudioAttributes = new AudioAttributes.Builder()
@@ -680,7 +709,7 @@
     public void testReadXml_oldXml_migrates() throws Exception {
         mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                /* showReviewPermissionsNotification= */ true);
+                /* showReviewPermissionsNotification= */ true, mClock);
 
         String xml = "<ranking version=\"2\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -816,7 +845,7 @@
     public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
         mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                /* showReviewPermissionsNotification= */ true);
+                /* showReviewPermissionsNotification= */ true, mClock);
 
         String xml = "<ranking version=\"3\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -875,7 +904,7 @@
     public void testReadXml_newXml_permissionNotificationOff() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                /* showReviewPermissionsNotification= */ false);
+                /* showReviewPermissionsNotification= */ false, mClock);
 
         String xml = "<ranking version=\"3\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -934,7 +963,7 @@
     public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                /* showReviewPermissionsNotification= */ true);
+                /* showReviewPermissionsNotification= */ true, mClock);
 
         String xml = "<ranking version=\"4\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1010,7 +1039,8 @@
         when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234);
         final ApplicationInfo app = new ApplicationInfo();
         app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
-        when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app);
+        when(mPm.getApplicationInfoAsUser(
+                eq("something"), anyInt(), eq(USER_SYSTEM))).thenReturn(app);
 
         mXmlHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234});
 
@@ -1452,6 +1482,149 @@
         assertTrue(actualChannel.isSoundRestored());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+    public void testRestoreXml_delayedRestore() throws Exception {
+        // simulate package not installed
+        when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+        when(mClock.millis()).thenReturn(System.currentTimeMillis());
+
+        String id = "id";
+        String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+                + "show_badge=\"true\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+
+        loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+        // settings are not available with real uid because pkg is not installed
+        assertThat(mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false)).isNull();
+        // but the settings are in memory with unknown_uid
+        assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNotNull();
+
+        // package is "installed"
+        when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+
+        // Trigger 2nd restore pass
+        mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+                new int[]{UID_P});
+
+        NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+                false);
+        assertThat(channel.getImportance()).isEqualTo(2);
+        assertThat(channel.canShowBadge()).isTrue();
+        assertThat(channel.canBypassDnd()).isFalse();
+
+        // removed from 'pending install' set
+        assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id,false)).isNull();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+    public void testRestoreXml_delayedRestore_afterReboot() throws Exception {
+        // load restore data
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false));
+        when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+                .thenReturn(appPermissions);
+
+        // simulate package not installed
+        when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+        when(mClock.millis()).thenReturn(System.currentTimeMillis());
+
+        String id = "id";
+        String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+                + "show_badge=\"true\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+
+        loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+        // simulate write to disk
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mXmlHelper.writeXml(serializer, false, USER_SYSTEM);
+        serializer.endDocument();
+        serializer.flush();
+
+        // simulate load after reboot
+        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
+                false, mClock);
+        loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM);
+
+        // Trigger 2nd restore pass
+        when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+        mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+                new int[]{UID_P});
+
+        NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+                false);
+        assertThat(channel.getImportance()).isEqualTo(2);
+        assertThat(channel.canShowBadge()).isTrue();
+        assertThat(channel.canBypassDnd()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+    public void testRestoreXml_delayedRestore_packageMissingAfterTwoDays() throws Exception {
+        // load restore data
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false));
+        when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+                .thenReturn(appPermissions);
+
+        // simulate package not installed
+        when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+
+        String id = "id";
+        String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+                + "show_badge=\"true\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+
+        loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+        // simulate write to disk
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mXmlHelper.writeXml(serializer, false, USER_SYSTEM);
+        serializer.endDocument();
+        serializer.flush();
+
+        // advance time by 2 days
+        when(mClock.millis()).thenReturn(
+                Duration.ofDays(2).toMillis() + System.currentTimeMillis());
+
+        // simulate load after reboot
+        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
+                false, mClock);
+        loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+
+        // Trigger 2nd restore pass
+        mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+                new int[]{UID_P});
+
+        // verify the 2nd restore pass failed because the restore data had been removed
+        assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNull();
+    }
 
     /**
      * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
@@ -1520,10 +1693,10 @@
 
         mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                false);
+                false, mClock);
         mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
-                false);
+                false, mClock);
 
         NotificationChannel channel =
                 new NotificationChannel("id", "name", IMPORTANCE_LOW);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5aabea3..b41db31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -643,7 +643,8 @@
         doReturn(false).when(mActivity).isInLetterboxAnimation();
         assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
 
-        doReturn(false).when(mainWindow).isOnScreen();
+        doReturn(false).when(mActivity).isVisibleRequested();
+        doReturn(false).when(mActivity).isVisible();
         assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
 
         doReturn(true).when(mActivity).isInLetterboxAnimation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index aa780ed..9697c65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -915,8 +915,7 @@
         assertEquals(window, mActivity.findMainWindow());
 
         spyOn(mActivity.mLetterboxUiController);
-        doReturn(true).when(mActivity.mLetterboxUiController)
-                .isSurfaceVisible(any());
+        doReturn(true).when(mActivity).isVisibleRequested();
 
         assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
                 mActivity.findMainWindow()));
@@ -1942,8 +1941,7 @@
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
-
-	final int scale = dh / dw;
+        final int scale = dh / dw;
 
         // App bounds should be dh / scale x dw / scale
         assertEquals(dw, rotatedDisplayBounds.width());
@@ -4210,6 +4208,43 @@
     }
 
     @Test
+    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+    public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
+        setUpDisplaySizeWithApp(2200, 2280);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        final DisplayContent dc = mActivity.mDisplayContent;
+        // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+        final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+                "navbar");
+        final Binder owner = new Binder();
+        navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+                        .setInsetsSize(Insets.of(0, 0, 0, 150))
+        };
+        dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+        assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
+        dc.sendNewConfiguration();
+
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+        prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+                SCREEN_ORIENTATION_LANDSCAPE);
+        // To force config to update again but with the same landscape orientation.
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+
+        assertTrue(activity.shouldCreateCompatDisplayInsets());
+        assertNotNull(activity.getCompatDisplayInsets());
+        // Activity is not letterboxed for fixed orientation because orientation is respected
+        // with insets, and should not be in size compat mode
+        assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity.inSizeCompatMode());
+    }
+
+    @Test
     public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
         if (Flags.insetsDecoupledConfiguration()) {
             // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ba7ba532..8fe45cb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4614,6 +4614,31 @@
     }
 
     /**
+     * Set owner for this subscription.
+     *
+     * @param subscriptionId the subId of the subscription.
+     * @param groupOwner The group owner to assign to the subscription
+     *
+     * @throws SecurityException if caller is not authorized.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setGroupOwner(int subscriptionId, @NonNull String groupOwner) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                iSub.setGroupOwner(subscriptionId, groupOwner);
+            } else {
+                throw new IllegalStateException("[setGroupOwner]: "
+                        + "subscription service unavailable");
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Set userHandle for a subscription.
      *
      * Used to set an association between a subscription and a user on the device so that voice
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 6678f40..1bfec29 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -309,6 +309,18 @@
      */
     int setUsageSetting(int usageSetting, int subId, String callingPackage);
 
+    /**
+      * Set owner for this subscription.
+      *
+      * @param subId the unique SubscriptionInfo index in database
+      * @param groupOwner The group owner to assign to the subscription
+      *
+      * @throws SecurityException if caller is not authorized.
+      *
+      * @hide
+      */
+     void setGroupOwner(int subId, String groupOwner);
+
      /**
       * Set userHandle for this subscription.
       *
diff --git a/tests/ChoreographerTests/Android.bp b/tests/ChoreographerTests/Android.bp
index 5d49120..3f48d70 100644
--- a/tests/ChoreographerTests/Android.bp
+++ b/tests/ChoreographerTests/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 android_test {
diff --git a/tests/CtsSurfaceControlTestsStaging/Android.bp b/tests/CtsSurfaceControlTestsStaging/Android.bp
index 96e4a9e..1038c9e 100644
--- a/tests/CtsSurfaceControlTestsStaging/Android.bp
+++ b/tests/CtsSurfaceControlTestsStaging/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 android_test {
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
index 61dd9d4..dbe9d36 100644
--- a/tests/WindowInsetsTests/AndroidManifest.xml
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -18,7 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.google.android.test.windowinsetstests">
 
-    <application android:label="@string/application_title">
+    <application android:label="@string/application_title"
+                 android:theme="@style/base">
         <activity android:name=".WindowInsetsTestsMainActivity"
                   android:exported="true">
             <intent-filter>
@@ -29,11 +30,9 @@
 
         <activity android:name=".ChatActivity"
                   android:label="@string/chat_activity_title"
-                  android:theme="@style/chat"
                   android:windowSoftInputMode="adjustResize" />
 
         <activity android:name=".ControllerActivity"
-                  android:label="@string/controller_activity_title"
-                  android:theme="@style/controller" />
+                  android:label="@string/controller_activity_title" />
     </application>
 </manifest>
diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml
index 5550eab..7013059 100644
--- a/tests/WindowInsetsTests/res/layout/controller_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml
@@ -15,92 +15,110 @@
 -->
 
 
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
 
-    <LinearLayout
-        android:id="@+id/content"
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+    />
+
+    <ScrollView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp">
 
-        <Spinner
-            android:id="@+id/spinnerBehavior"
+        <LinearLayout
+            android:id="@+id/content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
-            android:layout_marginBottom="20dp" />
+            android:orientation="vertical">
 
-        <ToggleButton
-            android:id="@+id/toggleButtonStatus"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:checked="true"
-            android:text="Status Bars Toggle Button"
-            android:textOff="Status Bars Invisible"
-            android:textOn="Status Bars Visible" />
+            <Spinner
+                android:id="@+id/spinnerBehavior"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="20dp" />
 
-        <SeekBar
-            android:id="@+id/seekBarStatus"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
-            android:layout_marginBottom="20dp"
-            android:max="10000"
-            android:progress="10000" />
+            <ToggleButton
+                android:id="@+id/toggleButtonStatus"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:checked="true"
+                android:text="@string/status_bars_toggle_button"
+                android:textOff="@string/status_bars_invisible"
+                android:textOn="@string/status_bars_visible" />
 
-        <ToggleButton
-            android:id="@+id/toggleButtonNavigation"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:checked="true"
-            android:text="Navigation Bars Toggle Button"
-            android:textOff="Navigation Bars Invisible"
-            android:textOn="Navigation Bars Visible" />
+            <SeekBar
+                android:id="@+id/seekBarStatus"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="20dp"
+                android:max="10000"
+                android:progress="10000" />
 
-        <SeekBar
-            android:id="@+id/seekBarNavigation"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
-            android:layout_marginBottom="20dp"
-            android:max="10000"
-            android:progress="10000" />
+            <ToggleButton
+                android:id="@+id/toggleButtonNavigation"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:checked="true"
+                android:text="@string/navigation_bars_toggle_button"
+                android:textOff="@string/navigation_bars_invisible"
+                android:textOn="@string/navigation_bars_visible" />
 
-        <ToggleButton
-            android:id="@+id/toggleButtonIme"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:checked="true"
-            android:text="IME Toggle Button"
-            android:textOff="IME Invisible"
-            android:textOn="IME Visible" />
+            <SeekBar
+                android:id="@+id/seekBarNavigation"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="20dp"
+                android:max="10000"
+                android:progress="10000" />
 
-        <SeekBar
-            android:id="@+id/seekBarIme"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
-            android:layout_marginBottom="20dp"
-            android:max="10000"
-            android:progress="0" />
+            <ToggleButton
+                android:id="@+id/toggleButtonIme"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:checked="true"
+                android:text="@string/ime_toggle_button"
+                android:textOff="@string/ime_invisible"
+                android:textOn="@string/ime_visible" />
 
-        <TextView
-            android:id="@+id/textViewControllableInsets"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_margin="5dp" />
+            <SeekBar
+                android:id="@+id/seekBarIme"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="20dp"
+                android:max="10000"
+                android:progress="0" />
 
-        <EditText
-            android:id="@+id/editText"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ems="10"
-            android:hint="For testing IME..."
-            android:inputType="text"
-            android:text="" />
+            <TextView
+                android:id="@+id/textViewControllableInsets"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="5dp" />
 
-    </LinearLayout>
+            <EditText
+                android:id="@+id/editText"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:autofillHints="@string/for_testing_ime"
+                android:hint="@string/for_testing_ime"
+                android:inputType="text"
+                android:text="" />
 
-</ScrollView>
\ No newline at end of file
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/layout/main_activity.xml b/tests/WindowInsetsTests/res/layout/main_activity.xml
index 621ed89..d6d4ff9 100644
--- a/tests/WindowInsetsTests/res/layout/main_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/main_activity.xml
@@ -16,22 +16,38 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <Button
-        android:id="@+id/chat_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/chat_activity_title"
-        android:textAllCaps="false"/>
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+    />
 
-    <Button
-        android:id="@+id/controller_button"
-        android:layout_width="wrap_content"
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="@string/controller_activity_title"
-        android:textAllCaps="false"/>
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp">
+
+        <Button
+            android:id="@+id/chat_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/chat_activity_title"
+            android:textAllCaps="false"/>
+
+        <Button
+            android:id="@+id/controller_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/controller_activity_title"
+            android:textAllCaps="false"/>
+
+    </LinearLayout>
 
 </LinearLayout>
diff --git a/tests/WindowInsetsTests/res/values-night/styles.xml b/tests/WindowInsetsTests/res/values-night/styles.xml
new file mode 100644
index 0000000..323c5fd
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values-night/styles.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+
+    <style name="base" parent="@style/Theme.MaterialComponents">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+
+        <item name="colorPrimary">@color/primaryColor</item>
+        <item name="colorPrimaryDark">@color/primaryDarkColor</item>
+        <item name="colorSecondary">?attr/colorPrimary</item>
+        <item name="colorOnSecondary">@color/primaryTextColor</item>
+
+        <!-- Window decor -->
+        <item name="android:windowLightStatusBar">false</item>
+        <item name="android:windowLightNavigationBar">false</item>
+
+    </style>
+
+    <color name="primaryColor">#639ff9</color>
+    <color name="primaryLightColor">#6f6bff</color>
+    <color name="primaryDarkColor">#0016bb</color>
+    <color name="primaryTextColor">#ffffff</color>
+
+    <color name="bubble">#333333</color>
+    <color name="bubble_self">#185abc</color>
+
+</resources>
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
index 516d458..7b70852 100644
--- a/tests/WindowInsetsTests/res/values/strings.xml
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -19,6 +19,16 @@
     <string name="application_title">Window Insets Tests</string>
     <string name="chat_activity_title">New Insets Chat</string>
     <string name="controller_activity_title">Window Insets Controller</string>
+    <string name="status_bars_toggle_button">Status Bars Toggle Button</string>
+    <string name="status_bars_invisible">Status Bars Invisible</string>
+    <string name="status_bars_visible">Status Bars Visible</string>
+    <string name="navigation_bars_toggle_button">Navigation Bars Toggle Button</string>
+    <string name="navigation_bars_invisible">Navigation Bars Invisible</string>
+    <string name="navigation_bars_visible">Navigation Bars Visible</string>
+    <string name="ime_toggle_button">IME Bars Toggle Button</string>
+    <string name="ime_invisible">IME Bars Invisible</string>
+    <string name="ime_visible">IME Bars Visible</string>
+    <string name="for_testing_ime">For testing IME&#8230;</string>
 
     <!-- The item positions should match the flag values respectively. -->
     <string-array name="behaviors">
diff --git a/tests/WindowInsetsTests/res/values/styles.xml b/tests/WindowInsetsTests/res/values/styles.xml
index a84ffbed..4ce6323 100644
--- a/tests/WindowInsetsTests/res/values/styles.xml
+++ b/tests/WindowInsetsTests/res/values/styles.xml
@@ -17,7 +17,7 @@
 
 <resources>
 
-    <style name="chat" parent="@style/Theme.MaterialComponents.Light">
+    <style name="base" parent="@style/Theme.MaterialComponents.Light">
         <item name="windowActionBar">false</item>
         <item name="windowNoTitle">true</item>
 
@@ -27,10 +27,8 @@
         <item name="colorOnSecondary">@color/primaryTextColor</item>
 
         <!-- Window decor -->
-        <item name="android:statusBarColor">#ffffff</item>
         <item name="android:windowLightStatusBar">true</item>
         <item name="android:windowLightNavigationBar">true</item>
-        <item name="android:navigationBarColor">#ffffff</item>
 
     </style>
 
@@ -63,11 +61,4 @@
     <dimen name="bubble_padding">8dp</dimen>
     <dimen name="bubble_padding_side">16dp</dimen>
 
-    <style name="controller" parent="android:Theme.Material">
-        <item name="android:colorPrimaryDark">#111111</item>
-        <item name="android:navigationBarColor">#111111</item>
-        <item name="android:colorPrimary">#222222</item>
-        <item name="android:colorAccent">#33ccff</item>
-    </style>
-
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index 167d560..1dd87df 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -16,12 +16,18 @@
 
 package com.google.android.test.windowinsetstests;
 
-import android.app.Activity;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
+
 import android.graphics.Insets;
 import android.os.Bundle;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
 import android.widget.AdapterView;
@@ -32,7 +38,9 @@
 import android.widget.TextView;
 import android.widget.ToggleButton;
 
-public class ControllerActivity extends Activity implements View.OnApplyWindowInsetsListener {
+import androidx.appcompat.app.AppCompatActivity;
+
+public class ControllerActivity extends AppCompatActivity {
 
     private ToggleButton mToggleStatus;
     private SeekBar mSeekStatus;
@@ -48,6 +56,29 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.controller_activity);
+        setSupportActionBar(findViewById(R.id.toolbar));
+        getWindow().setDecorFitsSystemWindows(false);
+        findViewById(R.id.root).setOnApplyWindowInsetsListener(
+                (v, insets) -> {
+                    final int visibleTypes = systemBars() | displayCutout();
+                    final Insets i = insets.getInsets(visibleTypes);
+                    v.setPadding(i.left, i.top, i.right, i.bottom);
+
+                    // Make the content view not obscured by gesture insets to prevent triggering
+                    // system gestures while controlling seek bars.
+                    final Insets gi = Insets.subtract(
+                            insets.getInsets(systemGestures() | visibleTypes), i);
+                    findViewById(R.id.content).setPadding(gi.left, gi.top, gi.right, gi.bottom);
+
+                    mNotFromUser[0] = true;
+                    updateWidgets(insets, statusBars(), mToggleStatus, mSeekStatus);
+                    updateWidgets(insets, navigationBars(), mToggleNavigation, mSeekNavigation);
+                    updateWidgets(insets, ime(), mToggleIme, mSeekIme);
+                    mLastInsets = insets;
+                    mNotFromUser[0] = false;
+
+                    return WindowInsets.CONSUMED;
+                });
         final Spinner spinnerBehavior = findViewById(R.id.spinnerBehavior);
         ArrayAdapter<CharSequence> adapterBehavior = ArrayAdapter.createFromResource(this,
                 R.array.behaviors, android.R.layout.simple_spinner_item);
@@ -66,23 +97,21 @@
         });
         mToggleStatus = findViewById(R.id.toggleButtonStatus);
         mToggleStatus.setTag(mNotFromUser);
-        mToggleStatus.setOnCheckedChangeListener(new ToggleListener(Type.statusBars()));
+        mToggleStatus.setOnCheckedChangeListener(new ToggleListener(statusBars()));
         mSeekStatus = findViewById(R.id.seekBarStatus);
-        mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(Type.statusBars()));
+        mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(statusBars()));
         mToggleNavigation = findViewById(R.id.toggleButtonNavigation);
         mToggleNavigation.setTag(mNotFromUser);
-        mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(Type.navigationBars()));
+        mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(navigationBars()));
         mSeekNavigation = findViewById(R.id.seekBarNavigation);
-        mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(Type.navigationBars()));
+        mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(navigationBars()));
         mToggleIme = findViewById(R.id.toggleButtonIme);
         mToggleIme.setTag(mNotFromUser);
-        mToggleIme.setOnCheckedChangeListener(new ToggleListener(Type.ime()));
+        mToggleIme.setOnCheckedChangeListener(new ToggleListener(ime()));
         mSeekIme = findViewById(R.id.seekBarIme);
-        mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(Type.ime()));
+        mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(ime()));
         mTextControllableInsets = findViewById(R.id.textViewControllableInsets);
-        final View contentView = findViewById(R.id.content);
-        contentView.setOnApplyWindowInsetsListener(this);
-        contentView.getWindowInsetsController().addOnControllableInsetsChangedListener(
+        mTextControllableInsets.getWindowInsetsController().addOnControllableInsetsChangedListener(
                 (c, types) -> mTextControllableInsets.setText(
                         "ControllableInsetsTypes:\n" + insetsTypesToString(types)));
     }
@@ -91,22 +120,6 @@
         return types == 0 ? "none" : WindowInsets.Type.toString(types);
     }
 
-    @Override
-    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
-        mNotFromUser[0] = true;
-        updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus);
-        updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation);
-        updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme);
-        mLastInsets = insets;
-        mNotFromUser[0] = false;
-
-        // Prevent triggering system gestures while controlling seek bars.
-        final Insets gestureInsets =  insets.getInsets(Type.systemGestures());
-        v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0);
-
-        return v.onApplyWindowInsets(insets);
-    }
-
     private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) {
         final boolean isVisible = insets.isVisible(types);
         final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible;
@@ -121,7 +134,7 @@
 
     private static class ToggleListener implements CompoundButton.OnCheckedChangeListener {
 
-        private final @Type.InsetsType int mTypes;
+        private final @InsetsType int mTypes;
 
         ToggleListener(int types) {
             mTypes = types;
@@ -143,7 +156,7 @@
 
     private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
 
-        private final @Type.InsetsType int mTypes;
+        private final @InsetsType int mTypes;
 
         private WindowInsetsAnimationController mController;
 
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
index 8b77a78..278ad84 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
@@ -16,16 +16,30 @@
 
 package com.google.android.test.windowinsetstests;
 
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
 
-public class WindowInsetsTestsMainActivity extends Activity {
+import android.content.Intent;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.view.WindowInsets;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class WindowInsetsTestsMainActivity extends AppCompatActivity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main_activity);
+        setSupportActionBar(findViewById(R.id.toolbar));
+
+        findViewById(R.id.root).setOnApplyWindowInsetsListener(
+                (v, insets) -> {
+                    final Insets i = insets.getInsets(systemBars() | displayCutout());
+                    v.setPadding(i.left, i.top, i.right, i.bottom);
+                    return WindowInsets.CONSUMED;
+                });
 
         findViewById(R.id.chat_button).setOnClickListener(
                 v -> startActivity(new Intent(this, ChatActivity.class)));
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
index 9d61111..be5c197 100644
--- a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -18,11 +18,13 @@
 
 #include <stdio.h>
 
+#include <algorithm>
 #include <iomanip>
 #include <iostream>
 #include <map>
 #include <sstream>
 #include <string>
+#include <unordered_set>
 
 #include "Errors.h"
 
@@ -30,21 +32,39 @@
 using namespace google::protobuf::io;
 using namespace std;
 
+static bool outer_class_name_clashes_with_any_message(const string& outer_class_name,
+                                                      const vector<DescriptorProto>& messages) {
+    return any_of(messages.cbegin(), messages.cend(), [&](const DescriptorProto& message) {
+        return message.name() == outer_class_name;
+    });
+}
+
 /**
  * If the descriptor gives us a class name, use that. Otherwise make one up from
  * the filename of the .proto file.
  */
-static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor,
+                                    const vector<DescriptorProto>& messages) {
     string name = file_descriptor.options().java_outer_classname();
-    if (name.size() == 0) {
-        name = to_camel_case(file_base_name(file_descriptor.name()));
-        if (name.size() == 0) {
-            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
-                       "Unable to make an outer class name for file: %s",
-                       file_descriptor.name().c_str());
-            name = "Unknown";
-        }
+    if (!name.empty()) {
+        return name;
     }
+
+    // Outer class and messages with the same name would result in invalid java (outer class and
+    // inner class cannot have same names).
+    // If the outer class name clashes with any message, let's append an "OuterClass" suffix.
+    // This behavior is consistent with the standard protoc.
+    name = to_camel_case(file_base_name(file_descriptor.name()));
+    while (outer_class_name_clashes_with_any_message(name, messages)) {
+        name += "OuterClass";
+    }
+
+    if (name.empty()) {
+        ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unable to make an outer class name for file: %s",
+                   file_descriptor.name().c_str());
+        name = "Unknown";
+    }
+
     return name;
 }
 
@@ -149,6 +169,12 @@
         write_field(text, message.field(i), indented);
     }
 
+    // Extensions
+    N = message.extension_size();
+    for (int i = 0; i < N; i++) {
+        write_field(text, message.extension(i), indented);
+    }
+
     text << indent << "}" << endl;
     text << endl;
 }
@@ -165,7 +191,7 @@
     stringstream text;
 
     string const package_name = make_java_package(file_descriptor);
-    string const outer_class_name = make_outer_class_name(file_descriptor);
+    string const outer_class_name = make_outer_class_name(file_descriptor, messages);
 
     text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
     text << "// source: " << file_descriptor.name() << endl << endl;
@@ -214,7 +240,7 @@
  */
 static void write_multiple_files(CodeGeneratorResponse* response,
                                  const FileDescriptorProto& file_descriptor,
-                                 set<string> messages_to_compile) {
+                                 const unordered_set<string>& messages_allowlist) {
     // If there is anything to put in the outer class file, create one
     if (file_descriptor.enum_type_size() > 0) {
         vector<EnumDescriptorProto> enums;
@@ -222,7 +248,7 @@
         for (int i = 0; i < N; i++) {
             auto enum_full_name =
                     file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
-            if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+            if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
                 continue;
             }
             enums.push_back(file_descriptor.enum_type(i));
@@ -230,9 +256,10 @@
 
         vector<DescriptorProto> messages;
 
-        if (messages_to_compile.empty() || !enums.empty()) {
+        if (messages_allowlist.empty() || !enums.empty()) {
             write_file(response, file_descriptor,
-                       make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+                       make_file_name(file_descriptor,
+                                      make_outer_class_name(file_descriptor, messages)),
                        true, enums, messages);
         }
     }
@@ -246,12 +273,12 @@
 
         auto message_full_name =
                 file_descriptor.package() + "." + file_descriptor.message_type(i).name();
-        if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+        if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
             continue;
         }
         messages.push_back(file_descriptor.message_type(i));
 
-        if (messages_to_compile.empty() || !messages.empty()) {
+        if (messages_allowlist.empty() || !messages.empty()) {
             write_file(response, file_descriptor,
                        make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
                        false, enums, messages);
@@ -261,14 +288,14 @@
 
 static void write_single_file(CodeGeneratorResponse* response,
                               const FileDescriptorProto& file_descriptor,
-                              set<string> messages_to_compile) {
+                              const unordered_set<string>& messages_allowlist) {
     int N;
 
     vector<EnumDescriptorProto> enums;
     N = file_descriptor.enum_type_size();
     for (int i = 0; i < N; i++) {
         auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
-        if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+        if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
             continue;
         }
 
@@ -281,22 +308,23 @@
         auto message_full_name =
                 file_descriptor.package() + "." + file_descriptor.message_type(i).name();
 
-        if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+        if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
             continue;
         }
 
         messages.push_back(file_descriptor.message_type(i));
     }
 
-    if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+    if (messages_allowlist.empty() || !enums.empty() || !messages.empty()) {
         write_file(response, file_descriptor,
-                   make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
-                   enums, messages);
+                   make_file_name(file_descriptor,
+                                  make_outer_class_name(file_descriptor, messages)),
+                   true, enums, messages);
     }
 }
 
 static void parse_args_string(stringstream args_string_stream,
-                              set<string>* messages_to_compile_out) {
+                              unordered_set<string>& messages_allowlist_out) {
     string line;
     while (getline(args_string_stream, line, ';')) {
         stringstream line_ss(line);
@@ -305,7 +333,7 @@
         if (arg_name == "include_filter") {
             string full_message_name;
             while (getline(line_ss, full_message_name, ',')) {
-                messages_to_compile_out->insert(full_message_name);
+                messages_allowlist_out.insert(full_message_name);
             }
         } else {
             ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
@@ -316,10 +344,10 @@
 CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
     CodeGeneratorResponse response;
 
-    set<string> messages_to_compile;
+    unordered_set<string> messages_allowlist;
     auto request_params = request.parameter();
     if (!request_params.empty()) {
-        parse_args_string(stringstream(request_params), &messages_to_compile);
+        parse_args_string(stringstream(request_params), messages_allowlist);
     }
 
     // Build the files we need.
@@ -328,9 +356,9 @@
         const FileDescriptorProto& file_descriptor = request.proto_file(i);
         if (should_generate_for_file(request, file_descriptor.name())) {
             if (file_descriptor.options().java_multiple_files()) {
-                write_multiple_files(&response, file_descriptor, messages_to_compile);
+                write_multiple_files(&response, file_descriptor, messages_allowlist);
             } else {
-                write_single_file(&response, file_descriptor, messages_to_compile);
+                write_single_file(&response, file_descriptor, messages_allowlist);
             }
         }
     }