diff options
| author | 2020-01-21 16:56:23 +0000 | |
|---|---|---|
| committer | 2020-01-24 11:50:42 +0000 | |
| commit | 746e1341c389fc90efbda541ccd6b8b6a27de18f (patch) | |
| tree | 091d7bc3789d99b79724bb8e2cd29e58dee6e8df | |
| parent | 03e931d33024dbcfeb26e6c922bb7347f2097bbe (diff) | |
Add API to limit maximum time the profile can be turned off.
If this policy is set:
* Whenever work profile gets turned off or system boots, timer is
started, so theat unless the profile is unlocked in time, apps
will be suspended.
* when the user is unlocked, alarm is discharged.
Currently there is no upfront warning notification, will be addressed
in a follow-up.
Bug: 143517719
Test: manual, using TestDPC
Change-Id: Ib0f936b6d1414f65ae8c86367b7059c14862827d
5 files changed, 298 insertions, 77 deletions
diff --git a/api/current.txt b/api/current.txt index cbea597ac42f..5205114348cc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6827,6 +6827,7 @@ package android.app.admin { method public int getLockTaskFeatures(@NonNull android.content.ComponentName); method @NonNull public String[] getLockTaskPackages(@NonNull android.content.ComponentName); method @Nullable public CharSequence getLongSupportMessage(@NonNull android.content.ComponentName); + method public long getManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName); method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName); method public long getMaximumTimeToLock(@Nullable android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName); @@ -6956,6 +6957,7 @@ package android.app.admin { method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean); method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean); method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); + method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long); method public void setMasterVolumeMuted(@NonNull android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int); method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long); @@ -7142,6 +7144,7 @@ package android.app.admin { field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0 field public static final int PERSONAL_APPS_NOT_SUSPENDED = 0; // 0x0 field public static final int PERSONAL_APPS_SUSPENDED_EXPLICITLY = 1; // 0x1 + field public static final int PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT = 2; // 0x2 field public static final String POLICY_DISABLE_CAMERA = "policy_disable_camera"; field public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture"; field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1 diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index aeeabb7c1726..a5f40d40f59a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2403,11 +2403,19 @@ public class DevicePolicyManager { public static final int PERSONAL_APPS_SUSPENDED_EXPLICITLY = 1 << 0; /** + * Flag for {@link #getPersonalAppsSuspendedReasons} return value. Set when personal apps are + * suspended by framework because managed profile was off for longer than allowed by policy. + * @see #setManagedProfileMaximumTimeOff + */ + public static final int PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT = 1 << 1; + + /** * @hide */ @IntDef(flag = true, prefix = { "PERSONAL_APPS_" }, value = { PERSONAL_APPS_NOT_SUSPENDED, - PERSONAL_APPS_SUSPENDED_EXPLICITLY + PERSONAL_APPS_SUSPENDED_EXPLICITLY, + PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT }) @Retention(RetentionPolicy.SOURCE) public @interface PersonalAppSuspensionReason {} @@ -11742,6 +11750,8 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with * @param suspended Whether personal apps should be suspended. + * @throws IllegalStateException if the profile owner doesn't have an activity that handles + * {@link #ACTION_CHECK_POLICY_COMPLIANCE} */ public void setPersonalAppsSuspended(@NonNull ComponentName admin, boolean suspended) { throwIfParentInstance("setPersonalAppsSuspended"); @@ -11753,4 +11763,52 @@ public class DevicePolicyManager { } } } + + /** + * Called by a profile owner of an organization-owned managed profile to set maximum time + * the profile is allowed to be turned off. If the profile is turned off for longer, personal + * apps are suspended on the device. + * + * <p>When personal apps are suspended, an ongoing notification about that is shown to the user. + * When the user taps the notification, system invokes {@link #ACTION_CHECK_POLICY_COMPLIANCE} + * in the profile owner package. Profile owner implementation that uses personal apps suspension + * must handle this intent. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with + * @param timeoutMs Maximum time the profile is allowed to be off in milliseconds or 0 if + * not limited. + * @throws IllegalStateException if the profile owner doesn't have an activity that handles + * {@link #ACTION_CHECK_POLICY_COMPLIANCE} + * @see #setPersonalAppsSuspended + */ + public void setManagedProfileMaximumTimeOff(@NonNull ComponentName admin, long timeoutMs) { + throwIfParentInstance("setManagedProfileMaximumTimeOff"); + if (mService != null) { + try { + mService.setManagedProfileMaximumTimeOff(admin, timeoutMs); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } + + /** + * Called by a profile owner of an organization-owned managed profile to get maximum time + * the profile is allowed to be turned off. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with + * @return Maximum time the profile is allowed to be off in milliseconds or 0 if not limited. + * @see #setPersonalAppsSuspended + */ + public long getManagedProfileMaximumTimeOff(@NonNull ComponentName admin) { + throwIfParentInstance("getManagedProfileMaximumTimeOff"); + if (mService != null) { + try { + return mService.getManagedProfileMaximumTimeOff(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return 0; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b9ffc4e214a6..a8c3aa35ad32 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -473,4 +473,7 @@ interface IDevicePolicyManager { int getPersonalAppsSuspendedReasons(in ComponentName admin); void setPersonalAppsSuspended(in ComponentName admin, boolean suspended); + + long getManagedProfileMaximumTimeOff(in ComponentName admin); + void setManagedProfileMaximumTimeOff(in ComponentName admin, long timeoutMs); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 43ee97dfa17b..9b85a7b55c94 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -75,4 +75,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void setPersonalAppsSuspended(ComponentName admin, boolean suspended) { } + + public void setManagedProfileMaximumTimeOff(ComponentName admin, long timeoutMs) { + } + + public long getManagedProfileMaximumTimeOff(ComponentName admin) { + return 0; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0171582e9d86..63efcb88a6cc 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -67,6 +67,9 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; +import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; +import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; @@ -315,7 +318,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; -import java.util.stream.Collectors; /** * Implementation of the device policy APIs. @@ -378,16 +380,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen"; - private static final String TAG_PERSONAL_APPS_SUSPENDED = "personal-apps-suspended"; + private static final String TAG_APPS_SUSPENDED = "apps-suspended"; private static final int REQUEST_EXPIRE_PASSWORD = 5571; + private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572; + private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms - private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION - = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION"; + private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION = + "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION"; + + private static final String ACTION_PROFILE_OFF_DEADLINE = + "com.android.server.ACTION_PROFILE_OFF_DEADLINE"; private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; @@ -799,9 +806,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long mPasswordTokenHandle = 0; - // Flag reflecting the current state of the personal apps suspension. This flag should - // only be written AFTER all the needed apps were suspended or unsuspended. - boolean mPersonalAppsSuspended = false; + // Whether user's apps are suspended. This flag should only be written AFTER all the needed + // apps were suspended or unsuspended. + boolean mAppsSuspended = false; public DevicePolicyData(int userHandle) { mUserHandle = userHandle; @@ -848,7 +855,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { RemoteBugreportUtils.NOTIFICATION_ID, RemoteBugreportUtils.buildNotification(mContext, DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED), - UserHandle.ALL); + UserHandle.ALL); } if (Intent.ACTION_BOOT_COMPLETED.equals(action) || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) { @@ -895,12 +902,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { handlePackagesChanged(null /* check all admins */, 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 */); + } } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_SWITCHED, userHandle); } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { synchronized (getLockObject()) { maybeSendAdminEnabledBroadcastLocked(userHandle); } + if (isManagedProfile(userHandle)) { + Slog.d(LOG_TAG, "Managed profile became unlocked"); + updatePersonalAppSuspension(userHandle, true /* profileIsOn */); + } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { handlePackagesChanged(null /* check all admins */, userHandle); } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action) @@ -918,8 +933,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // (ACTION_DATE_CHANGED), or when manual clock adjustment is made // (ACTION_TIME_CHANGED) updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true); + } else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) { + 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)); + } else { + Slog.wtf(LOG_TAG, "Got deadline alarm for nonexistent profile"); + } } - } private void sendDeviceOwnerUserCommand(String action, int userHandle) { @@ -1033,6 +1055,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_FACTORY_RESET_PROTECTION_POLICY = "factory_reset_protection_policy"; private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps"; + private static final String TAG_PROFILE_MAXIMUM_TIME_OFF = "profile-max-time-off"; + private static final String TAG_PROFILE_OFF_DEADLINE = "profile-off-deadline"; DeviceAdminInfo info; @@ -1157,6 +1181,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Whether the admin explicitly requires personal apps to be suspended boolean mSuspendPersonalApps = false; + // Maximum time the profile owned by this admin can be off. + long mProfileMaximumTimeOff = 0; + // Time by which the profile should be turned on according to System.currentTimeMillis(). + long mProfileOffDeadline = 0; + ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; @@ -1391,6 +1420,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mSuspendPersonalApps) { writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps); } + if (mProfileMaximumTimeOff != 0) { + writeAttributeValueToXml(out, TAG_PROFILE_MAXIMUM_TIME_OFF, mProfileMaximumTimeOff); + } + if (mProfileMaximumTimeOff != 0) { + writeAttributeValueToXml(out, TAG_PROFILE_OFF_DEADLINE, mProfileOffDeadline); + } } void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException { @@ -1630,6 +1665,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) { mSuspendPersonalApps = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_PROFILE_MAXIMUM_TIME_OFF.equals(tag)) { + mProfileMaximumTimeOff = + Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_PROFILE_OFF_DEADLINE.equals(tag)) { + mProfileOffDeadline = + Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -1855,7 +1896,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.println(mCrossProfileCalendarPackages); } pw.print("mCrossProfilePackages="); - pw.println(mCrossProfilePackages); + pw.println(mCrossProfilePackages); + pw.print("mSuspendPersonalApps="); + pw.println(mSuspendPersonalApps); + pw.print("mProfileMaximumTimeOff="); + pw.println(mProfileMaximumTimeOff); + pw.print("mProfileOffDeadline="); + pw.println(mProfileOffDeadline); } } @@ -2373,12 +2420,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BOOT_COMPLETED); filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION); + filter.addAction(ACTION_PROFILE_OFF_DEADLINE); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); filter.addAction(Intent.ACTION_USER_STOPPED); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_UNLOCKED); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); filter = new IntentFilter(); @@ -3436,11 +3485,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.endTag(null, TAG_PROTECTED_PACKAGES); } - if (policy.mPersonalAppsSuspended) { - out.startTag(null, TAG_PERSONAL_APPS_SUSPENDED); - out.attribute(null, ATTR_VALUE, - Boolean.toString(policy.mPersonalAppsSuspended)); - out.endTag(null, TAG_PERSONAL_APPS_SUSPENDED); + if (policy.mAppsSuspended) { + out.startTag(null, TAG_APPS_SUSPENDED); + out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mAppsSuspended)); + out.endTag(null, TAG_APPS_SUSPENDED); } out.endTag(null, "policies"); @@ -3659,8 +3707,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS)); } else if (TAG_PROTECTED_PACKAGES.equals(tag)) { policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME)); - } else if (TAG_PERSONAL_APPS_SUSPENDED.equals(tag)) { - policy.mPersonalAppsSuspended = + } else if (TAG_APPS_SUSPENDED.equals(tag)) { + policy.mAppsSuspended = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); @@ -3802,7 +3850,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { maybeMigrateToProfileOnOrganizationOwnedDeviceLocked(); } - checkPackageSuspensionOnBoot(); + 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. @@ -3810,34 +3861,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void checkPackageSuspensionOnBoot() { - int profileUserId = UserHandle.USER_NULL; - final boolean shouldSuspend; - synchronized (getLockObject()) { - for (final int userId : mOwners.getProfileOwnerKeys()) { - if (mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId)) { - profileUserId = userId; - break; - } - } - - if (profileUserId == UserHandle.USER_NULL) { - shouldSuspend = false; - } else { - shouldSuspend = getProfileOwnerAdminLocked(profileUserId).mSuspendPersonalApps; - } - } - - final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended; - if (suspended != shouldSuspend) { - suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM); - } - - if (shouldSuspend) { - sendPersonalAppsSuspendedNotification(profileUserId); - } - } - private void onLockSettingsReady() { getUserData(UserHandle.USER_SYSTEM); loadOwners(); @@ -3950,13 +3973,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override void handleUnlockUser(int userId) { startOwnerService(userId, "unlock-user"); - maybeUpdatePersonalAppsSuspendedNotification(userId); } @Override void handleStopUser(int userId) { stopOwnerService(userId, "stop-user"); - maybeUpdatePersonalAppsSuspendedNotification(userId); } private void startOwnerService(int userId, String actionForLog) { @@ -9427,10 +9448,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.println(); pw.increaseIndent(); pw.print("mPasswordOwner="); pw.println(policy.mPasswordOwner); - pw.decreaseIndent(); - pw.println(); - pw.increaseIndent(); pw.print("mProtectedPackages="); pw.println(policy.mProtectedPackages); + pw.print("mAppsSuspended="); pw.println(policy.mAppsSuspended); pw.decreaseIndent(); } } @@ -15250,10 +15269,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { false /* parent */); // DO shouldn't be able to use this method. enforceProfileOwnerOfOrganizationOwnedDevice(admin); - if (admin.mSuspendPersonalApps) { - return DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; + final DevicePolicyData userData = + getUserData(getProfileParentId(mInjector.userHandleGetCallingUserId())); + if (!userData.mAppsSuspended) { + return PERSONAL_APPS_NOT_SUSPENDED; } else { - return DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; + int reasons = PERSONAL_APPS_NOT_SUSPENDED; + if (admin.mSuspendPersonalApps) { + reasons |= PERSONAL_APPS_SUSPENDED_EXPLICITLY; + } + final long deadline = admin.mProfileOffDeadline; + if (deadline != 0 && System.currentTimeMillis() > deadline) { + reasons |= PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT; + } + return reasons; } } } @@ -15267,26 +15296,112 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { false /* parent */); // DO shouldn't be able to use this method. enforceProfileOwnerOfOrganizationOwnedDevice(admin); + enforceHandlesCheckPolicyComplianceIntent(callingUserId, admin.info.getPackageName()); + boolean shouldSaveSettings = false; if (admin.mSuspendPersonalApps != suspended) { admin.mSuspendPersonalApps = suspended; + shouldSaveSettings = true; + } + if (admin.mProfileOffDeadline != 0) { + admin.mProfileOffDeadline = 0; + shouldSaveSettings = true; + } + if (shouldSaveSettings) { saveSettingsLocked(callingUserId); } } - if (getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended == suspended) { - // Admin request matches current state, nothing to do. - return; - } - - suspendPersonalAppsInternal(suspended, UserHandle.USER_SYSTEM); + mInjector.binderWithCleanCallingIdentity( + () -> applyPersonalAppsSuspension(callingUserId, suspended)); + } - mInjector.binderWithCleanCallingIdentity(() -> { - if (suspended) { - sendPersonalAppsSuspendedNotification(callingUserId); + /** + * 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. + */ + private void updatePersonalAppSuspension(int profileUserId, boolean running) { + final boolean shouldSuspend; + synchronized (getLockObject()) { + final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId); + if (profileOwner != null) { + final boolean deadlineReached = + updateProfileOffDeadlineLocked(profileUserId, profileOwner, running); + shouldSuspend = deadlineReached || profileOwner.mSuspendPersonalApps; + Slog.d(LOG_TAG, String.format( + "Should personal use be suspended: %b; explicit: %b; timeout: %b", + shouldSuspend, profileOwner.mSuspendPersonalApps, deadlineReached)); } else { - clearPersonalAppsSuspendedNotification(callingUserId); + shouldSuspend = false; } - }); + } + + applyPersonalAppsSuspension(profileUserId, shouldSuspend); + } + + /** + * 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. + */ + private boolean updateProfileOffDeadlineLocked( + int profileUserId, ActiveAdmin profileOwner, boolean unlocked) { + final long now = System.currentTimeMillis(); + 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; + } + boolean shouldSaveSettings = false; + if (profileOwner.mProfileOffDeadline != 0 + && (profileOwner.mProfileMaximumTimeOff == 0 || unlocked)) { + // There is a deadline but either there is no policy or the profile is unlocked -> clear + // the deadline. + Slog.i(LOG_TAG, "Profile off deadline is reset to zero"); + profileOwner.mProfileOffDeadline = 0; + shouldSaveSettings = true; + } else if (profileOwner.mProfileOffDeadline == 0 + && (profileOwner.mProfileMaximumTimeOff != 0 && !unlocked)) { + // There profile is locked and there is a policy, but the deadline is not set -> set the + // deadline. + Slog.i(LOG_TAG, "Profile off deadline is set."); + profileOwner.mProfileOffDeadline = now + profileOwner.mProfileMaximumTimeOff; + shouldSaveSettings = true; + } + + updateProfileOffAlarm(profileOwner.mProfileOffDeadline); + + if (shouldSaveSettings) { + saveSettingsLocked(profileUserId); + } + return false; + } + + private void updateProfileOffAlarm(long profileOffDeadline) { + final AlarmManager am = mInjector.getAlarmManager(); + final PendingIntent pi = PendingIntent.getBroadcast(mContext, REQUEST_PROFILE_OFF_DEADLINE, + new Intent(ACTION_PROFILE_OFF_DEADLINE), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + 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."); + } + } + + private void applyPersonalAppsSuspension(int profileUserId, boolean shouldSuspend) { + final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mAppsSuspended; + if (suspended != shouldSuspend) { + suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM); + } + + if (shouldSuspend) { + sendPersonalAppsSuspendedNotification(profileUserId); + } else { + clearPersonalAppsSuspendedNotification(); + } } private void suspendPersonalAppsInternal(boolean suspended, int userId) { @@ -15310,21 +15425,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); synchronized (getLockObject()) { - getUserData(userId).mPersonalAppsSuspended = suspended; + getUserData(userId).mAppsSuspended = suspended; saveSettingsLocked(userId); } } - private void maybeUpdatePersonalAppsSuspendedNotification(int profileUserId) { - // TODO(b/147414651): Unless updated, the notification stops working after turning the - // profile off and back on, so it has to be updated more often than necessary. - if (getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended - && getProfileParentId(profileUserId) == UserHandle.USER_SYSTEM) { - sendPersonalAppsSuspendedNotification(profileUserId); - } - } - - private void clearPersonalAppsSuspendedNotification(int userId) { + private void clearPersonalAppsSuspendedNotification() { mInjector.binderWithCleanCallingIdentity(() -> mInjector.getNotificationManager().cancel( SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED)); @@ -15358,4 +15464,48 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.getNotificationManager().notify( SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification); } + + @Override + public void setManagedProfileMaximumTimeOff(ComponentName who, long timeoutMs) { + final int userId = mInjector.userHandleGetCallingUserId(); + synchronized (getLockObject()) { + final ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, + false /* parent */); + // DO shouldn't be able to use this method. + enforceProfileOwnerOfOrganizationOwnedDevice(admin); + enforceHandlesCheckPolicyComplianceIntent(userId, admin.info.getPackageName()); + if (admin.mProfileMaximumTimeOff == timeoutMs) { + return; + } + admin.mProfileMaximumTimeOff = timeoutMs; + saveSettingsLocked(userId); + } + + mInjector.binderWithCleanCallingIdentity( + () -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked())); + } + + void enforceHandlesCheckPolicyComplianceIntent(@UserIdInt int userId, String packageName) { + mInjector.binderWithCleanCallingIdentity(() -> { + final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE); + intent.setPackage(packageName); + final List<ResolveInfo> handlers = mInjector.getPackageManager() + .queryIntentActivitiesAsUser(intent, /* flags= */ 0, userId); + Preconditions.checkState(!handlers.isEmpty(), + "Admin doesn't handle " + DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE); + }); + } + + @Override + public long getManagedProfileMaximumTimeOff(ComponentName who) { + synchronized (getLockObject()) { + final ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, + false /* parent */); + // DO shouldn't be able to use this method. + enforceProfileOwnerOfOrganizationOwnedDevice(admin); + return admin.mProfileMaximumTimeOff; + } + } } |