Merge "Pass title and description as intent params to the picker" into main
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0947e33..5a3a8d5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.app.Instrumentation.DEBUG_FINISH_ACTIVITY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
@@ -7297,6 +7298,9 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void finish(int finishTask) {
+        if (DEBUG_FINISH_ACTIVITY) {
+            Log.d("Instrumentation", "finishActivity: finishTask=" + finishTask, new Throwable());
+        }
         if (mParent == null) {
             int resultCode;
             Intent resultData;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index db216b1..be27046 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -107,6 +107,8 @@
     // If set, will print the stack trace for activity starts within the process
     static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
             SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
+    static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE &&
+            SystemProperties.getBoolean("persist.wm.debug.finish_activity", false);
 
     /**
      * @hide
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index ba356bb..6514872 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -16,6 +16,8 @@
 
 package android.database;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.BytesLong;
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -640,6 +642,7 @@
      */
     public boolean putBlob(byte[] value,
             @IntRange(from = 0) int row, @IntRange(from = 0) int column) {
+        requireNonNull(value);
         acquireReference();
         try {
             return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
@@ -658,6 +661,7 @@
      */
     public boolean putString(String value,
             @IntRange(from = 0) int row, @IntRange(from = 0) int column) {
+        requireNonNull(value);
         acquireReference();
         try {
             return nativePutString(mWindowPtr, value, row - mStartPos, column);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index cbac912..ca3e3d2 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -569,7 +569,6 @@
             return native_setup(
                     new WeakReference<>(this),
                     cameraId,
-                    ActivityThread.currentOpPackageName(),
                     rotationOverride,
                     forceSlowJpegMode,
                     clientAttribution.getParcel(),
@@ -660,7 +659,6 @@
     private native int native_setup(
             Object cameraThis,
             int cameraId,
-            String packageName,
             int rotationOverride,
             boolean forceSlowJpegMode,
             Parcel clientAttributionParcel,
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 2dbd4b8..6201359 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -980,6 +980,8 @@
         clientAttribution.uid = USE_CALLING_UID;
         clientAttribution.pid = USE_CALLING_PID;
         clientAttribution.deviceId = contextAttribution.deviceId;
+        clientAttribution.packageName = mContext.getOpPackageName();
+        clientAttribution.attributionTag = mContext.getAttributionTag();
         clientAttribution.next = new AttributionSourceState[0];
         return clientAttribution;
     }
@@ -1041,8 +1043,6 @@
                         cameraService.connectDevice(
                                 callbacks,
                                 cameraId,
-                                mContext.getOpPackageName(),
-                                mContext.getAttributionTag(),
                                 oomScoreOffset,
                                 mContext.getApplicationInfo().targetSdkVersion,
                                 rotationOverride,
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index b8fd3d0..3f74fac 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -582,8 +582,8 @@
 
 // connect to camera service
 static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
-                                                 jint cameraId, jstring clientPackageName,
-                                                 jint rotationOverride, jboolean forceSlowJpegMode,
+                                                 jint cameraId, jint rotationOverride,
+                                                 jboolean forceSlowJpegMode,
                                                  jobject jClientAttributionParcel,
                                                  jint devicePolicy) {
     AttributionSourceState clientAttribution;
@@ -591,16 +591,8 @@
         return -EACCES;
     }
 
-    // Convert jstring to String16
-    const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
-        env->GetStringChars(clientPackageName, NULL));
-    jsize rawClientNameLen = env->GetStringLength(clientPackageName);
-    std::string clientName = toStdString(rawClientName, rawClientNameLen);
-    env->ReleaseStringChars(clientPackageName,
-                            reinterpret_cast<const jchar*>(rawClientName));
-
     int targetSdkVersion = android_get_application_target_sdk_version();
-    sp<Camera> camera = Camera::connect(cameraId, clientName, targetSdkVersion, rotationOverride,
+    sp<Camera> camera = Camera::connect(cameraId, targetSdkVersion, rotationOverride,
                                         forceSlowJpegMode, clientAttribution, devicePolicy);
     if (camera == NULL) {
         return -EACCES;
@@ -1089,7 +1081,7 @@
          (void *)android_hardware_Camera_getNumberOfCameras},
         {"_getCameraInfo", "(IILandroid/os/Parcel;ILandroid/hardware/Camera$CameraInfo;)V",
          (void *)android_hardware_Camera_getCameraInfo},
-        {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZLandroid/os/Parcel;I)I",
+        {"native_setup", "(Ljava/lang/Object;IIZLandroid/os/Parcel;I)I",
          (void *)android_hardware_Camera_native_setup},
         {"native_release", "()V", (void *)android_hardware_Camera_release},
         {"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 822a07c..544f0f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -894,9 +894,7 @@
 
     private static boolean isDraggingToFullscreenAllowed(
             @NonNull DividerAttributes dividerAttributes) {
-        // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
-        // updated to v7.
-        return false;
+        return dividerAttributes.isDraggingToFullscreenAllowed();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index b1cbe8d..3572d16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -97,7 +97,7 @@
 adb logcat -s "SurfaceControlRegistry"
 ```
 
-## Tracing activity starts in the app process
+## Tracing activity starts & finishes in the app process
 
 It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
 (ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
@@ -113,6 +113,19 @@
 adb reboot
 ```
 
+Likewise, to trace where a finish() call may be made in the app process, you can enable this system
+property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.finish_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.finish_activity \"\"
+adb reboot
+```
+
 ## Dumps
 
 Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index 102d21a..43acbb1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -2246,6 +2246,8 @@
         clientAttribution.uid = -1; // USE_CALLING_UID
         clientAttribution.pid = -1; // USE_CALLING_PID
         clientAttribution.deviceId = contextAttribution.deviceId;
+        clientAttribution.packageName = context.getOpPackageName();
+        clientAttribution.attributionTag = context.getAttributionTag();
         clientAttribution.next = new AttributionSourceState[0];
         return clientAttribution;
     }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index ad3374a..ac85ab7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -169,10 +169,8 @@
 
             ICameraClient dummyCallbacks = new DummyCameraClient();
 
-            String clientPackageName = getContext().getPackageName();
-
             ICamera cameraUser = mUtils.getCameraService()
-                    .connect(dummyCallbacks, cameraId, clientPackageName,
+                    .connect(dummyCallbacks, cameraId,
                             getContext().getApplicationInfo().targetSdkVersion,
                             ICameraService.ROTATION_OVERRIDE_NONE,
                             /*forceSlowJpegMode*/false,
@@ -267,8 +265,6 @@
 
             ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks();
 
-            String clientPackageName = getContext().getPackageName();
-            String clientAttributionTag = getContext().getAttributionTag();
             AttributionSourceState clientAttribution =
                     CameraTestUtils.getClientAttribution(mContext);
             clientAttribution.deviceId = DEVICE_ID_DEFAULT;
@@ -277,7 +273,6 @@
             ICameraDeviceUser cameraUser =
                     mUtils.getCameraService().connectDevice(
                         dummyCallbacks, String.valueOf(cameraId),
-                        clientPackageName, clientAttributionTag,
                         0 /*oomScoreOffset*/,
                         getContext().getApplicationInfo().targetSdkVersion,
                         ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 0ab1ee9..35ad924 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -242,9 +242,6 @@
 
         ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks();
 
-        String clientPackageName = getContext().getPackageName();
-        String clientAttributionTag = getContext().getAttributionTag();
-
         mMockCb = spy(dummyCallbacks);
 
         AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
@@ -252,7 +249,6 @@
         clientAttribution.uid = ICameraService.USE_CALLING_UID;
 
         mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
-                clientPackageName, clientAttributionTag,
                 /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
                 ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT);
         assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 861c405..fd4fc20 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -740,8 +740,6 @@
     // The settings provider must hold its lock when calling here.
     @GuardedBy("mLock")
     public void removeSettingsForPackageLocked(String packageName) {
-        boolean removedSomething = false;
-
         final int settingCount = mSettings.size();
         for (int i = settingCount - 1; i >= 0; i--) {
             String name = mSettings.keyAt(i);
@@ -752,14 +750,9 @@
             }
             Setting setting = mSettings.valueAt(i);
             if (packageName.equals(setting.packageName)) {
-                mSettings.removeAt(i);
-                removedSomething = true;
+                deleteSettingLocked(setting.name);
             }
         }
-
-        if (removedSomething) {
-            scheduleWriteIfNeededLocked();
-        }
     }
 
     // The settings provider must hold its lock when calling here.
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 4b4ced3..48ce49d 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -988,7 +988,40 @@
     }
 
     @Test
-   public void testGetFlagOverrideToSync() {
+    public void testMemoryUsagePerPackage_StatsUpdatedOnAppDataCleared() {
+        SettingsState settingsState =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        final String testKey1 = SETTING_NAME;
+        final String testKey2 = SETTING_NAME + "_2";
+        final String testValue1 = Strings.repeat("A", 9000);
+        final String testValue2 = Strings.repeat("A", 9001);
+        final String packageName = "p";
+        // Inserting the first setting should be okay
+        settingsState.insertSettingLocked(testKey1, testValue1, null, true, packageName);
+        int expectedMemUsageForPackage = (testKey1.length() + testValue1.length()
+                + testValue1.length() /* size for default */) * Character.BYTES;
+        assertEquals(expectedMemUsageForPackage, settingsState.getMemoryUsage(packageName));
+        // Inserting the second setting should fail
+        try {
+            settingsState.insertSettingLocked(testKey2, testValue2, null, true, packageName);
+            fail("Should throw because it exceeded max memory usage per package");
+        } catch (IllegalStateException ex) {
+            assertTrue(ex.getMessage().startsWith("You are adding too many system settings."));
+        }
+        // Now clear app data and check that the memory usage is cleared
+        settingsState.removeSettingsForPackageLocked(packageName);
+        assertEquals(0, settingsState.getMemoryUsage(packageName));
+        // Try inserting the second setting again and it should go through
+        settingsState.insertSettingLocked(testKey2, testValue2, null, true, packageName);
+        expectedMemUsageForPackage = (testKey2.length() + testValue2.length()
+                + testValue2.length() /* size for default */) * Character.BYTES;
+        assertEquals(expectedMemUsageForPackage, settingsState.getMemoryUsage(packageName));
+    }
+
+    @Test
+    public void testGetFlagOverrideToSync() {
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
         Object lock = new Object();
         SettingsState settingsState =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 887e349..25e91be 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.composable
 
+import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
@@ -48,6 +49,16 @@
     fun SceneScope.Content(
         modifier: Modifier = Modifier,
     ) {
+        val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
+        if (!isContentVisible) {
+            // If the content isn't supposed to be visible, show a large empty box as it's needed
+            // for scene transition animations (can't just skip rendering everything or shared
+            // elements won't have correct final/initial bounds from animating in and out of the
+            // lockscreen scene).
+            Box(modifier)
+            return
+        }
+
         val coroutineScope = rememberCoroutineScope()
         val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle()
         val view = LocalView.current
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
deleted file mode 100644
index 07d8890..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * 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.ambient.touch;
-
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.DreamManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ShadeTouchHandlerTest extends SysuiTestCase {
-    @Mock
-    CentralSurfaces mCentralSurfaces;
-
-    @Mock
-    ShadeViewController mShadeViewController;
-
-    @Mock
-    DreamManager mDreamManager;
-
-    @Mock
-    TouchHandler.TouchSession mTouchSession;
-
-    ShadeTouchHandler mTouchHandler;
-
-    @Captor
-    ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor;
-    @Captor
-    ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor;
-
-    private static final int TOUCH_HEIGHT = 20;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
-                mDreamManager, TOUCH_HEIGHT);
-    }
-
-    // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
-    @Test
-    public void testSwipeDown_captured() {
-        final boolean captured = swipe(Direction.DOWN);
-
-        assertThat(captured).isTrue();
-    }
-
-    // Verifies that a swipe in the upward direction is not captured.
-    @Test
-    public void testSwipeUp_notCaptured() {
-        final boolean captured = swipe(Direction.UP);
-
-        // Motion events not captured as the swipe is going in the wrong direction.
-        assertThat(captured).isFalse();
-    }
-
-    // Verifies that a swipe down forwards captured touches to central surfaces for handling.
-    @Test
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-    public void testSwipeDown_communalEnabled_sentToCentralSurfaces() {
-        swipe(Direction.DOWN);
-
-        // Both motion events are sent for central surfaces to process.
-        verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any());
-    }
-
-    // Verifies that a swipe down forwards captured touches to the shade view for handling.
-    @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
-    public void testSwipeDown_communalDisabled_sentToShadeView() {
-        swipe(Direction.DOWN);
-
-        // Both motion events are sent for the shade view to process.
-        verify(mShadeViewController, times(2)).handleExternalTouch(any());
-    }
-
-    // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
-    // handling.
-    @Test
-    public void testSwipeDown_dreaming_sentToShadeView() {
-        when(mDreamManager.isDreaming()).thenReturn(true);
-
-        swipe(Direction.DOWN);
-
-        // Both motion events are sent for the shade view to process.
-        verify(mShadeViewController, times(2)).handleExternalTouch(any());
-    }
-
-    // Verifies that a swipe up is not forwarded to central surfaces.
-    @Test
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-    public void testSwipeUp_communalEnabled_touchesNotSent() {
-        swipe(Direction.UP);
-
-        // Motion events are not sent for central surfaces to process as the swipe is going in the
-        // wrong direction.
-        verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any());
-    }
-
-    // Verifies that a swipe up is not forwarded to the shade view.
-    @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
-    public void testSwipeUp_communalDisabled_touchesNotSent() {
-        swipe(Direction.UP);
-
-        // Motion events are not sent for the shade view to process as the swipe is going in the
-        // wrong direction.
-        verify(mShadeViewController, never()).handleExternalTouch(any());
-    }
-
-    /**
-     * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
-     * touch handler's gesture listener.
-     * <p>
-     * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
-     * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0.
-     */
-    private boolean swipe(Direction direction) {
-        Mockito.clearInvocations(mTouchSession);
-        mTouchHandler.onSessionStart(mTouchSession);
-
-        verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture());
-        verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture());
-
-        final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0;
-        final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT;
-
-        // Send touches to the input and gesture listener.
-        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0);
-        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0);
-        mInputListenerCaptor.getValue().onInputEvent(event1);
-        mInputListenerCaptor.getValue().onInputEvent(event2);
-        final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0,
-                startY - endY);
-
-        return captured;
-    }
-
-    private enum Direction {
-        DOWN, UP,
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
new file mode 100644
index 0000000..4314676
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.ambient.touch
+
+import android.app.DreamManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.GestureDetector
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.google.common.truth.Truth
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeTouchHandlerTest : SysuiTestCase() {
+    private var mCentralSurfaces = mock<CentralSurfaces>()
+    private var mShadeViewController = mock<ShadeViewController>()
+    private var mDreamManager = mock<DreamManager>()
+    private var mTouchSession = mock<TouchSession>()
+
+    private lateinit var mTouchHandler: ShadeTouchHandler
+
+    private var mGestureListenerCaptor = argumentCaptor<GestureDetector.OnGestureListener>()
+    private var mInputListenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>()
+
+    @Before
+    fun setup() {
+        mTouchHandler =
+            ShadeTouchHandler(
+                Optional.of(mCentralSurfaces),
+                mShadeViewController,
+                mDreamManager,
+                TOUCH_HEIGHT
+            )
+    }
+
+    // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
+    @Test
+    fun testSwipeDown_captured() {
+        val captured = swipe(Direction.DOWN)
+        Truth.assertThat(captured).isTrue()
+    }
+
+    // Verifies that a swipe in the upward direction is not captured.
+    @Test
+    fun testSwipeUp_notCaptured() {
+        val captured = swipe(Direction.UP)
+
+        // Motion events not captured as the swipe is going in the wrong direction.
+        Truth.assertThat(captured).isFalse()
+    }
+
+    // Verifies that a swipe down forwards captured touches to central surfaces for handling.
+    @Test
+    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+    fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
+        swipe(Direction.DOWN)
+
+        // Both motion events are sent for central surfaces to process.
+        verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any())
+    }
+
+    // Verifies that a swipe down forwards captured touches to the shade view for handling.
+    @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    fun testSwipeDown_communalDisabled_sentToShadeView() {
+        swipe(Direction.DOWN)
+
+        // Both motion events are sent for the shade view to process.
+        verify(mShadeViewController, times(2)).handleExternalTouch(any())
+    }
+
+    // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
+    // handling.
+    @Test
+    fun testSwipeDown_dreaming_sentToShadeView() {
+        whenever(mDreamManager.isDreaming).thenReturn(true)
+        swipe(Direction.DOWN)
+
+        // Both motion events are sent for the shade view to process.
+        verify(mShadeViewController, times(2)).handleExternalTouch(any())
+    }
+
+    // Verifies that a swipe up is not forwarded to central surfaces.
+    @Test
+    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+    fun testSwipeUp_communalEnabled_touchesNotSent() {
+        swipe(Direction.UP)
+
+        // Motion events are not sent for central surfaces to process as the swipe is going in the
+        // wrong direction.
+        verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any())
+    }
+
+    // Verifies that a swipe up is not forwarded to the shade view.
+    @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    fun testSwipeUp_communalDisabled_touchesNotSent() {
+        swipe(Direction.UP)
+
+        // Motion events are not sent for the shade view to process as the swipe is going in the
+        // wrong direction.
+        verify(mShadeViewController, never()).handleExternalTouch(any())
+    }
+
+    /**
+     * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
+     * touch handler's gesture listener.
+     *
+     * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
+     * of the gesture region, [.TOUCH_HEIGHT], and goes upward to 0.
+     */
+    private fun swipe(direction: Direction): Boolean {
+        clearInvocations(mTouchSession)
+        mTouchHandler.onSessionStart(mTouchSession)
+        verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture())
+        verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture())
+        val startY = (if (direction == Direction.UP) TOUCH_HEIGHT else 0).toFloat()
+        val endY = (if (direction == Direction.UP) 0 else TOUCH_HEIGHT).toFloat()
+
+        // Send touches to the input and gesture listener.
+        val event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0f, startY, 0)
+        val event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0f, endY, 0)
+        mInputListenerCaptor.lastValue.onInputEvent(event1)
+        mInputListenerCaptor.lastValue.onInputEvent(event2)
+        return mGestureListenerCaptor.lastValue.onScroll(event1, event2, 0f, startY - endY)
+    }
+
+    private enum class Direction {
+        DOWN,
+        UP
+    }
+
+    companion object {
+        private const val TOUCH_HEIGHT = 20
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index de4b999..875e9e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.platform.test.flag.junit.FlagsParameterization
@@ -27,10 +29,13 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.shared.model.ClockSize
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
@@ -38,6 +43,8 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -201,6 +208,44 @@
             }
         }
 
+    @Test
+    fun isContentVisible_whenNotOccluded_visible() =
+        with(kosmos) {
+            testScope.runTest {
+                val isContentVisible by collectLastValue(underTest.isContentVisible)
+
+                keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null)
+                runCurrent()
+                assertThat(isContentVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun isContentVisible_whenOccluded_notVisible() =
+        with(kosmos) {
+            testScope.runTest {
+                val isContentVisible by collectLastValue(underTest.isContentVisible)
+
+                keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+                runCurrent()
+                assertThat(isContentVisible).isFalse()
+            }
+        }
+
+    @Test
+    fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() =
+        with(kosmos) {
+            testScope.runTest {
+                val isContentVisible by collectLastValue(underTest.isContentVisible)
+                keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+                runCurrent()
+
+                sceneInteractor.snapToScene(Scenes.Shade, "")
+                runCurrent()
+                assertThat(isContentVisible).isFalse()
+            }
+        }
+
     private fun prepareConfiguration(): Int {
         val configuration = context.resources.configuration
         configuration.setLayoutDirection(Locale.US)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 1de0abe..8a29f96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSize
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -48,6 +49,7 @@
     val shadeInteractor: ShadeInteractor,
     @Application private val applicationScope: CoroutineScope,
     unfoldTransitionInteractor: UnfoldTransitionInteractor,
+    occlusionInteractor: SceneContainerOcclusionInteractor,
 ) {
     @VisibleForTesting val clockSize = clockInteractor.clockSize
 
@@ -93,6 +95,16 @@
                 initialValue = UnfoldTranslations(),
             )
 
+    /** Whether the content of the scene UI should be shown. */
+    val isContentVisible: StateFlow<Boolean> =
+        occlusionInteractor.isOccludingActivityShown
+            .map { !it }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = true,
+            )
+
     fun getSmartSpacePaddingTop(resources: Resources): Int {
         return if (clockSize.value == ClockSize.LARGE) {
             resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index 233e9b5..2d510e1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -43,8 +43,14 @@
     sceneInteractor: SceneInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
-    /** Whether a show-when-locked activity is at the top of the current activity stack. */
-    private val isOccludingActivityShown: StateFlow<Boolean> =
+    /**
+     * Whether a show-when-locked activity is at the top of the current activity stack.
+     *
+     * Note: this isn't enough to figure out whether the scene container UI should be invisible as
+     * that also depends on the things like the state of AOD and the current scene. If the code
+     * needs that, [invisibleDueToOcclusion] should be collected instead.
+     */
+    val isOccludingActivityShown: StateFlow<Boolean> =
         keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn(
             scope = applicationScope,
             started = SharingStarted.WhileSubscribed(),
@@ -69,6 +75,9 @@
     /**
      * Whether the scene container should become invisible due to "occlusion" by an in-foreground
      * "show when locked" activity.
+     *
+     * Note: this returns `false` when an overlaid scene (like shade or QS) is shown above the
+     * occluding activity.
      */
     val invisibleDueToOcclusion: StateFlow<Boolean> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 534d9d2..900201f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -186,6 +186,7 @@
         interactor.configurationBasedDimensions
             .map {
                 when {
+                    !it.useSplitShade -> 0
                     it.useLargeScreenHeader -> it.marginTopLargeScreen
                     else -> it.marginTop
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 97f5efc..677d1fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1799,8 +1799,10 @@
             when {
                 fingerprint != null && face != null -> "coex"
                 fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
-                fingerprint != null && !fingerprint.isAnySidefpsType ->
-                    "fingerprint only, non-sideFps"
+                fingerprint != null && fingerprint.isAnyUdfpsType -> "fingerprint only, udfps"
+                fingerprint != null &&
+                    fingerprint.sensorType == FingerprintSensorProperties.TYPE_REAR ->
+                    "fingerprint only, rearFps"
                 face != null -> "face only"
                 else -> "?"
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 24e47b0..550ecb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
 
@@ -34,5 +35,6 @@
             shadeInteractor = shadeInteractor,
             applicationScope = applicationCoroutineScope,
             unfoldTransitionInteractor = unfoldTransitionInteractor,
+            occlusionInteractor = sceneContainerOcclusionInteractor,
         )
     }
diff --git a/services/core/java/com/android/server/CertBlacklister.java b/services/core/java/com/android/server/CertBlacklister.java
deleted file mode 100644
index e726c6a..0000000
--- a/services/core/java/com/android/server/CertBlacklister.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.content.Context;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.os.Binder;
-import android.os.FileUtils;
-import android.provider.Settings;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import libcore.io.IoUtils;
-
-/**
- * <p>CertBlacklister provides a simple mechanism for updating the platform denylists for SSL
- * certificate public keys and serial numbers.
- */
-public class CertBlacklister extends Binder {
-
-    private static final String TAG = "CertBlacklister";
-
-    private static final String DENYLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
-
-    public static final String PUBKEY_PATH = DENYLIST_ROOT + "pubkey_blacklist.txt";
-    public static final String SERIAL_PATH = DENYLIST_ROOT + "serial_blacklist.txt";
-
-    public static final String PUBKEY_BLACKLIST_KEY = "pubkey_blacklist";
-    public static final String SERIAL_BLACKLIST_KEY = "serial_blacklist";
-
-    private static class BlacklistObserver extends ContentObserver {
-
-        private final String mKey;
-        private final String mName;
-        private final String mPath;
-        private final File mTmpDir;
-        private final ContentResolver mContentResolver;
-
-        public BlacklistObserver(String key, String name, String path, ContentResolver cr) {
-            super(null);
-            mKey = key;
-            mName = name;
-            mPath = path;
-            mTmpDir = new File(mPath).getParentFile();
-            mContentResolver = cr;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            writeDenylist();
-        }
-
-        public String getValue() {
-            return Settings.Secure.getString(mContentResolver, mKey);
-        }
-
-        private void writeDenylist() {
-            new Thread("BlacklistUpdater") {
-                public void run() {
-                    synchronized(mTmpDir) {
-                        String blacklist = getValue();
-                        if (blacklist != null) {
-                            Slog.i(TAG, "Certificate blacklist changed, updating...");
-                            FileOutputStream out = null;
-                            try {
-                                // create a temporary file
-                                File tmp = File.createTempFile("journal", "", mTmpDir);
-                                // mark it -rw-r--r--
-                                tmp.setReadable(true, false);
-                                // write to it
-                                out = new FileOutputStream(tmp);
-                                out.write(blacklist.getBytes());
-                                // sync to disk
-                                FileUtils.sync(out);
-                                // atomic rename
-                                tmp.renameTo(new File(mPath));
-                                Slog.i(TAG, "Certificate blacklist updated");
-                            } catch (IOException e) {
-                                Slog.e(TAG, "Failed to write blacklist", e);
-                            } finally {
-                                IoUtils.closeQuietly(out);
-                            }
-                        }
-                    }
-                }
-            }.start();
-        }
-    }
-
-    public CertBlacklister(Context context) {
-        registerObservers(context.getContentResolver());
-    }
-
-    private BlacklistObserver buildPubkeyObserver(ContentResolver cr) {
-        return new BlacklistObserver(PUBKEY_BLACKLIST_KEY,
-                    "pubkey",
-                    PUBKEY_PATH,
-                    cr);
-    }
-
-    private BlacklistObserver buildSerialObserver(ContentResolver cr) {
-        return new BlacklistObserver(SERIAL_BLACKLIST_KEY,
-                    "serial",
-                    SERIAL_PATH,
-                    cr);
-    }
-
-    private void registerObservers(ContentResolver cr) {
-        // set up the public key denylist observer
-        cr.registerContentObserver(
-            Settings.Secure.getUriFor(PUBKEY_BLACKLIST_KEY),
-            true,
-            buildPubkeyObserver(cr)
-        );
-
-        // set up the serial number denylist observer
-        cr.registerContentObserver(
-            Settings.Secure.getUriFor(SERIAL_BLACKLIST_KEY),
-            true,
-            buildSerialObserver(cr)
-        );
-    }
-}
diff --git a/services/core/java/com/android/server/CertBlocklister.java b/services/core/java/com/android/server/CertBlocklister.java
new file mode 100644
index 0000000..9e23f88
--- /dev/null
+++ b/services/core/java/com/android/server/CertBlocklister.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.provider.Settings;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * <p>CertBlocklister provides a simple mechanism for updating the platform denylists for SSL
+ * certificate public keys and serial numbers.
+ */
+public class CertBlocklister extends Binder {
+
+    private static final String TAG = "CertBlocklister";
+
+    private static final String DENYLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
+
+    /* For compatibility reasons, the name of these paths cannot be changed */
+    public static final String PUBKEY_PATH = DENYLIST_ROOT + "pubkey_blacklist.txt";
+    public static final String SERIAL_PATH = DENYLIST_ROOT + "serial_blacklist.txt";
+
+    /* For compatibility reasons, the name of these keys cannot be changed */
+    public static final String PUBKEY_BLOCKLIST_KEY = "pubkey_blacklist";
+    public static final String SERIAL_BLOCKLIST_KEY = "serial_blacklist";
+
+    private static class BlocklistObserver extends ContentObserver {
+
+        private final String mKey;
+        private final String mName;
+        private final String mPath;
+        private final File mTmpDir;
+        private final ContentResolver mContentResolver;
+
+        BlocklistObserver(String key, String name, String path, ContentResolver cr) {
+            super(null);
+            mKey = key;
+            mName = name;
+            mPath = path;
+            mTmpDir = new File(mPath).getParentFile();
+            mContentResolver = cr;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            new Thread("BlocklistUpdater") {
+                public void run() {
+                    writeDenylist();
+                }
+            }.start();
+        }
+
+        public String getValue() {
+            return Settings.Secure.getStringForUser(
+                mContentResolver, mKey, mContentResolver.getUserId());
+        }
+
+        private void writeDenylist() {
+            synchronized (mTmpDir) {
+                String blocklist = getValue();
+                if (blocklist == null) {
+                    return;
+                }
+                if (mPath.equals(SERIAL_PATH)) {
+                    Slog.w(TAG, "The certificate blocklist based on serials is deprecated. "
+                            + "Please use the pubkey blocklist instead.");
+                }
+                Slog.i(TAG, "Certificate blocklist changed, updating...");
+                FileOutputStream out = null;
+                try {
+                    // Create a temporary file and rename it atomically.
+                    File tmp = File.createTempFile("journal", "", mTmpDir);
+                    tmp.setReadable(true /* readable */, false /* ownerOnly */);
+                    out = new FileOutputStream(tmp);
+                    out.write(blocklist.getBytes());
+                    FileUtils.sync(out);
+                    tmp.renameTo(new File(mPath));
+                    Slog.i(TAG, "Certificate blocklist updated");
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to write blocklist", e);
+                } finally {
+                    IoUtils.closeQuietly(out);
+                }
+            }
+        }
+    }
+
+    public CertBlocklister(Context context) {
+        registerObservers(context.getContentResolver());
+    }
+
+    private BlocklistObserver buildPubkeyObserver(ContentResolver cr) {
+        return new BlocklistObserver(PUBKEY_BLOCKLIST_KEY,
+                    "pubkey",
+                    PUBKEY_PATH,
+                    cr);
+    }
+
+    private BlocklistObserver buildSerialObserver(ContentResolver cr) {
+        return new BlocklistObserver(SERIAL_BLOCKLIST_KEY,
+                    "serial",
+                    SERIAL_PATH,
+                    cr);
+    }
+
+    private void registerObservers(ContentResolver cr) {
+        // set up the public key denylist observer
+        cr.registerContentObserver(
+                Settings.Secure.getUriFor(PUBKEY_BLOCKLIST_KEY),
+                true,
+                buildPubkeyObserver(cr)
+        );
+
+        // set up the serial number denylist observer
+        cr.registerContentObserver(
+                Settings.Secure.getUriFor(SERIAL_BLOCKLIST_KEY),
+                true,
+                buildSerialObserver(cr)
+        );
+    }
+}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bc83a0e..bacfd8f 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -921,8 +921,7 @@
 
     //helper function to determine if limit on num listeners applies to callingUid
     private boolean doesLimitApplyForListeners(int callingUid, int exemptUid) {
-        return (callingUid != Process.SYSTEM_UID
-                && callingUid != Process.PHONE_UID
+        return (!TelephonyPermissions.isSystemOrPhone(callingUid)
                 && callingUid != exemptUid);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c0b8034..2e63cdb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -186,6 +186,7 @@
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.telephony.CarrierAppUtils;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -4492,8 +4493,7 @@
     void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName,
             boolean hidden) {
         final int callingUid = Binder.getCallingUid();
-        final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID
-                || callingUid == Process.SYSTEM_UID;
+        final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid);
         if (!calledFromSystemOrPhone) {
             mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
                     "setSystemAppHiddenUntilInstalled");
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index ff8abf8..924b36c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -92,6 +92,7 @@
 
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -356,7 +357,7 @@
      * If not, throws a {@link SecurityException}.
      */
     public static void enforceSystemOrPhoneCaller(String methodName, int callingUid) {
-        if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) {
+        if (!TelephonyPermissions.isSystemOrPhone(callingUid)) {
             throw new SecurityException(
                     "Cannot call " + methodName + " from UID " + callingUid);
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 791d030..e5a1ebf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2445,11 +2445,11 @@
                 t.traceEnd();
             }
 
-            t.traceBegin("CertBlacklister");
+            t.traceBegin("CertBlocklister");
             try {
-                CertBlacklister blacklister = new CertBlacklister(context);
+                CertBlocklister blocklister = new CertBlocklister(context);
             } catch (Throwable e) {
-                reportWtf("starting CertBlacklister", e);
+                reportWtf("starting CertBlocklister", e);
             }
             t.traceEnd();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 7aec42b..00daf41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -43,6 +43,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
@@ -142,6 +143,7 @@
     private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>();
 
     private @Mock ActivityManager mActivityManager;
+    private @Mock ActivityManagerInternal mActivityManagerInternal;
     private @Mock AlarmManager mAlarmManager;
     private @Mock BiometricManager mBiometricManager;
     private @Mock DevicePolicyManager mDevicePolicyManager;
@@ -158,6 +160,7 @@
     private HandlerThread mHandlerThread;
     private TrustManagerService mService;
     private ITrustManager mTrustManager;
+    private ActivityManagerInternal mPreviousActivityManagerInternal;
 
     @Before
     public void setUp() throws Exception {
@@ -210,6 +213,11 @@
         mMockContext.setMockPackageManager(mPackageManager);
         mMockContext.addMockSystemService(UserManager.class, mUserManager);
         doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+        mPreviousActivityManagerInternal = LocalServices.getService(
+                ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class,
+                mActivityManagerInternal);
         LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
 
         grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
@@ -257,7 +265,14 @@
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(SystemServiceManager.class);
-        mHandlerThread.quit();
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        if (mPreviousActivityManagerInternal != null) {
+            LocalServices.addService(ActivityManagerInternal.class,
+                    mPreviousActivityManagerInternal);
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+        }
     }
 
     @Test
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index d4b6c91..feea55b 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -32,6 +32,8 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 /**
@@ -310,10 +312,18 @@
         // This avoid breaking legacy code that rely on public-facing APIs to access cell location,
         // and it doesn't create an info leak risk because the cell location is stored in the phone
         // process anyway, and the system server already has location access.
-        if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
-                || query.callingUid == Process.NETWORK_STACK_UID
-                || query.callingUid == Process.ROOT_UID) {
-            return LocationPermissionResult.ALLOWED;
+        if (Flags.supportPhoneUidCheckForMultiuser()) {
+            if (TelephonyPermissions.isSystemOrPhone(query.callingUid)
+                    || UserHandle.isSameApp(query.callingUid, Process.NETWORK_STACK_UID)
+                    || UserHandle.isSameApp(query.callingUid, Process.ROOT_UID)) {
+                return LocationPermissionResult.ALLOWED;
+            }
+        } else {
+            if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
+                    || query.callingUid == Process.NETWORK_STACK_UID
+                    || query.callingUid == Process.ROOT_UID) {
+                return LocationPermissionResult.ALLOWED;
+            }
         }
 
         // Check the system-wide requirements. If the location main switch is off and the caller is