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

Commit cbaa1b5b authored by Pavel Grafov's avatar Pavel Grafov Committed by Automerger Merge Worker
Browse files

Merge "Warn the user about impending personal app suspension." into rvc-dev am: 89d4da77

Change-Id: Ie6c4b46fa51ab2d17e0dd3edb4e40445cd3503db
parents 2248929e 89d4da77
Loading
Loading
Loading
Loading
+113 −79
Original line number Diff line number Diff line
@@ -403,11 +403,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
    private static final long MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD = 3 * MS_PER_DAY;
    /** When to warn the user about the approaching work profile off deadline: 1 day before */
    private static final long MANAGED_PROFILE_OFF_WARNING_PERIOD = 1 * MS_PER_DAY;
    private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION =
            "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
    private static final String ACTION_PROFILE_OFF_DEADLINE =
    @VisibleForTesting
    static final String ACTION_PROFILE_OFF_DEADLINE =
            "com.android.server.ACTION_PROFILE_OFF_DEADLINE";
    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
@@ -649,6 +652,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    private static final boolean ENABLE_LOCK_GUARD = true;
    /** Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away. */
    private static final int PROFILE_OFF_DEADLINE_DEFAULT = 0;
    /** Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD. */
    private static final int PROFILE_OFF_DEADLINE_WARNING = 1;
    /** Profile off deadline reached, notify the user that personal apps blocked. */
    private static final int PROFILE_OFF_DEADLINE_REACHED = 2;
    interface Stats {
        int LOCK_GUARD_GUARD = 0;
@@ -926,11 +936,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    mUserData.remove(userHandle);
                }
                handlePackagesChanged(null /* check all admins */, userHandle);
                updatePersonalAppsSuspensionOnUserStart(userHandle);
            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
                sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STOPPED, userHandle);
                if (isManagedProfile(userHandle)) {
                    Slog.d(LOG_TAG, "Managed profile was stopped");
                    updatePersonalAppSuspension(userHandle, false /* profileIsOn */);
                    updatePersonalAppsSuspension(userHandle, false /* unlocked */);
                }
            } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_SWITCHED, userHandle);
@@ -940,7 +951,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                }
                if (isManagedProfile(userHandle)) {
                    Slog.d(LOG_TAG, "Managed profile became unlocked");
                    updatePersonalAppSuspension(userHandle, true /* profileIsOn */);
                    updatePersonalAppsSuspension(userHandle, true /* unlocked */);
                }
            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
                handlePackagesChanged(null /* check all admins */, userHandle);
@@ -967,7 +978,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                Slog.i(LOG_TAG, "Profile off deadline alarm was triggered");
                final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
                if (userId >= 0) {
                    updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked(userId));
                    updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
                } else {
                    Slog.wtf(LOG_TAG, "Got deadline alarm for nonexistent profile");
                }
@@ -2486,6 +2497,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        public void runCryptoSelfTest() {
            CryptoTestHelper.runAndLogSelfTest();
        }
        public String[] getPersonalAppsForSuspension(int userId) {
            return new PersonalAppsSuspensionHelper(
                    mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
                    .getPersonalAppsForSuspension();
        }
        public long systemCurrentTimeMillis() {
            return System.currentTimeMillis();
        }
    }
    /**
@@ -4045,10 +4066,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    applyManagedProfileRestrictionIfDeviceOwnerLocked();
                }
                maybeStartSecurityLogMonitorOnActivityManagerReady();
                final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
                if (userId >= 0) {
                    updatePersonalAppSuspension(userId, false /* running */);
                }
                break;
            case SystemService.PHASE_BOOT_COMPLETED:
                ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
@@ -4056,6 +4073,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }
    private void updatePersonalAppsSuspensionOnUserStart(int userHandle) {
        final int profileUserHandle = getManagedUserId(userHandle);
        if (profileUserHandle >= 0) {
            // Given that the parent user has just started, profile should be locked.
            updatePersonalAppsSuspension(profileUserHandle, false /* unlocked */);
        } else {
            suspendPersonalAppsInternal(userHandle, false);
        }
    }
    private void onLockSettingsReady() {
        getUserData(UserHandle.USER_SYSTEM);
        loadOwners();
@@ -15893,11 +15920,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            }
        }
        final int suspendedState = suspended
                ? PERSONAL_APPS_SUSPENDED_EXPLICITLY
                : PERSONAL_APPS_NOT_SUSPENDED;
        mInjector.binderWithCleanCallingIdentity(
                () -> applyPersonalAppsSuspension(callingUserId, suspendedState));
        mInjector.binderWithCleanCallingIdentity(() -> updatePersonalAppsSuspension(
                callingUserId, mUserManager.isUserUnlocked(callingUserId)));
        DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
@@ -15907,44 +15931,54 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    }
    /**
     * Checks whether there is a policy that requires personal apps to be suspended and if so,
     * applies it.
     * @param running whether the profile is currently considered running.
     * Checks whether personal apps should be suspended according to the policy and applies the
     * change if needed.
     *
     * @param unlocked whether the profile is currently running unlocked.
     */
    private void updatePersonalAppSuspension(int profileUserId, boolean running) {
        final int suspensionState;
    private void updatePersonalAppsSuspension(int profileUserId, boolean unlocked) {
        final boolean suspended;
        synchronized (getLockObject()) {
            final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
            if (profileOwner != null) {
                final boolean deadlineReached =
                        updateProfileOffDeadlineLocked(profileUserId, profileOwner, running);
                suspensionState = makeSuspensionReasons(
                        profileOwner.mSuspendPersonalApps, deadlineReached);
                Slog.d(LOG_TAG,
                        String.format("New personal apps suspension state: %d", suspensionState));
                final int deadlineState =
                        updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
                suspended = profileOwner.mSuspendPersonalApps
                        || deadlineState == PROFILE_OFF_DEADLINE_REACHED;
                Slog.d(LOG_TAG, String.format("Personal apps suspended: %b, deadline state: %d",
                            suspended, deadlineState));
                updateProfileOffDeadlineNotificationLocked(profileUserId, profileOwner,
                        unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState);
            } else {
                suspensionState = PERSONAL_APPS_NOT_SUSPENDED;
                suspended = false;
            }
        }
        applyPersonalAppsSuspension(profileUserId, suspensionState);
        final int parentUserId = getProfileParentId(profileUserId);
        suspendPersonalAppsInternal(parentUserId, suspended);
    }
    /**
     * Checks work profile time off policy, scheduling personal apps suspension via alarm if
     * necessary.
     * @return whether the apps should be suspended based on maximum time off policy.
     * @return profile deadline state
     */
    private boolean updateProfileOffDeadlineLocked(
    private int updateProfileOffDeadlineLocked(
            int profileUserId, ActiveAdmin profileOwner, boolean unlocked) {
        final long now = System.currentTimeMillis();
        final long now = mInjector.systemCurrentTimeMillis();
        if (profileOwner.mProfileOffDeadline != 0 && now > profileOwner.mProfileOffDeadline) {
            // Profile off deadline is already reached.
            Slog.i(LOG_TAG, "Profile off deadline has been reached.");
            return true;
            return PROFILE_OFF_DEADLINE_REACHED;
        }
        boolean shouldSaveSettings = false;
        if (profileOwner.mProfileOffDeadline != 0
        if (profileOwner.mSuspendPersonalApps) {
            // When explicit suspension is active, deadline shouldn't be set.
            if (profileOwner.mProfileOffDeadline != 0) {
                profileOwner.mProfileOffDeadline = 0;
                shouldSaveSettings = true;
            }
        } else if (profileOwner.mProfileOffDeadline != 0
                && (profileOwner.mProfileMaximumTimeOffMillis == 0 || unlocked)) {
            // There is a deadline but either there is no policy or the profile is unlocked -> clear
            // the deadline.
@@ -15960,52 +15994,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            shouldSaveSettings = true;
        }
        updateProfileOffAlarm(profileOwner.mProfileOffDeadline);
        if (shouldSaveSettings) {
            saveSettingsLocked(profileUserId);
        }
        return false;
        final long alarmTime;
        final int deadlineState;
        if (profileOwner.mProfileOffDeadline == 0) {
            alarmTime = 0;
            deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
        } else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) {
            // The deadline is close, upon the alarm personal apps should be suspended.
            alarmTime = profileOwner.mProfileOffDeadline;
            deadlineState = PROFILE_OFF_DEADLINE_WARNING;
        } else {
            // The deadline is quite far, upon the alarm we should warn the user first, so the
            // alarm is scheduled earlier than the actual deadline.
            alarmTime = profileOwner.mProfileOffDeadline - MANAGED_PROFILE_OFF_WARNING_PERIOD;
            deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
        }
    private void updateProfileOffAlarm(long profileOffDeadline) {
        final AlarmManager am = mInjector.getAlarmManager();
        final PendingIntent pi = mInjector.pendingIntentGetBroadcast(
                mContext, REQUEST_PROFILE_OFF_DEADLINE, new Intent(ACTION_PROFILE_OFF_DEADLINE),
                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
        if (alarmTime == 0) {
            Slog.i(LOG_TAG, "Profile off deadline alarm is removed.");
            am.cancel(pi);
        if (profileOffDeadline != 0) {
            Slog.i(LOG_TAG, "Profile off deadline alarm is set.");
            am.set(AlarmManager.RTC, profileOffDeadline, pi);
        } else {
            Slog.i(LOG_TAG, "Profile off deadline alarm is removed.");
        }
            Slog.i(LOG_TAG, "Profile off deadline alarm is set.");
            am.set(AlarmManager.RTC, alarmTime, pi);
        }
    private void applyPersonalAppsSuspension(
            int profileUserId, @PersonalAppsSuspensionReason int suspensionState) {
        final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mAppsSuspended;
        final boolean shouldSuspend = suspensionState != PERSONAL_APPS_NOT_SUSPENDED;
        if (suspended != shouldSuspend) {
            suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM);
        return deadlineState;
    }
        if (suspensionState == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT) {
            sendPersonalAppsSuspendedNotification(profileUserId);
        } else {
            clearPersonalAppsSuspendedNotification();
        }
    private void suspendPersonalAppsInternal(int userId, boolean suspended) {
        if (getUserData(userId).mAppsSuspended == suspended) {
            return;
        }
    private void suspendPersonalAppsInternal(boolean suspended, int userId) {
        Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
                suspended ? "Suspending" : "Unsuspending", userId));
        mInjector.binderWithCleanCallingIdentity(() -> {
            try {
                final String[] appsToSuspend =
                        new PersonalAppsSuspensionHelper(
                                mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
                                .getPersonalAppsForSuspension();
                final String[] appsToSuspend = mInjector.getPersonalAppsForSuspension(userId);
                final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser(
                        appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId);
                if (!ArrayUtils.isEmpty(failedPackages)) {
@@ -16024,35 +16057,36 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }
    private void clearPersonalAppsSuspendedNotification() {
        mInjector.binderWithCleanCallingIdentity(() ->
                mInjector.getNotificationManager().cancel(
                        SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
    }
    private void updateProfileOffDeadlineNotificationLocked(int profileUserId,
            @Nullable ActiveAdmin profileOwner, int notificationState) {
    private void sendPersonalAppsSuspendedNotification(int userId) {
        final String profileOwnerPackageName;
        final long maxTimeOffDays;
        synchronized (getLockObject()) {
            profileOwnerPackageName = mOwners.getProfileOwnerComponent(userId).getPackageName();
            final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
            maxTimeOffDays = TimeUnit.MILLISECONDS.toDays(poAdmin.mProfileMaximumTimeOffMillis);
        if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) {
            mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED);
            return;
        }
        final String profileOwnerPackageName = profileOwner.info.getPackageName();
        final long maxTimeOffDays =
                TimeUnit.MILLISECONDS.toDays(profileOwner.mProfileMaximumTimeOffMillis);
        final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
        intent.setPackage(profileOwnerPackageName);
        final PendingIntent pendingIntent = mInjector.pendingIntentGetActivityAsUser(mContext,
                0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT, null /* options */,
                UserHandle.of(userId));
                0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT,
                null /* options */, UserHandle.of(profileUserId));
        // TODO(b/149075510): Only the first of the notifications should be dismissible.
        final String title = mContext.getString(
                notificationState == PROFILE_OFF_DEADLINE_WARNING
                ? R.string.personal_apps_suspended_tomorrow_title
                : R.string.personal_apps_suspended_title);
        final Notification notification =
                new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                        .setSmallIcon(android.R.drawable.stat_sys_warning)
                        .setOngoing(true)
                        .setContentTitle(
                                mContext.getString(
                                        R.string.personal_apps_suspended_title))
                        .setContentTitle(title)
                        .setContentText(mContext.getString(
                            R.string.personal_apps_suspended_text, maxTimeOffDays))
                        .setColor(mContext.getColor(R.color.system_notification_accent_color))
@@ -16086,7 +16120,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
        mInjector.binderWithCleanCallingIdentity(
                () -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
                () -> updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked()));
        DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
+10 −5
Original line number Diff line number Diff line
@@ -51,6 +51,10 @@ import java.util.Set;
public class PersonalAppsSuspensionHelper {
    private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;

    // Flags to get all packages even if the user is still locked.
    private static final int PACKAGE_QUERY_FLAGS =
            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;

    private final Context mContext;
    private final PackageManager mPackageManager;

@@ -67,7 +71,7 @@ public class PersonalAppsSuspensionHelper {
     */
    String[] getPersonalAppsForSuspension() {
        final List<PackageInfo> installedPackageInfos =
                mPackageManager.getInstalledPackages(0 /* flags */);
                mPackageManager.getInstalledPackages(PACKAGE_QUERY_FLAGS);
        final Set<String> result = new ArraySet<>();
        for (final PackageInfo packageInfo : installedPackageInfos) {
            final ApplicationInfo info = packageInfo.applicationInfo;
@@ -97,7 +101,7 @@ public class PersonalAppsSuspensionHelper {
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        final List<ResolveInfo> matchingActivities =
                mPackageManager.queryIntentActivities(intent, 0);
                mPackageManager.queryIntentActivities(intent, PACKAGE_QUERY_FLAGS);
        for (final ResolveInfo resolveInfo : matchingActivities) {
            if (resolveInfo.activityInfo == null
                    || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
@@ -107,7 +111,7 @@ public class PersonalAppsSuspensionHelper {
            final String packageName = resolveInfo.activityInfo.packageName;
            try {
                final ApplicationInfo applicationInfo =
                        mPackageManager.getApplicationInfo(packageName, 0);
                        mPackageManager.getApplicationInfo(packageName, PACKAGE_QUERY_FLAGS);
                if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) {
                    result.add(packageName);
                }
@@ -147,7 +151,8 @@ public class PersonalAppsSuspensionHelper {
    private String getSettingsPackageName() {
        final Intent intent = new Intent(Settings.ACTION_SETTINGS);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, /* flags= */ 0);
        final ResolveInfo resolveInfo =
                mPackageManager.resolveActivity(intent, PACKAGE_QUERY_FLAGS);
        if (resolveInfo != null) {
            return resolveInfo.activityInfo.packageName;
        }
@@ -164,7 +169,7 @@ public class PersonalAppsSuspensionHelper {
        intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
        intentToResolve.setPackage(packageName);
        final List<ResolveInfo> resolveInfos =
                mPackageManager.queryIntentActivities(intentToResolve, /* flags= */ 0);
                mPackageManager.queryIntentActivities(intentToResolve, PACKAGE_QUERY_FLAGS);
        return resolveInfos != null && !resolveInfos.isEmpty();
    }

+17 −0
Original line number Diff line number Diff line
@@ -124,6 +124,9 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
        // Key is a pair of uri and userId
        private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>();

        // Used as an override when set to nonzero.
        private long mCurrentTimeMillis = 0;

        public MockInjector(MockSystemServices services, DpmMockContext context) {
            super(context);
            this.services = services;
@@ -470,5 +473,19 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi

        @Override
        public void runCryptoSelfTest() {}

        @Override
        public String[] getPersonalAppsForSuspension(int userId) {
            return new String[]{};
        }

        public void setSystemCurrentTimeMillis(long value) {
            mCurrentTimeMillis = value;
        }

        @Override
        public long systemCurrentTimeMillis() {
            return mCurrentTimeMillis != 0 ? mCurrentTimeMillis : System.currentTimeMillis();
        }
    }
}
+228 −16

File changed.

Preview size limit exceeded, changes collapsed.