Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ee9c1ea authored by Alex Johnston's avatar Alex Johnston
Browse files

Enterprise exemption for dismissible notificatons

Starting from Android U, notifications with the ongoing parameter
can be dismissed by a user on an unlocked device. An exempted app
can have non-dismissible notifications.

The following exemptions will be added:
* Admin apps
* Apps that are granted the SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS

Allow apps with the MANAGE_DEVICE_POLICY_APP_EXEMPTIONS permission
can grant the SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS exemption
to other apps.

Bug: 246330879
Bug: 266237746
Test: atest NotificationManagerServiceTest
Change-Id: Iff0b785e096c22dd466401c7af14964e04cc73da
parent da8c7655
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1231,6 +1231,7 @@ package android.app.admin {
    field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
    field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
    field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
    field public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS = 1; // 0x1
    field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
    field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
    field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
+12 −1
Original line number Diff line number Diff line
@@ -3876,6 +3876,16 @@ public class DevicePolicyManager {
    @SystemApi
    public static final int EXEMPT_FROM_APP_STANDBY =  0;
    /**
     * Prevent an app from dismissible notifications. Starting from Android U, notifications with
     * the ongoing parameter can be dismissed by a user on an unlocked device. An app with
     * this exemption can create non-dismissable notifications.
     *
     * @hide
     */
    @SystemApi
    public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS =  1;
    /**
     * Exemptions to platform restrictions, given to an application through
     * {@link #setApplicationExemptions(String, Set)}.
@@ -3883,7 +3893,8 @@ public class DevicePolicyManager {
     * @hide
     */
    @IntDef(prefix = { "EXEMPT_FROM_"}, value = {
            EXEMPT_FROM_APP_STANDBY
            EXEMPT_FROM_APP_STANDBY,
            EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ApplicationExemptionConstants {}
+5 −0
Original line number Diff line number Diff line
@@ -304,4 +304,9 @@ public abstract class DevicePolicyManagerInternal {
     * True if either the entire device or the user is organization managed.
     */
    public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);

    /**
     * Returns whether the application exemptions feature flag is enabled.
     */
    public abstract boolean isApplicationExemptionsFlagEnabled();
}
+16 −4
Original line number Diff line number Diff line
@@ -579,6 +579,8 @@ public class NotificationManagerService extends SystemService {
    private static final boolean ONGOING_DISMISSAL = SystemProperties.getBoolean(
            "persist.sysui.notification.ongoing_dismissal", true);
    @VisibleForTesting
    protected boolean mSystemExemptFromDismissal = false;
    // used as a mutex for access to all active notifications & listeners
    final Object mNotificationLock = new Object();
@@ -2595,6 +2597,8 @@ public class NotificationManagerService extends SystemService {
        mAllowFgsDismissal = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, true);
        mSystemExemptFromDismissal =
                mDpm.isApplicationExemptionsFlagEnabled();
        DeviceConfig.addOnPropertiesChangedListener(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                new HandlerExecutor(mHandler),
@@ -6801,12 +6805,20 @@ public class NotificationManagerService extends SystemService {
        // partition.
        boolean isSystemAppExempt = (ai.flags
                & (ApplicationInfo.FLAG_UPDATED_SYSTEM_APP | ApplicationInfo.FLAG_SYSTEM)) > 0;
        return isSystemAppExempt || notification.isMediaNotification() || isEnterpriseExempted();
        return isSystemAppExempt || notification.isMediaNotification() || isEnterpriseExempted(ai);
    }
    // TODO: b/266237746 Enterprise app exemptions
    private boolean isEnterpriseExempted() {
        return false;
    private boolean isEnterpriseExempted(ApplicationInfo ai) {
        // Check if the app is an organization admin app
        // TODO(b/234609037): Replace with new DPM APIs to check if organization admin
        if (mDpm != null && (mDpm.isActiveProfileOwner(ai.uid)
                || mDpm.isActiveDeviceOwner(ai.uid))) {
            return true;
        }
        // Check if an app has been given system exemption
        return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow(
                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
                ai.packageName) == AppOpsManager.MODE_ALLOWED;
    }
    private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
+17 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
@@ -55,6 +56,7 @@ import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -703,6 +705,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
    static {
        APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
                EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY);
        APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
                EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
                OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS);
    }
    /**
@@ -750,6 +755,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
    private static final String HEADLESS_FLAG = "headless";
    private static final boolean DEFAULT_HEADLESS_FLAG = true;
    // TODO(b/266831522) remove the flag after rollout.
    private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
    private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
    /**
     * This feature flag is checked once after boot and this value us used until the next reboot to
     * avoid needing to handle the flag changing on the fly.
@@ -14189,6 +14198,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
            return getDeviceStateCache().isUserOrganizationManaged(userHandle);
        }
        @Override
        public boolean isApplicationExemptionsFlagEnabled() {
            return DeviceConfig.getBoolean(
                    NAMESPACE_DEVICE_POLICY_MANAGER,
                    APPLICATION_EXEMPTIONS_FLAG,
                    DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
        }
    }
    private Intent createShowAdminSupportIntent(int userId) {
Loading