From 4f16073556f7978708fb71c87628cfe1692412d5 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 27 Oct 2015 17:15:38 -0700 Subject: [PATCH] Make UserManager enforce user restrictions, not DPM. - Now even if a user restriction is set via UserManager, it'll be correctly enforced. - Changed the way AudioService enforces the OP_MUTE_MICROPHONE and OP_AUDIO_MASTER_VOLUME app ops -- previously, when they're set, even a muting call would be rejected. This was why DPMS.setUserRestriction() used different calling orders for DISALLOW_UNMUTE_MICROPHONE/DISALLOW_ADJUST_VOLUME depending on setting them or clearing them. Now, even when the app ops are set, we still allow muting calls. Bug 23902097 Bug 24981972 Change-Id: I865b5de43e15f5955f94006475a5ec6254904d31 --- .../android/server/audio/AudioService.java | 6 +- .../android/server/pm/UserManagerService.java | 32 +++-- .../server/pm/UserRestrictionsUtils.java | 127 +++++++++++++++++- .../DevicePolicyManagerService.java | 78 +---------- 4 files changed, 153 insertions(+), 90 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 66e731a29dde9..d89f47cc622da 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1756,7 +1756,8 @@ public class AudioService extends IAudioService.Stub { if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); } - if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage) + // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting. + if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } @@ -1848,7 +1849,8 @@ public class AudioService extends IAudioService.Stub { if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); } - if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage) + // If OP_MUTE_MICROPHONE is set, disallow unmuting. + if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c1f827fa733cc..62ced52fdbef3 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -199,6 +199,14 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mRestrictionsLock") private final SparseArray mCachedEffectiveUserRestrictions = new SparseArray<>(); + /** + * User restrictions that have already been applied in {@link #applyUserRestrictionsRL}. We + * use it to detect restrictions that have changed since the last + * {@link #applyUserRestrictionsRL} call. + */ + @GuardedBy("mRestrictionsLock") + private final SparseArray mAppliedUserRestrictions = new SparseArray<>(); + private final Bundle mGuestRestrictions = new Bundle(); /** @@ -727,8 +735,6 @@ public class UserManagerService extends IUserManager.Stub { Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId + " bundle=" + newRestrictions); } - final Bundle prevRestrictions = getEffectiveUserRestrictions(userId); - // Update system restrictions. if (newRestrictions != null) { // If newRestrictions == the current one, it's probably a bug. @@ -742,12 +748,18 @@ public class UserManagerService extends IUserManager.Stub { mCachedEffectiveUserRestrictions.put(userId, effective); - applyUserRestrictionsRL(userId, effective, prevRestrictions); + applyUserRestrictionsRL(userId, effective); } @GuardedBy("mRestrictionsLock") - private void applyUserRestrictionsRL(int userId, - Bundle newRestrictions, Bundle prevRestrictions) { + private void applyUserRestrictionsRL(int userId, Bundle newRestrictions) { + final Bundle prevRestrictions = mAppliedUserRestrictions.get(userId); + + if (DBG) { + Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId + + " new=" + newRestrictions + " prev=" + prevRestrictions); + } + final long token = Binder.clearCallingIdentity(); try { mAppOpsService.setUserRestrictions(newRestrictions, userId); @@ -757,7 +769,10 @@ public class UserManagerService extends IUserManager.Stub { Binder.restoreCallingIdentity(token); } - // TODO Move the code from DPMS.setUserRestriction(). + UserRestrictionsUtils.applyUserRestrictions( + mContext, userId, newRestrictions, prevRestrictions); + + mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions)); } @GuardedBy("mRestrictionsLock") @@ -768,9 +783,8 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mRestrictionsLock") private void updateEffectiveUserRestrictionsForAllUsersRL() { // First, invalidate all cached values. - synchronized (mRestrictionsLock) { - mCachedEffectiveUserRestrictions.clear(); - } + mCachedEffectiveUserRestrictions.clear(); + // We don't want to call into ActivityManagerNative while taking a lock, so we'll call // it on a handler. final Runnable r = new Runnable() { diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 23e3b35ae3da8..28df9f673ed0d 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -18,10 +18,19 @@ package com.android.server.pm; import com.google.android.collect.Sets; -import com.android.internal.util.Preconditions; - +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.media.IAudioService; +import android.net.Uri; +import android.os.Binder; import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.UserHandle; import android.os.UserManager; +import android.util.Slog; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; @@ -31,6 +40,8 @@ import java.io.PrintWriter; import java.util.Set; public class UserRestrictionsUtils { + private static final String TAG = "UserRestrictionsUtils"; + private UserRestrictionsUtils() { } @@ -115,6 +126,118 @@ public class UserRestrictionsUtils { } } + /** + * Takes a new use restriction set and the previous set, and apply the restrictions that have + * changed. + */ + public static void applyUserRestrictions(Context context, int userId, + @Nullable Bundle newRestrictions, @Nullable Bundle prevRestrictions) { + if (newRestrictions == null) { + newRestrictions = Bundle.EMPTY; + } + if (prevRestrictions == null) { + prevRestrictions = Bundle.EMPTY; + } + for (String key : USER_RESTRICTIONS) { + final boolean newValue = newRestrictions.getBoolean(key); + final boolean prevValue = prevRestrictions.getBoolean(key); + + if (newValue != prevValue) { + applyUserRestriction(context, userId, key, newValue); + } + } + } + + private static void applyUserRestriction(Context context, int userId, String key, + boolean newValue) { + // When certain restrictions are cleared, we don't update the system settings, + // because these settings are changeable on the Settings UI and we don't know the original + // value -- for example LOCATION_MODE might have been off already when the restriction was + // set, and in that case even if the restriction is lifted, changing it to ON would be + // wrong. So just don't do anything in such a case. If the user hopes to enable location + // later, they can do it on the Settings UI. + + final ContentResolver cr = context.getContentResolver(); + final long id = Binder.clearCallingIdentity(); + try { + switch (key) { + case UserManager.DISALLOW_UNMUTE_MICROPHONE: + IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE)) + .setMicrophoneMute(newValue, context.getPackageName(), userId); + break; + case UserManager.DISALLOW_ADJUST_VOLUME: + IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE)) + .setMasterMute(newValue, 0, context.getPackageName(), userId); + break; + case UserManager.DISALLOW_CONFIG_WIFI: + if (newValue) { + android.provider.Settings.Secure.putIntForUser(cr, + android.provider.Settings.Secure + .WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, userId); + } + break; + case UserManager.DISALLOW_SHARE_LOCATION: + if (newValue) { + android.provider.Settings.Secure.putIntForUser(cr, + android.provider.Settings.Secure.LOCATION_MODE, + android.provider.Settings.Secure.LOCATION_MODE_OFF, + userId); + android.provider.Settings.Secure.putStringForUser(cr, + android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "", + userId); + } + // Send out notifications as some clients may want to reread the + // value which actually changed due to a restriction having been + // applied. + final String property = + android.provider.Settings.Secure.SYS_PROP_SETTING_VERSION; + long version = SystemProperties.getLong(property, 0) + 1; + SystemProperties.set(property, Long.toString(version)); + + final String name = android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED; + final Uri url = Uri.withAppendedPath( + android.provider.Settings.Secure.CONTENT_URI, name); + context.getContentResolver().notifyChange(url, null, true, userId); + + break; + case UserManager.DISALLOW_DEBUGGING_FEATURES: + if (newValue) { + // Only disable adb if changing for system user, since it is global + // TODO: should this be admin user? + if (userId == UserHandle.USER_SYSTEM) { + android.provider.Settings.Global.putStringForUser(cr, + android.provider.Settings.Global.ADB_ENABLED, "0", + userId); + } + } + break; + case UserManager.ENSURE_VERIFY_APPS: + if (newValue) { + android.provider.Settings.Global.putStringForUser( + context.getContentResolver(), + android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, "1", + userId); + android.provider.Settings.Global.putStringForUser( + context.getContentResolver(), + android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1", + userId); + } + break; + case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES: + if (newValue) { + android.provider.Settings.Secure.putIntForUser(cr, + android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, 0, + userId); + } + break; + } + } catch (RemoteException re) { + Slog.e(TAG, "Failed to talk to AudioService.", re); + } finally { + Binder.restoreCallingIdentity(id); + } + } + public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) { boolean noneSet = true; if (restrictions != null) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2e7f609426d0b..1602c120c611e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5618,9 +5618,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final long id = mInjector.binderClearCallingIdentity(); try { - // Original value. - final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user); - // Save the restriction to ActiveAdmin. // TODO When DO sets a restriction, it'll always be treated as device-wide. // If there'll be a policy that can be set by both, we'll need scoping support, @@ -5629,85 +5626,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner); saveSettingsLocked(userHandle); - // Tell UserManager the new value. Note this needs to be done before calling - // into AudioService, because AS will check AppOps that'll be updated by UM. + // Tell UserManager the new value. if (isDeviceOwner) { mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL(); } else { mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle); } - - // New value. - final boolean enabled = mUserManager.hasUserRestriction(key, user); - - // TODO The rest of the code should move to UserManagerService. - - if (enabled && !alreadyRestricted) { - if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { - mInjector.getIAudioService() - .setMicrophoneMute(true, mContext.getPackageName(), userHandle); - } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - mInjector.getIAudioService() - .setMasterMute(true, 0, mContext.getPackageName(), userHandle); - } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) { - mInjector.settingsSecurePutIntForUser( - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, - userHandle); - } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { - mInjector.settingsSecurePutIntForUser( - Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_MODE_OFF, - userHandle); - mInjector.settingsSecurePutStringForUser( - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "", - userHandle); - } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) { - // Only disable adb if changing for system user, since it is global - // TODO: should this be admin user? - if (userHandle == UserHandle.USER_SYSTEM) { - mInjector.settingsGlobalPutStringForUser( - Settings.Global.ADB_ENABLED, "0", userHandle); - } - } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) { - mInjector.settingsGlobalPutStringForUser( - Settings.Global.PACKAGE_VERIFIER_ENABLE, "1", - userHandle); - mInjector.settingsGlobalPutStringForUser( - Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1", - userHandle); - } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) { - mInjector.settingsSecurePutIntForUser( - Settings.Secure.INSTALL_NON_MARKET_APPS, 0, - userHandle); - } - } - - if (enabled != alreadyRestricted) { - if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { - // Send out notifications however as some clients may want to reread the - // value which actually changed due to a restriction having been - // applied. - final String property = Settings.Secure.SYS_PROP_SETTING_VERSION; - long version = mInjector.systemPropertiesGetLong(property, 0) + 1; - mInjector.systemPropertiesSet(property, Long.toString(version)); - - final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED; - Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name); - mContext.getContentResolver().notifyChange(url, null, true, userHandle); - } - } - if (!enabled && alreadyRestricted) { - if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { - mInjector.getIAudioService() - .setMicrophoneMute(false, mContext.getPackageName(), - userHandle); - } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - mInjector.getIAudioService() - .setMasterMute(false, 0, mContext.getPackageName(), userHandle); - } - } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); } finally { mInjector.binderRestoreCallingIdentity(id); }