Merge "Move recentsAnimationController to shell" into udc-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5a84b97..fb5d189 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2033,6 +2033,7 @@
 
   public abstract class SharedConnectivityService extends android.app.Service {
     method public void onBind();
+    method public final void setCountdownLatch(@Nullable java.util.concurrent.CountDownLatch);
   }
 
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c95d081..dfb9cf6 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2550,41 +2550,15 @@
      * <ul>
      *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED UNSPECIFIED}</li>
      *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB LINEAR_SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB EXTENDED_SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB LINEAR_EXTENDED_SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709 BT709}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020 BT2020}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3 DCI_P3}</li>
      *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3 DISPLAY_P3}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953 NTSC_1953}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C SMPTE_C}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB ADOBE_RGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB PRO_PHOTO_RGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES ACES}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG ACESCG}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ CIE_XYZ}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB CIE_LAB}</li>
+     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG BT2020_HLG}</li>
      * </ul>
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED
      * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3
      * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB
+     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG
      * @hide
      */
     public static final Key<long[]> REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP =
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 9b1f0cd..9a202ae 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -112,7 +112,7 @@
         mCaptor.getValue().onAllAuthenticatorsRegistered(mProps);
         List<FaceSensorPropertiesInternal> actual = mFaceManager.getSensorPropertiesInternal();
 
-        assertThat(actual).isEqualTo(mProps);
+        assertThat(actual).containsExactlyElementsIn(mProps);
         verify(mService, never()).getSensorPropertiesInternal(any());
     }
 
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index f31903a..5058065 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -113,7 +113,7 @@
         List<FingerprintSensorPropertiesInternal> actual =
                 mFingerprintManager.getSensorPropertiesInternal();
 
-        assertThat(actual).isEqualTo(mProps);
+        assertThat(actual).containsExactlyElementsIn(mProps);
         verify(mService, never()).getSensorPropertiesInternal(any());
     }
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9dc37c9..8b38deb 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -195,7 +195,6 @@
         "lottie",
         "LowLightDreamLib",
         "motion_tool_lib",
-        "renderscript_toolkit",
     ],
     manifest: "AndroidManifest.xml",
 
@@ -329,7 +328,6 @@
         "WindowManager-Shell",
         "LowLightDreamLib",
         "motion_tool_lib",
-        "renderscript_toolkit",
         "androidx.core_core-animation-testing-nodeps",
         "androidx.compose.ui_ui",
     ],
@@ -370,6 +368,7 @@
     plugins: ["dagger2-compiler"],
     lint: {
         test: true,
+        extra_check_modules: ["SystemUILintChecker"],
     },
 }
 
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index 5b2ec48..0cd0623 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -14,22 +14,15 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<FrameLayout
+<com.android.systemui.animation.view.LaunchableImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
-
-    <com.android.systemui.animation.view.LaunchableImageView
-        android:id="@+id/home_controls_chip"
-        android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
-        android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
-        android:layout_gravity="bottom|start"
-        android:padding="@dimen/dream_overlay_bottom_affordance_padding"
-        android:background="@drawable/dream_overlay_bottom_affordance_bg"
-        android:scaleType="fitCenter"
-        android:tint="?android:attr/textColorPrimary"
-        android:src="@drawable/controls_icon"
-        android:contentDescription="@string/quick_controls_title" />
-
-</FrameLayout>
+    android:id="@+id/home_controls_chip"
+    android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+    android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
+    android:layout_gravity="bottom|start"
+    android:padding="@dimen/dream_overlay_bottom_affordance_padding"
+    android:background="@drawable/dream_overlay_bottom_affordance_bg"
+    android:scaleType="fitCenter"
+    android:tint="?android:attr/textColorPrimary"
+    android:src="@drawable/controls_icon"
+    android:contentDescription="@string/quick_controls_title" />
diff --git a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
index 50f3ffc..b75c638 100644
--- a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
@@ -17,13 +17,12 @@
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/media_entry_chip"
-    android:layout_height="@dimen/keyguard_affordance_fixed_height"
-    android:layout_width="@dimen/keyguard_affordance_fixed_width"
+    android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+    android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
     android:layout_gravity="bottom|start"
-    android:scaleType="center"
+    android:scaleType="fitCenter"
+    android:padding="@dimen/dream_overlay_bottom_affordance_padding"
     android:tint="?android:attr/textColorPrimary"
     android:src="@drawable/ic_music_note"
-    android:background="@drawable/keyguard_bottom_affordance_bg"
-    android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
-    android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+    android:background="@drawable/dream_overlay_bottom_affordance_bg"
     android:contentDescription="@string/controls_media_title" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab304aa..ee9e07a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1632,7 +1632,6 @@
     <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
-    <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
     <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
     <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
     <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b3a641c..0e2f8f0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -4125,6 +4125,19 @@
                     KeyguardFingerprintListenModel.TABLE_HEADERS,
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
+        } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
+            final int userId = mUserTracker.getUserId();
+            pw.println("  Fingerprint state (user=" + userId + ")");
+            pw.println("    mFingerprintSensorProperties.isEmpty="
+                    + mFingerprintSensorProperties.isEmpty());
+            pw.println("    mFpm.isHardwareDetected="
+                    + mFpm.isHardwareDetected());
+
+            new DumpsysTableLogger(
+                    "KeyguardFingerprintListen",
+                    KeyguardFingerprintListenModel.TABLE_HEADERS,
+                    mFingerprintListenBuffer.toList()
+            ).printTableData(pw);
         }
         if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
             final int userId = mUserTracker.getUserId();
@@ -4155,6 +4168,19 @@
                     KeyguardFaceListenModel.TABLE_HEADERS,
                     mFaceListenBuffer.toList()
             ).printTableData(pw);
+        } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
+            final int userId = mUserTracker.getUserId();
+            pw.println("  Face state (user=" + userId + ")");
+            pw.println("    mFaceSensorProperties.isEmpty="
+                    + mFaceSensorProperties.isEmpty());
+            pw.println("    mFaceManager.isHardwareDetected="
+                    + mFaceManager.isHardwareDetected());
+
+            new DumpsysTableLogger(
+                    "KeyguardFaceListen",
+                    KeyguardFingerprintListenModel.TABLE_HEADERS,
+                    mFingerprintListenBuffer.toList()
+            ).printTableData(pw);
         }
 
         new DumpsysTableLogger(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 7f395d8..82a8858 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -33,7 +33,6 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -157,14 +156,14 @@
      * Contains values/logic associated with the dream complication view.
      */
     public static class DreamHomeControlsChipViewHolder implements ViewHolder {
-        private final View mView;
+        private final ImageView mView;
         private final ComplicationLayoutParams mLayoutParams;
         private final DreamHomeControlsChipViewController mViewController;
 
         @Inject
         DreamHomeControlsChipViewHolder(
                 DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
-                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
+                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
         ) {
             mView = view;
@@ -174,7 +173,7 @@
         }
 
         @Override
-        public View getView() {
+        public ImageView getView() {
             return mView;
         }
 
@@ -187,7 +186,7 @@
     /**
      * Controls behavior of the dream complication.
      */
-    static class DreamHomeControlsChipViewController extends ViewController<View> {
+    static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
         private static final String TAG = "DreamHomeControlsCtrl";
         private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -216,7 +215,7 @@
 
         @Inject
         DreamHomeControlsChipViewController(
-                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
+                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 ActivityStarter activityStarter,
                 Context context,
                 ControlsComponent controlsComponent,
@@ -231,10 +230,9 @@
 
         @Override
         protected void onViewAttached() {
-            final ImageView chip = mView.findViewById(R.id.home_controls_chip);
-            chip.setImageResource(mControlsComponent.getTileImageId());
-            chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
-            chip.setOnClickListener(this::onClickHomeControls);
+            mView.setImageResource(mControlsComponent.getTileImageId());
+            mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+            mView.setOnClickListener(this::onClickHomeControls);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index a7aa97f..cf05d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -19,7 +19,7 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import android.view.LayoutInflater;
-import android.view.View;
+import android.widget.ImageView;
 
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
@@ -74,8 +74,8 @@
         @Provides
         @DreamHomeControlsComplicationScope
         @Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
-        static View provideHomeControlsChipView(LayoutInflater layoutInflater) {
-            return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+        static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
+            return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
                     null, false);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 616bd81..3be42cb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -101,8 +101,8 @@
     @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS)
     static ComplicationLayoutParams provideMediaEntryLayoutParams(@Main Resources res) {
         return new ComplicationLayoutParams(
-                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
-                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
                 ComplicationLayoutParams.POSITION_BOTTOM
                         | ComplicationLayoutParams.POSITION_START,
                 ComplicationLayoutParams.DIRECTION_END,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 00e5aac..a3bf652 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -151,7 +151,7 @@
     private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
     private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
     private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
-    private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 0.9f;
+    private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f;
     private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
 
     private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index dd713ae..750272d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -21,17 +21,20 @@
 import android.graphics.Canvas
 import android.graphics.Point
 import android.graphics.Rect
+import android.renderscript.Allocation
+import android.renderscript.Element
+import android.renderscript.RenderScript
+import android.renderscript.ScriptIntrinsicBlur
 import android.util.Log
 import android.util.MathUtils
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-import com.google.android.renderscript.Toolkit
 import javax.inject.Inject
 
 private const val TAG = "MediaArtworkProcessor"
 private const val COLOR_ALPHA = (255 * 0.7f).toInt()
-private const val BLUR_RADIUS = 25
+private const val BLUR_RADIUS = 25f
 private const val DOWNSAMPLE = 6
 
 @SysUISingleton
@@ -44,6 +47,10 @@
         if (mArtworkCache != null) {
             return mArtworkCache
         }
+        val renderScript = RenderScript.create(context)
+        val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
+        var input: Allocation? = null
+        var output: Allocation? = null
         var inBitmap: Bitmap? = null
         try {
             @Suppress("DEPRECATION")
@@ -59,8 +66,18 @@
                 inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
                 oldIn.recycle()
             }
+            val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+                    Bitmap.Config.ARGB_8888)
 
-            val outBitmap = Toolkit.blur(inBitmap, BLUR_RADIUS)
+            input = Allocation.createFromBitmap(renderScript, inBitmap,
+                    Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
+            output = Allocation.createFromBitmap(renderScript, outBitmap)
+
+            blur.setRadius(BLUR_RADIUS)
+            blur.setInput(input)
+            blur.forEach(output)
+            output.copyTo(outBitmap)
+
             val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
 
             val canvas = Canvas(outBitmap)
@@ -70,6 +87,9 @@
             Log.e(TAG, "error while processing artwork", ex)
             return null
         } finally {
+            input?.destroy()
+            output?.destroy()
+            blur.destroy()
             inBitmap?.recycle()
         }
     }
@@ -78,4 +98,4 @@
         mArtworkCache?.recycle()
         mArtworkCache = null
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index a062e7b..492f231 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -77,8 +77,11 @@
     private final FalsingManager mFalsingManager;
     private final UiEventLogger mUiEventLogger;
 
-    @VisibleForTesting String mSelectedCardId;
-    @VisibleForTesting boolean mIsDismissed;
+
+    @VisibleForTesting
+    String mSelectedCardId;
+    @VisibleForTesting
+    boolean mIsDismissed;
 
     public WalletScreenController(
             Context context,
@@ -124,9 +127,20 @@
         }
         Log.i(TAG, "Successfully retrieved wallet cards.");
         List<WalletCard> walletCards = response.getWalletCards();
-        List<WalletCardViewInfo> data = new ArrayList<>(walletCards.size());
+
+        boolean allUnknown = true;
         for (WalletCard card : walletCards) {
-            data.add(new QAWalletCardViewInfo(mContext, card));
+            if (card.getCardType() != WalletCard.CARD_TYPE_UNKNOWN) {
+                allUnknown = false;
+                break;
+            }
+        }
+
+        List<WalletCardViewInfo> paymentCardData = new ArrayList<>();
+        for (WalletCard card : walletCards) {
+            if (allUnknown || card.getCardType() == WalletCard.CARD_TYPE_PAYMENT) {
+                paymentCardData.add(new QAWalletCardViewInfo(mContext, card));
+            }
         }
 
         // Get on main thread for UI updates.
@@ -134,18 +148,18 @@
             if (mIsDismissed) {
                 return;
             }
-            if (data.isEmpty()) {
+            if (paymentCardData.isEmpty()) {
                 showEmptyStateView();
             } else {
                 int selectedIndex = response.getSelectedIndex();
-                if (selectedIndex >= data.size()) {
+                if (selectedIndex >= paymentCardData.size()) {
                     Log.w(TAG, "Invalid selected card index, showing empty state.");
                     showEmptyStateView();
                 } else {
                     boolean isUdfpsEnabled = mKeyguardUpdateMonitor.isUdfpsEnrolled()
                             && mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
                     mWalletView.showCardCarousel(
-                            data,
+                            paymentCardData,
                             selectedIndex,
                             !mKeyguardStateController.isUnlocked(),
                             isUdfpsEnabled);
@@ -213,7 +227,6 @@
     }
 
 
-
     @Override
     public void onCardClicked(@NonNull WalletCardViewInfo cardInfo) {
         if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 3312c43..aad49f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -35,7 +35,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.view.LaunchableImageView;
 import com.android.systemui.condition.SelfExecutingMonitor;
@@ -89,9 +88,6 @@
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
     @Mock
-    private View mView;
-
-    @Mock
     private LaunchableImageView mHomeControlsView;
 
     @Mock
@@ -115,7 +111,6 @@
         when(mControlsComponent.getControlsListingController()).thenReturn(
                 Optional.of(mControlsListingController));
         when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
-        when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
 
         mMonitor = SelfExecutingMonitor.createInstance();
     }
@@ -223,7 +218,7 @@
     public void testClick_logsUiEvent() {
         final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
                 new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
-                        mView,
+                        mHomeControlsView,
                         mActivityStarter,
                         mContext,
                         mControlsComponent,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
index b1950ea..692af6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
@@ -22,6 +22,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -63,6 +66,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
+import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -99,6 +103,8 @@
     ArgumentCaptor<PendingIntent> mIntentCaptor;
     @Captor
     ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
+    @Captor
+    ArgumentCaptor<List<WalletCardViewInfo>> mPaymentCardDataCaptor;
     private WalletScreenController mController;
     private TestableLooper mTestableLooper;
 
@@ -107,7 +113,7 @@
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
         when(mUserTracker.getUserContext()).thenReturn(mContext);
-        mWalletView = new WalletView(mContext);
+        mWalletView = spy(new WalletView(mContext));
         mWalletView.getCardCarousel().setExpectedViewWidth(CARD_CAROUSEL_WIDTH);
         when(mWalletClient.getLogo()).thenReturn(mWalletLogo);
         when(mWalletClient.getShortcutLongLabel()).thenReturn(SHORTCUT_LONG_LABEL);
@@ -430,6 +436,41 @@
         assertEquals(GONE, mWalletView.getVisibility());
     }
 
+    @Test
+    public void onWalletCardsRetrieved_cardDataAllUnknown_showsAllCards() {
+        List<WalletCard> walletCardList = List.of(
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN));
+        GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
+        mController.onWalletCardsRetrieved(response);
+        mTestableLooper.processAllMessages();
+
+        verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(),
+                anyBoolean(),
+                anyBoolean());
+        List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue();
+        assertEquals(paymentCardData.size(), walletCardList.size());
+    }
+
+    @Test
+    public void onWalletCardsRetrieved_cardDataDifferentTypes_onlyShowsPayment() {
+        List<WalletCard> walletCardList = List.of(createWalletCardWithType(mContext,
+                        WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_PAYMENT),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_NON_PAYMENT)
+                );
+        GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
+        mController.onWalletCardsRetrieved(response);
+        mTestableLooper.processAllMessages();
+
+        verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(),
+                anyBoolean(),
+                anyBoolean());
+        List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue();
+        assertEquals(paymentCardData.size(), 1);
+    }
+
     private WalletCard createNonActiveWalletCard(Context context) {
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
@@ -457,6 +498,15 @@
                 .build();
     }
 
+    private WalletCard createWalletCardWithType(Context context, int cardType) {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+        return new WalletCard.Builder(CARD_ID_1, cardType, createIcon(), "•••• 1234", pendingIntent)
+                .setCardIcon(createIcon())
+                .setCardLabel("Hold to reader")
+                .build();
+    }
+
     private WalletCard createCrazyWalletCard(Context context, boolean hasLabel) {
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d09ca5c..7c84b72 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -843,7 +843,10 @@
 
                     final long sessionStart = mBatteryUsageStatsStore
                             .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
-                    final long sessionEnd = mStats.getStartClockTime();
+                    final long sessionEnd;
+                    synchronized (mStats) {
+                        sessionEnd = mStats.getStartClockTime();
+                    }
                     final BatteryUsageStatsQuery queryBeforeReset =
                             new BatteryUsageStatsQuery.Builder()
                                     .setMaxStatsAgeMs(0)
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 7591057..1f65730 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -385,9 +385,11 @@
     public void setProcess(@Nullable ProcessRecord app) {
         this.app = app;
         if (app != null) {
+            setProcessCached(app.isCached());
             setProcessInstrumented(app.getActiveInstrumentation() != null);
             setProcessPersistent(app.isPersistent());
         } else {
+            setProcessCached(false);
             setProcessInstrumented(false);
             setProcessPersistent(false);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index bef16b6..bc33158 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1328,7 +1328,9 @@
                 synchronized (mService) {
                     BroadcastProcessQueue leaf = mProcessQueues.get(uid);
                     while (leaf != null) {
-                        leaf.setProcessCached(cached);
+                        // Update internal state by refreshing values previously
+                        // read from any known running process
+                        leaf.setProcess(leaf.app);
                         updateQueueDeferred(leaf);
                         updateRunnableList(leaf);
                         leaf = leaf.processNameNext;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index fa3f684..50d00b4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1039,7 +1039,13 @@
         mInFullBackup = inFullBackup;
     }
 
+    @GuardedBy("mService")
+    public void setCached(boolean cached) {
+        mState.setCached(cached);
+    }
+
     @Override
+    @GuardedBy("mService")
     public boolean isCached() {
         return mState.isCached();
     }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 82444f0..6bd4880 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -16,14 +16,22 @@
 
 package com.android.server.biometrics.log;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.WakeReason;
 import android.util.Slog;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.util.stream.Stream;
+
 /**
  * Wrapper for {@link FrameworkStatsLog} to isolate the testable parts.
  */
@@ -63,7 +71,7 @@
                 orientationType(operationContext.getOrientation()),
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
-                BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+                toProtoWakeReason(operationContext));
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -89,7 +97,8 @@
                 orientationType(operationContext.getOrientation()),
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
-                BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+                toProtoWakeReason(operationContext),
+                toProtoWakeReasonDetails(operationContext));
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -137,7 +146,81 @@
                 orientationType(operationContext.getOrientation()),
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
-                BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+                toProtoWakeReason(operationContext),
+                toProtoWakeReasonDetails(operationContext));
+    }
+
+    @VisibleForTesting
+    static int[] toProtoWakeReasonDetails(@NonNull OperationContextExt operationContext) {
+        final OperationContext ctx = operationContext.toAidlContext();
+        return Stream.of(toProtoWakeReasonDetails(ctx.authenticateReason))
+                .mapToInt(i -> i)
+                .filter(i -> i != BiometricsProtoEnums.DETAILS_UNKNOWN)
+                .toArray();
+    }
+
+    @VisibleForTesting
+    static int toProtoWakeReason(@NonNull OperationContextExt operationContext) {
+        @WakeReason final int reason = operationContext.getWakeReason();
+        switch (reason) {
+            case WakeReason.POWER_BUTTON:
+                return BiometricsProtoEnums.WAKE_REASON_POWER_BUTTON;
+            case WakeReason.GESTURE:
+                return BiometricsProtoEnums.WAKE_REASON_GESTURE;
+            case WakeReason.WAKE_KEY:
+                return BiometricsProtoEnums.WAKE_REASON_WAKE_KEY;
+            case WakeReason.WAKE_MOTION:
+                return BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION;
+            case WakeReason.LID:
+                return BiometricsProtoEnums.WAKE_REASON_LID;
+            case WakeReason.DISPLAY_GROUP_ADDED:
+                return BiometricsProtoEnums.WAKE_REASON_DISPLAY_GROUP_ADDED;
+            case WakeReason.TAP:
+                return BiometricsProtoEnums.WAKE_REASON_TAP;
+            case WakeReason.LIFT:
+                return BiometricsProtoEnums.WAKE_REASON_LIFT;
+            case WakeReason.BIOMETRIC:
+                return BiometricsProtoEnums.WAKE_REASON_BIOMETRIC;
+            default:
+                return BiometricsProtoEnums.WAKE_REASON_UNKNOWN;
+        }
+    }
+
+    private static int toProtoWakeReasonDetails(@Nullable AuthenticateReason reason) {
+        if (reason != null) {
+            switch (reason.getTag()) {
+                case AuthenticateReason.faceAuthenticateReason:
+                    return toProtoWakeReasonDetailsFromFace(reason.getFaceAuthenticateReason());
+            }
+        }
+        return BiometricsProtoEnums.DETAILS_UNKNOWN;
+    }
+
+    private static int toProtoWakeReasonDetailsFromFace(@AuthenticateReason.Face int reason) {
+        switch (reason) {
+            case AuthenticateReason.Face.STARTED_WAKING_UP:
+                return BiometricsProtoEnums.DETAILS_FACE_STARTED_WAKING_UP;
+            case AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN:
+                return BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN;
+            case AuthenticateReason.Face.ASSISTANT_VISIBLE:
+                return BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE;
+            case AuthenticateReason.Face.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN:
+                return BiometricsProtoEnums.DETAILS_FACE_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
+            case AuthenticateReason.Face.NOTIFICATION_PANEL_CLICKED:
+                return BiometricsProtoEnums.DETAILS_FACE_NOTIFICATION_PANEL_CLICKED;
+            case AuthenticateReason.Face.OCCLUDING_APP_REQUESTED:
+                return BiometricsProtoEnums.DETAILS_FACE_OCCLUDING_APP_REQUESTED;
+            case AuthenticateReason.Face.PICK_UP_GESTURE_TRIGGERED:
+                return BiometricsProtoEnums.DETAILS_FACE_PICK_UP_GESTURE_TRIGGERED;
+            case AuthenticateReason.Face.QS_EXPANDED:
+                return BiometricsProtoEnums.DETAILS_FACE_QS_EXPANDED;
+            case AuthenticateReason.Face.SWIPE_UP_ON_BOUNCER:
+                return BiometricsProtoEnums.DETAILS_FACE_SWIPE_UP_ON_BOUNCER;
+            case AuthenticateReason.Face.UDFPS_POINTER_DOWN:
+                return BiometricsProtoEnums.DETAILS_FACE_UDFPS_POINTER_DOWN;
+            default:
+                return BiometricsProtoEnums.DETAILS_UNKNOWN;
+        }
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index ecb7e7c..2934339 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -188,10 +188,17 @@
     }
 
     /** {@link OperationContext#reason}. */
+    @OperationReason
     public byte getReason() {
         return mAidlContext.reason;
     }
 
+    /** {@link OperationContext#wakeReason}. */
+    @WakeReason
+    public int getWakeReason() {
+        return mAidlContext.wakeReason;
+    }
+
     /** If the screen is currently on. */
     public boolean isDisplayOn() {
         return mIsDisplayOn;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 46337a9..bb79c99 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2340,7 +2340,6 @@
                 mAppOps,
                 new SysUiStatsEvent.BuilderFactory(),
                 mShowReviewPermissionsNotification);
-        mPreferencesHelper.updateFixedImportance(mUm.getUsers());
         mRankingHelper = new RankingHelper(getContext(),
                 mRankingHandler,
                 mPreferencesHelper,
@@ -2771,6 +2770,9 @@
             maybeShowInitialReviewPermissionsNotification();
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
+        } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
+            mPreferencesHelper.updateFixedImportance(mUm.getUsers());
+            mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers());
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4bafbc7..aa97aa3 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -237,7 +237,6 @@
                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                     NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
         }
-        ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
         synchronized (mPackagePreferences) {
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 tag = parser.getName();
@@ -255,27 +254,18 @@
                         String name = parser.getAttributeValue(null, ATT_NAME);
                         if (!TextUtils.isEmpty(name)) {
                             restorePackage(parser, forRestore, userId, name, upgradeForBubbles,
-                                    migrateToPermission, pkgPerms);
+                                    migrateToPermission);
                         }
                     }
                 }
             }
         }
-        if (migrateToPermission) {
-            for (PackagePermission p : pkgPerms) {
-                try {
-                    mPermissionHelper.setNotificationPermission(p);
-                } catch (Exception e) {
-                    Slog.e(TAG, "could not migrate setting for " + p.packageName, e);
-                }
-            }
-        }
     }
 
     @GuardedBy("mPackagePreferences")
     private void restorePackage(TypedXmlPullParser parser, boolean forRestore,
             @UserIdInt int userId, String name, boolean upgradeForBubbles,
-            boolean migrateToPermission, ArrayList<PermissionHelper.PackagePermission> pkgPerms) {
+            boolean migrateToPermission) {
         try {
             int uid = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
             if (forRestore) {
@@ -379,14 +369,6 @@
             if (migrateToPermission) {
                 r.importance = appImportance;
                 r.migrateToPm = true;
-                if (r.uid != UNKNOWN_UID) {
-                    // Don't call into permission system until we have a valid uid
-                    PackagePermission pkgPerm = new PackagePermission(
-                            r.pkg, UserHandle.getUserId(r.uid),
-                            r.importance != IMPORTANCE_NONE,
-                            hasUserConfiguredSettings(r));
-                    pkgPerms.add(pkgPerm);
-                }
             }
         } catch (Exception e) {
             Slog.w(TAG, "Failed to restore pkg", e);
@@ -2663,6 +2645,31 @@
         }
     }
 
+    public void migrateNotificationPermissions(List<UserInfo> users) {
+        for (UserInfo user : users) {
+            List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
+                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL),
+                    user.getUserHandle().getIdentifier());
+            for (PackageInfo pi : packages) {
+                synchronized (mPackagePreferences) {
+                    PackagePreferences p = getOrCreatePackagePreferencesLocked(
+                            pi.packageName, pi.applicationInfo.uid);
+                    if (p.migrateToPm && p.uid != UNKNOWN_UID) {
+                        try {
+                            PackagePermission pkgPerm = new PackagePermission(
+                                    p.pkg, UserHandle.getUserId(p.uid),
+                                    p.importance != IMPORTANCE_NONE,
+                                    hasUserConfiguredSettings(p));
+                            mPermissionHelper.setNotificationPermission(pkgPerm);
+                        } catch (Exception e) {
+                            Slog.e(TAG, "could not migrate setting for " + p.pkg, e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private void updateConfig() {
         mRankingHandler.requestSort();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index cf8460b..2d4f5ca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -53,6 +53,7 @@
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
+import android.app.UidObserver;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
@@ -83,6 +84,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.AlarmManagerInternal;
 import com.android.server.DropBoxManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService.Injector;
@@ -153,11 +155,14 @@
     private PackageManagerInternal mPackageManagerInt;
     @Mock
     private UsageStatsManagerInternal mUsageStatsManagerInt;
+    @Mock
+    private AlarmManagerInternal mAlarmManagerInt;
 
     private ActivityManagerService mAms;
     private BroadcastQueue mQueue;
     BroadcastConstants mConstants;
     private BroadcastSkipPolicy mSkipPolicy;
+    private UidObserver mUidObserver;
 
     /**
      * Desired behavior of the next
@@ -204,6 +209,8 @@
         LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+        LocalServices.removeServiceForTest(AlarmManagerInternal.class);
+        LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
         doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
         doAnswer((invocation) -> {
@@ -281,6 +288,11 @@
         }).when(mAms).getProcessRecordLocked(any(), anyInt());
         doNothing().when(mAms).appNotResponding(any(), any());
 
+        doAnswer((invocation) -> {
+            mUidObserver = invocation.getArgument(0);
+            return null;
+        }).when(mAms).registerUidObserver(any(), anyInt(), anyInt(), any());
+
         mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
         mConstants.TIMEOUT = 100;
         mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
@@ -305,6 +317,8 @@
         } else {
             throw new UnsupportedOperationException();
         }
+
+        mQueue.start(mContext.getContentResolver());
     }
 
     @After
@@ -683,12 +697,22 @@
                 anyInt(), anyInt(), any());
     }
 
-    private void verifyScheduleRegisteredReceiver(ProcessRecord app,
+    private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent)
+            throws Exception {
+        verifyScheduleRegisteredReceiver(times(1), app, intent, UserHandle.USER_SYSTEM);
+    }
+
+    private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
             Intent intent) throws Exception {
-        verify(app.getThread()).scheduleRegisteredReceiver(
+        verifyScheduleRegisteredReceiver(mode, app, intent, UserHandle.USER_SYSTEM);
+    }
+
+    private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
+            Intent intent, int userId) throws Exception {
+        verify(app.getThread(), mode).scheduleRegisteredReceiver(
                 any(), argThat(filterEqualsIgnoringComponent(intent)),
                 anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
-                eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+                eq(userId), anyInt(), anyInt(), any());
     }
 
     private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
@@ -1934,4 +1958,49 @@
                 getUidForPackage(PACKAGE_ORANGE));
         assertNull(receiverOrangeApp);
     }
+
+    /**
+     * Verify broadcasts to runtime receivers in cached processes are deferred
+     * until that process leaves the cached state.
+     */
+    @Test
+    public void testDeferralPolicy_UntilActive() throws Exception {
+        // Legacy stack doesn't support deferral
+        Assume.assumeTrue(mImpl == Impl.MODERN);
+
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+        final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+        receiverGreenApp.setCached(true);
+        receiverBlueApp.setCached(true);
+        receiverYellowApp.setCached(false);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastOptions opts = BroadcastOptions.makeBasic()
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, opts,
+                List.of(makeRegisteredReceiver(receiverGreenApp),
+                        makeRegisteredReceiver(receiverBlueApp),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                        makeRegisteredReceiver(receiverYellowApp))));
+        waitForIdle();
+
+        // Green ignored since it's in cached state
+        verifyScheduleRegisteredReceiver(never(), receiverGreenApp, airplane);
+
+        // Blue delivered both since it has a manifest receiver
+        verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+
+        // Yellow delivered since it's not cached
+        verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
+
+        // Shift green to be active and confirm that deferred broadcast is delivered
+        receiverGreenApp.setCached(false);
+        mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false);
+        waitForIdle();
+        verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..5adf391
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.server.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.WakeReason;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class BiometricFrameworkStatsLoggerTest {
+
+    @Test
+    public void testConvertsWakeReason_whenEmpty() {
+        final OperationContextExt ctx = new OperationContextExt();
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+        assertThat(reasonDetails).isEmpty();
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenPowerReason() {
+        final OperationContext context = new OperationContext();
+        context.wakeReason = WakeReason.WAKE_MOTION;
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(new OperationContextExt(context));
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION);
+        assertThat(reasonDetails).isEmpty();
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenFaceReason() {
+        final OperationContext context = new OperationContext();
+        context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
+                AuthenticateReason.Face.ASSISTANT_VISIBLE);
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+        assertThat(reasonDetails).asList().containsExactly(
+                BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE);
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenVendorReason() {
+        final OperationContext context = new OperationContext();
+        context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
+                new AuthenticateReason.Vendor());
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+        assertThat(reasonDetails).isEmpty();
+    }
+
+
+    @Test
+    public void testConvertsWakeReason_whenPowerAndFaceReason() {
+        final OperationContext context = new OperationContext();
+        context.wakeReason = WakeReason.WAKE_KEY;
+        context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
+                AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN);
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_KEY);
+        assertThat(reasonDetails).asList().containsExactly(
+                BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN);
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenPowerAndVendorReason() {
+        final OperationContext context = new OperationContext();
+        context.wakeReason = WakeReason.LID;
+        context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
+                new AuthenticateReason.Vendor());
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_LID);
+        assertThat(reasonDetails).isEmpty();
+    }
+}
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 06bcb91..50e5bbf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -679,10 +679,6 @@
         compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
         compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
 
-        verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
-        verify(mPermissionHelper).setNotificationPermission(oExpected);
-        verify(mPermissionHelper).setNotificationPermission(pExpected);
-
         // verify that we also write a state for review_permissions_notification to eventually
         // show a notification
         assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 87ca99f..06a86cc 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -46,6 +46,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
 
 
 /**
@@ -77,6 +78,8 @@
             new KnownNetworkConnectionStatus.Builder()
                     .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
                     .setExtras(Bundle.EMPTY).build();
+    // Used for testing
+    private CountDownLatch mCountDownLatch;
 
     @Override
     @Nullable
@@ -265,12 +268,24 @@
     public void onBind() {
     }
 
+    /** @hide */
+    @TestApi
+    public final void setCountdownLatch(@Nullable CountDownLatch latch) {
+        mCountDownLatch = latch;
+    }
+
     private void onRegisterCallback(ISharedConnectivityCallback callback) {
         mRemoteCallbackList.register(callback);
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
     }
 
     private void onUnregisterCallback(ISharedConnectivityCallback callback) {
         mRemoteCallbackList.unregister(callback);
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index 514ba3c..4a293cb 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -26,7 +26,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -39,6 +42,7 @@
 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 
@@ -51,12 +55,16 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Unit tests for {@link SharedConnectivityService}.
  */
 @SmallTest
 public class SharedConnectivityServiceTest {
+    private static final int LATCH_TIMEOUT = 2;
+
     private static final NetworkProviderInfo NETWORK_PROVIDER_INFO =
             new NetworkProviderInfo.Builder("TEST_NAME", "TEST_MODEL")
                     .setDeviceType(DEVICE_TYPE_TABLET).setConnectionStrength(2)
@@ -75,7 +83,7 @@
                     .addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo(
                             NETWORK_PROVIDER_INFO).build();
     private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
-    private static final HotspotNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS =
+    private static final HotspotNetworkConnectionStatus HOTSPOT_NETWORK_CONNECTION_STATUS =
             new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
                     .setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build();
     private static final KnownNetworkConnectionStatus KNOWN_NETWORK_CONNECTION_STATUS =
@@ -88,25 +96,77 @@
     @Mock
     Resources mResources;
 
+    @Mock
+    ISharedConnectivityCallback mCallback;
+
+    @Mock
+    IBinder mBinder;
+
     static class FakeSharedConnectivityService extends SharedConnectivityService {
         public void attachBaseContext(Context context) {
             super.attachBaseContext(context);
         }
 
+        private HotspotNetwork mConnectedHotspotNetwork;
+        private HotspotNetwork mDisconnectedHotspotNetwork;
+        private KnownNetwork mConnectedKnownNetwork;
+        private KnownNetwork mForgottenKnownNetwork;
+        private CountDownLatch mLatch;
+
+        public HotspotNetwork getConnectedHotspotNetwork() {
+            return mConnectedHotspotNetwork;
+        }
+
+        public HotspotNetwork getDisconnectedHotspotNetwork() {
+            return mDisconnectedHotspotNetwork;
+        }
+
+        public KnownNetwork getConnectedKnownNetwork() {
+            return mConnectedKnownNetwork;
+        }
+
+        public KnownNetwork getForgottenKnownNetwork() {
+            return mForgottenKnownNetwork;
+        }
+
+        public void initializeLatch() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        public CountDownLatch getLatch() {
+            return mLatch;
+        }
+
         @Override
         public void onConnectHotspotNetwork(@NonNull HotspotNetwork network) {
+            mConnectedHotspotNetwork = network;
+            if (mLatch != null) {
+                mLatch.countDown();
+            }
         }
 
         @Override
         public void onDisconnectHotspotNetwork(@NonNull HotspotNetwork network) {
+            mDisconnectedHotspotNetwork = network;
+            if (mLatch != null) {
+                mLatch.countDown();
+            }
         }
 
         @Override
         public void onConnectKnownNetwork(@NonNull KnownNetwork network) {
+            mConnectedKnownNetwork = network;
+            if (mLatch != null) {
+                mLatch.countDown();
+            }
         }
 
         @Override
         public void onForgetKnownNetwork(@NonNull KnownNetwork network) {
+            mForgottenKnownNetwork = network;
+            if (mLatch != null) {
+                mLatch.countDown();
+            }
         }
     }
 
@@ -165,10 +225,10 @@
         ISharedConnectivityService.Stub binder =
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
 
-        service.updateHotspotNetworkConnectionStatus(TETHER_NETWORK_CONNECTION_STATUS);
+        service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
 
         assertThat(binder.getHotspotNetworkConnectionStatus())
-                .isEqualTo(TETHER_NETWORK_CONNECTION_STATUS);
+                .isEqualTo(HOTSPOT_NETWORK_CONNECTION_STATUS);
     }
 
     @Test
@@ -225,7 +285,115 @@
         assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isFalse();
     }
 
-    private SharedConnectivityService createService() {
+    @Test
+    public void connectHotspotNetwork() throws RemoteException, InterruptedException {
+        FakeSharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        service.initializeLatch();
+
+        binder.connectHotspotNetwork(HOTSPOT_NETWORK);
+
+        assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        assertThat(service.getConnectedHotspotNetwork()).isEqualTo(HOTSPOT_NETWORK);
+    }
+
+    @Test
+    public void disconnectHotspotNetwork() throws RemoteException, InterruptedException {
+        FakeSharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        service.initializeLatch();
+
+        binder.disconnectHotspotNetwork(HOTSPOT_NETWORK);
+
+        assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        assertThat(service.getDisconnectedHotspotNetwork()).isEqualTo(HOTSPOT_NETWORK);
+    }
+
+    @Test
+    public void connectKnownNetwork() throws RemoteException , InterruptedException {
+        FakeSharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        service.initializeLatch();
+
+        binder.connectKnownNetwork(KNOWN_NETWORK);
+
+        assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        assertThat(service.getConnectedKnownNetwork()).isEqualTo(KNOWN_NETWORK);
+    }
+
+    @Test
+    public void forgetKnownNetwork() throws RemoteException, InterruptedException {
+        FakeSharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        service.initializeLatch();
+
+        binder.forgetKnownNetwork(KNOWN_NETWORK);
+
+        assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        assertThat(service.getForgottenKnownNetwork()).isEqualTo(KNOWN_NETWORK);
+    }
+
+    @Test
+    public void registerCallback() throws RemoteException, InterruptedException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        when(mCallback.asBinder()).thenReturn(mBinder);
+        when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+        SharedConnectivitySettingsState state = buildSettingsState();
+
+        CountDownLatch latch = new CountDownLatch(1);
+        service.setCountdownLatch(latch);
+        binder.registerCallback(mCallback);
+        assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        service.setHotspotNetworks(HOTSPOT_NETWORKS);
+        service.setKnownNetworks(KNOWN_NETWORKS);
+        service.setSettingsState(state);
+        service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+        service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+        verify(mCallback).onHotspotNetworksUpdated(HOTSPOT_NETWORKS);
+        verify(mCallback).onKnownNetworksUpdated(KNOWN_NETWORKS);
+        verify(mCallback).onSharedConnectivitySettingsChanged(state);
+        verify(mCallback).onHotspotNetworkConnectionStatusChanged(
+                HOTSPOT_NETWORK_CONNECTION_STATUS);
+        verify(mCallback).onKnownNetworkConnectionStatusChanged(KNOWN_NETWORK_CONNECTION_STATUS);
+    }
+
+    @Test
+    public void unregisterCallback() throws RemoteException, InterruptedException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        when(mCallback.asBinder()).thenReturn(mBinder);
+        when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+
+        CountDownLatch latch = new CountDownLatch(1);
+        service.setCountdownLatch(latch);
+        binder.registerCallback(mCallback);
+        assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        latch = new CountDownLatch(1);
+        service.setCountdownLatch(latch);
+        binder.unregisterCallback(mCallback);
+        assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+        service.setHotspotNetworks(HOTSPOT_NETWORKS);
+        service.setKnownNetworks(KNOWN_NETWORKS);
+        service.setSettingsState(buildSettingsState());
+        service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+        service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+        verify(mCallback, never()).onHotspotNetworksUpdated(any());
+        verify(mCallback, never()).onKnownNetworksUpdated(any());
+        verify(mCallback, never()).onSharedConnectivitySettingsChanged(any());
+        verify(mCallback, never()).onHotspotNetworkConnectionStatusChanged(any());
+        verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any());
+    }
+
+    private FakeSharedConnectivityService createService() {
         FakeSharedConnectivityService service = new FakeSharedConnectivityService();
         service.attachBaseContext(mContext);
         return service;