Merge "Remove RESET_PASSWORD_TOKEN policy when the generated escrow token is not valid." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 2c6e1cb..8eb8811 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7015,6 +7015,7 @@
     method public boolean areNotificationsEnabled();
     method public boolean areNotificationsPaused();
     method public boolean canNotifyAsPackage(@NonNull String);
+    method @FlaggedApi("android.app.api_rich_ongoing") public boolean canPostPromotedNotifications();
     method public boolean canUseFullScreenIntent();
     method public void cancel(int);
     method public void cancel(@Nullable String, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 72a68f8..659222d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -398,6 +398,7 @@
     method public android.content.ComponentName getEffectsSuppressor();
     method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
     method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
+    method @FlaggedApi("android.app.api_rich_ongoing") public void setCanPostPromotedNotifications(@NonNull String, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
     method @FlaggedApi("android.app.modes_api") public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b9fe356..237780f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -259,5 +259,6 @@
     void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
 
     void setCanBePromoted(String pkg, int uid, boolean promote);
-    boolean canBePromoted(String pkg, int uid);
+    boolean appCanBePromoted(String pkg, int uid);
+    boolean canBePromoted(String pkg);
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 83f9ff7..06f21b8 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -953,6 +953,36 @@
     }
 
     /**
+     * Returns whether the calling app's properly formatted notifications can appear in a promoted
+     * format, which may result in higher ranking, appearances on additional surfaces, and richer
+     * presentation.
+     */
+    @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+    public boolean canPostPromotedNotifications() {
+        INotificationManager service = getService();
+        try {
+            return service.canBePromoted(mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Setter for {@link #canPostPromotedNotifications()}. Only callable by the OS.
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+    public void setCanPostPromotedNotifications(@NonNull String pkg, int uid, boolean allowed) {
+        INotificationManager service = getService();
+        try {
+            service.setCanBePromoted(pkg, uid, allowed);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Creates a group container for {@link NotificationChannel} objects.
      *
      * This can be used to rename an existing group.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index c807960..ba0d938 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.qs.composefragment
 
 import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.PointF
 import android.graphics.Rect
 import android.os.Bundle
 import android.util.IndentingPrintWriter
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
@@ -185,62 +188,76 @@
         savedInstanceState: Bundle?,
     ): View {
         val context = inflater.context
-        return ComposeView(context).apply {
-            setBackPressedDispatcher()
-            setContent {
-                PlatformTheme {
-                    val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
+        val composeView =
+            ComposeView(context).apply {
+                setBackPressedDispatcher()
+                setContent {
+                    PlatformTheme {
+                        val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
 
-                    AnimatedVisibility(
-                        visible = visible,
-                        modifier =
-                            Modifier.windowInsetsPadding(WindowInsets.navigationBars)
-                                .thenIf(notificationScrimClippingParams.isEnabled) {
-                                    Modifier.notificationScrimClip(
-                                        notificationScrimClippingParams.leftInset,
-                                        notificationScrimClippingParams.top,
-                                        notificationScrimClippingParams.rightInset,
-                                        notificationScrimClippingParams.bottom,
-                                        notificationScrimClippingParams.radius,
+                        AnimatedVisibility(
+                            visible = visible,
+                            modifier =
+                                Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+                                    .thenIf(notificationScrimClippingParams.isEnabled) {
+                                        Modifier.notificationScrimClip(
+                                            notificationScrimClippingParams.leftInset,
+                                            notificationScrimClippingParams.top,
+                                            notificationScrimClippingParams.rightInset,
+                                            notificationScrimClippingParams.bottom,
+                                            notificationScrimClippingParams.radius,
+                                        )
+                                    }
+                                    .graphicsLayer { elevation = 4.dp.toPx() },
+                        ) {
+                            val isEditing by
+                                viewModel.containerViewModel.editModeViewModel.isEditing
+                                    .collectAsStateWithLifecycle()
+                            val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
+                            AnimatedContent(
+                                targetState = isEditing,
+                                transitionSpec = {
+                                    fadeIn(animationSpecEditMode) togetherWith
+                                        fadeOut(animationSpecEditMode)
+                                },
+                                label = "EditModeAnimatedContent",
+                            ) { editing ->
+                                if (editing) {
+                                    val qqsPadding by
+                                        viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+                                    EditMode(
+                                        viewModel = viewModel.containerViewModel.editModeViewModel,
+                                        modifier =
+                                            Modifier.fillMaxWidth()
+                                                .padding(top = { qqsPadding })
+                                                .padding(
+                                                    horizontal = {
+                                                        QuickSettingsShade.Dimensions.Padding
+                                                            .roundToPx()
+                                                    }
+                                                ),
                                     )
+                                } else {
+                                    CollapsableQuickSettingsSTL()
                                 }
-                                .graphicsLayer { elevation = 4.dp.toPx() },
-                    ) {
-                        val isEditing by
-                            viewModel.containerViewModel.editModeViewModel.isEditing
-                                .collectAsStateWithLifecycle()
-                        val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
-                        AnimatedContent(
-                            targetState = isEditing,
-                            transitionSpec = {
-                                fadeIn(animationSpecEditMode) togetherWith
-                                    fadeOut(animationSpecEditMode)
-                            },
-                            label = "EditModeAnimatedContent",
-                        ) { editing ->
-                            if (editing) {
-                                val qqsPadding by
-                                    viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
-                                EditMode(
-                                    viewModel = viewModel.containerViewModel.editModeViewModel,
-                                    modifier =
-                                        Modifier.fillMaxWidth()
-                                            .padding(top = { qqsPadding })
-                                            .padding(
-                                                horizontal = {
-                                                    QuickSettingsShade.Dimensions.Padding
-                                                        .roundToPx()
-                                                }
-                                            ),
-                                )
-                            } else {
-                                CollapsableQuickSettingsSTL()
                             }
                         }
                     }
                 }
             }
-        }
+
+        val frame =
+            FrameLayoutTouchPassthrough(
+                context,
+                { notificationScrimClippingParams.isEnabled },
+                { notificationScrimClippingParams.top },
+            )
+        frame.addView(
+            composeView,
+            FrameLayout.LayoutParams.MATCH_PARENT,
+            FrameLayout.LayoutParams.MATCH_PARENT,
+        )
+        return frame
     }
 
     /**
@@ -762,3 +779,26 @@
 }
 
 private const val EDIT_MODE_TIME_MILLIS = 500
+
+/**
+ * Ignore touches below the value returned by [clippingTopProvider], when clipping is enabled, as
+ * per [clippingEnabledProvider].
+ */
+private class FrameLayoutTouchPassthrough(
+    context: Context,
+    private val clippingEnabledProvider: () -> Boolean,
+    private val clippingTopProvider: () -> Int,
+) : FrameLayout(context) {
+    override fun isTransformedTouchPointInView(
+        x: Float,
+        y: Float,
+        child: View?,
+        outLocalPoint: PointF?,
+    ): Boolean {
+        return if (clippingEnabledProvider() && y + translationY > clippingTopProvider()) {
+            false
+        } else {
+            super.isTransformedTouchPointInView(x, y, child, outLocalPoint)
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dc62b27..bcc2019 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4116,20 +4116,31 @@
         }
 
         @Override
-        @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
-        public boolean canBePromoted(String pkg, int uid) {
+        @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+        public boolean appCanBePromoted(String pkg, int uid) {
             checkCallerIsSystemOrSystemUiOrShell();
-            if (!android.app.Flags.uiRichOngoing()) {
+            if (!android.app.Flags.apiRichOngoing()) {
                 return false;
             }
             return mPreferencesHelper.canBePromoted(pkg, uid);
         }
 
         @Override
-        @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+        @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+        public boolean canBePromoted(String callingPkg) {
+            checkCallerIsSameApp(callingPkg);
+            if (!android.app.Flags.apiRichOngoing()) {
+                return false;
+            }
+            return mPreferencesHelper.canBePromoted(callingPkg, Binder.getCallingUid());
+        }
+
+
+        @Override
+        @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
         public void setCanBePromoted(String pkg, int uid, boolean promote) {
             checkCallerIsSystemOrSystemUiOrShell();
-            if (!android.app.Flags.uiRichOngoing()) {
+            if (!android.app.Flags.apiRichOngoing()) {
                 return;
             }
             boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote);
@@ -7776,7 +7787,7 @@
             return false;
         }
 
-        if (android.app.Flags.uiRichOngoing()) {
+        if (android.app.Flags.apiRichOngoing()) {
             // This would normally be done in fixNotification(), but we need the channel info so
             // it's done a little late
             if (mPreferencesHelper.canBePromoted(pkg, notificationUid)
@@ -10740,7 +10751,7 @@
     }
 
     @GuardedBy("mNotificationLock")
-    @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
     private @NonNull List<NotificationRecord> findAppNotificationByListLocked(
             ArrayList<NotificationRecord> list, String pkg, int userId) {
         List<NotificationRecord> records = new ArrayList<>();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index fcc8d2f..fdb9f67 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -850,14 +850,14 @@
         }
     }
 
-    @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
     public boolean canBePromoted(String packageName, int uid) {
         synchronized (mLock) {
             return getOrCreatePackagePreferencesLocked(packageName, uid).canHavePromotedNotifs;
         }
     }
 
-    @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
     public boolean setCanBePromoted(String packageName, int uid, boolean promote) {
         boolean changed = false;
         synchronized (mLock) {
@@ -3065,7 +3065,7 @@
         boolean migrateToPm = false;
         long creationTime;
 
-        @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+        @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
         boolean canHavePromotedNotifs = false;
 
         @UserIdInt int userId;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ff9c3e5..611e0d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3389,18 +3389,31 @@
                     return true;
                 }
                 // Does it contain a device admin for any user?
-                int[] users;
+                int[] allUsers = mUserManager.getUserIds();
+                int[] targetUsers;
                 if (userId == UserHandle.USER_ALL) {
-                    users = mUserManager.getUserIds();
+                    targetUsers = allUsers;
                 } else {
-                    users = new int[]{userId};
+                    targetUsers = new int[]{userId};
                 }
-                for (int i = 0; i < users.length; ++i) {
-                    if (dpm.packageHasActiveAdmins(packageName, users[i])) {
+
+                for (int i = 0; i < targetUsers.length; ++i) {
+                    if (dpm.packageHasActiveAdmins(packageName, targetUsers[i])) {
                         return true;
                     }
-                    if (isDeviceManagementRoleHolder(packageName, users[i])
-                            && dpmi.isUserOrganizationManaged(users[i])) {
+                }
+
+                // If a package is DMRH on a managed user, it should also be treated as an admin on
+                // that user. If that package is also a system package, it should also be protected
+                // on other users otherwise "uninstall updates" on an unmanaged user may break
+                // management on other users because apk version is shared between all users.
+                var packageState = snapshotComputer().getPackageStateInternal(packageName);
+                if (packageState == null) {
+                    return false;
+                }
+                for (int user : packageState.isSystem() ? allUsers : targetUsers) {
+                    if (isDeviceManagementRoleHolder(packageName, user)
+                            && dpmi.isUserOrganizationManaged(user)) {
                         return true;
                     }
                 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 130690d..d69d678 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16556,7 +16556,7 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testSetCanBePromoted_granted() throws Exception {
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
@@ -16632,7 +16632,7 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
@@ -16663,7 +16663,7 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testSetCanBePromoted_revoked() throws Exception {
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
@@ -16728,7 +16728,7 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception {
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
@@ -16763,10 +16763,10 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testPostPromotableNotification() throws Exception {
         mBinderService.setCanBePromoted(mPkg, mUid, true);
-        assertThat(mBinderService.canBePromoted(mPkg, mUid)).isTrue();
+        assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isTrue();
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
 
@@ -16776,7 +16776,6 @@
                 .setColor(Color.WHITE)
                 .setColorized(true)
                 .build();
-        //assertThat(n.hasPromotableCharacteristics()).isTrue();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
 
@@ -16794,7 +16793,7 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testPostPromotableNotification_noPermission() throws Exception {
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
@@ -16822,7 +16821,7 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     public void testPostPromotableNotification_unimportantNotification() throws Exception {
         mBinderService.setCanBePromoted(mPkg, mUid, true);
         mContext.getTestablePermissions().setPermission(