diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/os/IUserManager.aidl | 3 | ||||
| -rw-r--r-- | core/java/android/os/UserManager.java | 62 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/UnlaunchableAppActivity.java | 2 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 5 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java | 12 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserManagerService.java | 216 |
8 files changed, 185 insertions, 117 deletions
diff --git a/api/current.txt b/api/current.txt index ced83540ec22..60d314f5cc1b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -32264,6 +32264,7 @@ package android.os { method public deprecated void setUserRestrictions(android.os.Bundle); method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); method public static boolean supportsMultipleUsers(); + method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle); field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking"; field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; diff --git a/api/system-current.txt b/api/system-current.txt index 30530a359d8f..ec509ad59539 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -103,6 +103,7 @@ package android { field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING"; field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE"; + field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS"; field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"; field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 9c90c3802caf..f643c578ebe7 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -79,9 +79,7 @@ interface IUserManager { void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); boolean markGuestForDeletion(int userHandle); - void setQuietModeEnabled(int userHandle, boolean enableQuietMode, in IntentSender target); boolean isQuietModeEnabled(int userHandle); - boolean trySetQuietModeDisabled(int userHandle, in IntentSender target); void setSeedAccountData(int userHandle, in String accountName, in String accountType, in PersistableBundle accountOptions, boolean persist); String getSeedAccountName(); @@ -99,4 +97,5 @@ interface IUserManager { boolean isUserRunning(int userId); boolean isUserNameSet(int userHandle); boolean hasRestrictedProfiles(); + boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 3504142a81c9..75cbd57fe178 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2130,15 +2130,46 @@ public class UserManager { } /** - * Set quiet mode of a managed profile. + * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a + * managed profile don't run, generate notifications, or consume data or battery. + * <p> + * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be + * shown to the user. + * <p> + * The change may not happen instantly, however apps can listen for + * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and + * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of + * the change of the quiet mode. Apps can also check the current state of quiet mode by + * calling {@link #isQuietModeEnabled(UserHandle)}. + * <p> + * The caller must either be the foreground default launcher or have one of these permissions: + * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}. + * + * @param enableQuietMode whether quiet mode should be enabled or disabled + * @param userHandle user handle of the profile + * @return {@code false} if user's credential is needed in order to turn off quiet mode, + * {@code true} otherwise + * @throws SecurityException if the caller is invalid + * @throws IllegalArgumentException if {@code userHandle} is not a managed profile + * + * @see #isQuietModeEnabled(UserHandle) + */ + public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) { + return trySetQuietModeEnabled(enableQuietMode, userHandle, null); + } + + /** + * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify + * a target to start when user is unlocked. * - * @param userHandle The user handle of the profile. - * @param enableQuietMode Whether quiet mode should be enabled or disabled. + * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)} * @hide */ - public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) { + public boolean trySetQuietModeEnabled( + boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) { try { - mService.setQuietModeEnabled(userHandle, enableQuietMode, null); + return mService.trySetQuietModeEnabled( + mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2160,27 +2191,6 @@ public class UserManager { } /** - * Tries disabling quiet mode for a given user. If the user is still locked, we unlock the user - * first by showing the confirm credentials screen and disable quiet mode upon successful - * unlocking. If the user is already unlocked, we call through to {@link #setQuietModeEnabled} - * directly. - * - * @param userHandle The user that is going to disable quiet mode. - * @param target The target to launch when the user is unlocked. - * @return {@code true} if quiet mode is disabled without showing confirm credentials screen, - * {@code false} otherwise. - * @hide - */ - public boolean trySetQuietModeDisabled( - @UserIdInt int userHandle, @Nullable IntentSender target) { - try { - return mService.trySetQuietModeDisabled(userHandle, target); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * If the target user is a managed profile of the calling user or the caller * is itself a managed profile, then this returns a badged copy of the given * icon to be able to distinguish it from the original icon. For badging an diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 8016a6559bcc..2eadaf3a06b0 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -111,7 +111,7 @@ public class UnlaunchableAppActivity extends Activity @Override public void onClick(DialogInterface dialog, int which) { if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) { - UserManager.get(this).trySetQuietModeDisabled(mUserId, mTarget); + UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget); } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 13fedfec6082..e303927e78c6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3632,6 +3632,11 @@ <permission android:name="android.permission.READ_RUNTIME_PROFILES" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to turn on / off quiet mode. + @hide <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MODIFY_QUIET_MODE" + android:protectionLevel="signature|privileged" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 316bd5bcaca2..7f4deb0372f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -61,14 +61,10 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { public void setWorkModeEnabled(boolean enableWorkMode) { synchronized (mProfiles) { for (UserInfo ui : mProfiles) { - if (enableWorkMode) { - if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) { - StatusBarManager statusBarManager = (StatusBarManager) mContext - .getSystemService(android.app.Service.STATUS_BAR_SERVICE); - statusBarManager.collapsePanels(); - } - } else { - mUserManager.setQuietModeEnabled(ui.id, true); + if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) { + StatusBarManager statusBarManager = (StatusBarManager) mContext + .getSystemService(android.app.Service.STATUS_BAR_SERVICE); + statusBarManager.collapsePanels(); } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 03cd4f1d3269..768eb8f37549 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -27,6 +27,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; +import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IStopUserCallback; import android.app.KeyguardManager; @@ -38,6 +39,7 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; @@ -386,7 +388,7 @@ public class UserManagerService extends IUserManager.Stub { /** * Start an {@link IntentSender} when user is unlocked after disabling quiet mode. * - * @see {@link #trySetQuietModeDisabled(int, IntentSender)} + * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)} */ private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub { private final IntentSender mTarget; @@ -784,48 +786,114 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public void setQuietModeEnabled(int userHandle, boolean enableQuietMode, IntentSender target) { - checkManageUsersPermission("silence profile"); - boolean changed = false; - UserInfo profile, parent; - synchronized (mPackagesLock) { - synchronized (mUsersLock) { - profile = getUserInfoLU(userHandle); - parent = getProfileParentLU(userHandle); + public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode, + int userHandle, @Nullable IntentSender target) { + Preconditions.checkNotNull(callingPackage); + + if (enableQuietMode && target != null) { + throw new IllegalArgumentException( + "target should only be specified when we are disabling quiet mode."); + } + if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) { + throw new SecurityException("Not allowed to call trySetQuietModeEnabled, " + + "caller is foreground default launcher " + + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission"); + } + + final long identity = Binder.clearCallingIdentity(); + try { + if (enableQuietMode) { + setQuietModeEnabled(userHandle, true /* enableQuietMode */, target); + return true; + } else { + boolean needToShowConfirmCredential = + mLockPatternUtils.isSecure(userHandle) + && !StorageManager.isUserKeyUnlocked(userHandle); + if (needToShowConfirmCredential) { + showConfirmCredentialToDisableQuietMode(userHandle, target); + return false; + } else { + setQuietModeEnabled(userHandle, false /* enableQuietMode */, target); + return true; + } } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * An app can modify quiet mode if the caller meets one of the condition: + * <ul> + * <li>Has system UID or root UID</li> + * <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li> + * <li>Has {@link Manifest.permission#MANAGE_USERS}</li> + * </ul> + */ + private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) { + if (hasManageUsersPermission()) { + return true; + } + + final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission( + Manifest.permission.MODIFY_QUIET_MODE, + callingUid, -1, true) == PackageManager.PERMISSION_GRANTED; + if (hasModifyQuietModePermission) { + return true; + } + + final ShortcutServiceInternal shortcutInternal = + LocalServices.getService(ShortcutServiceInternal.class); + if (shortcutInternal != null) { + boolean isForegroundLauncher = + shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid); + if (isForegroundLauncher) { + return true; + } + } + return false; + } + + private void setQuietModeEnabled( + int userHandle, boolean enableQuietMode, IntentSender target) { + final UserInfo profile, parent; + final UserData profileUserData; + synchronized (mUsersLock) { + profile = getUserInfoLU(userHandle); + parent = getProfileParentLU(userHandle); + if (profile == null || !profile.isManagedProfile()) { throw new IllegalArgumentException("User " + userHandle + " is not a profile"); } - if (profile.isQuietModeEnabled() != enableQuietMode) { - profile.flags ^= UserInfo.FLAG_QUIET_MODE; - writeUserLP(getUserDataLU(profile.id)); - changed = true; + if (profile.isQuietModeEnabled() == enableQuietMode) { + Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode); + return; } + profile.flags ^= UserInfo.FLAG_QUIET_MODE; + profileUserData = getUserDataLU(profile.id); } - if (changed) { - long identity = Binder.clearCallingIdentity(); - try { - if (enableQuietMode) { - ActivityManager.getService().stopUser(userHandle, /* force */true, null); - LocalServices.getService(ActivityManagerInternal.class) - .killForegroundAppsForUser(userHandle); - } else { - IProgressListener callback = target != null - ? new DisableQuietModeUserUnlockedCallback(target) - : null; - ActivityManager.getService().startUserInBackgroundWithListener( - userHandle, callback); - } - } catch (RemoteException e) { - Slog.e(LOG_TAG, "fail to start/stop user for quiet mode", e); - } finally { - Binder.restoreCallingIdentity(identity); + synchronized (mPackagesLock) { + writeUserLP(profileUserData); + } + try { + if (enableQuietMode) { + ActivityManager.getService().stopUser(userHandle, /* force */true, null); + LocalServices.getService(ActivityManagerInternal.class) + .killForegroundAppsForUser(userHandle); + } else { + IProgressListener callback = target != null + ? new DisableQuietModeUserUnlockedCallback(target) + : null; + ActivityManager.getService().startUserInBackgroundWithListener( + userHandle, callback); } - - broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(), - enableQuietMode); + } catch (RemoteException e) { + // Should not happen, same process. + e.rethrowAsRuntimeException(); } + broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(), + enableQuietMode); } @Override @@ -842,54 +910,42 @@ public class UserManagerService extends IUserManager.Stub { } } - @Override - public boolean trySetQuietModeDisabled( + /** + * Show confirm credential screen to unlock user in order to turn off quiet mode. + */ + private void showConfirmCredentialToDisableQuietMode( @UserIdInt int userHandle, @Nullable IntentSender target) { - checkManageUsersPermission("silence profile"); - if (StorageManager.isUserKeyUnlocked(userHandle) - || !mLockPatternUtils.isSecure(userHandle)) { - // if the user is already unlocked, no need to show a profile challenge - setQuietModeEnabled(userHandle, false, target); - return true; - } - - long identity = Binder.clearCallingIdentity(); - try { - // otherwise, we show a profile challenge to trigger decryption of the user - final KeyguardManager km = (KeyguardManager) mContext.getSystemService( - Context.KEYGUARD_SERVICE); - // We should use userHandle not credentialOwnerUserId here, as even if it is unified - // lock, confirm screenlock page will know and show personal challenge, and unlock - // work profile when personal challenge is correct - final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, - userHandle); - if (unlockIntent == null) { - return false; - } - final Intent callBackIntent = new Intent( - ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK); - if (target != null) { - callBackIntent.putExtra(Intent.EXTRA_INTENT, target); - } - callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle); - callBackIntent.setPackage(mContext.getPackageName()); - callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - final PendingIntent pendingIntent = PendingIntent.getBroadcast( - mContext, - 0, - callBackIntent, - PendingIntent.FLAG_CANCEL_CURRENT | - PendingIntent.FLAG_ONE_SHOT | - PendingIntent.FLAG_IMMUTABLE); - // After unlocking the challenge, it will disable quiet mode and run the original - // intentSender - unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender()); - unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - mContext.startActivity(unlockIntent); - } finally { - Binder.restoreCallingIdentity(identity); + // otherwise, we show a profile challenge to trigger decryption of the user + final KeyguardManager km = (KeyguardManager) mContext.getSystemService( + Context.KEYGUARD_SERVICE); + // We should use userHandle not credentialOwnerUserId here, as even if it is unified + // lock, confirm screenlock page will know and show personal challenge, and unlock + // work profile when personal challenge is correct + final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, + userHandle); + if (unlockIntent == null) { + return; } - return false; + final Intent callBackIntent = new Intent( + ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK); + if (target != null) { + callBackIntent.putExtra(Intent.EXTRA_INTENT, target); + } + callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle); + callBackIntent.setPackage(mContext.getPackageName()); + callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, + 0, + callBackIntent, + PendingIntent.FLAG_CANCEL_CURRENT | + PendingIntent.FLAG_ONE_SHOT | + PendingIntent.FLAG_IMMUTABLE); + // After unlocking the challenge, it will disable quiet mode and run the original + // intentSender + unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender()); + unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + mContext.startActivity(unlockIntent); } @Override |