diff options
5 files changed, 67 insertions, 12 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index b873be3e7042..713126ee9341 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -24,6 +24,10 @@ import java.util.List; /** * Device policy manager local system service interface. * + * Maintenance note: if you need to expose information from DPMS to lower level services such as + * PM/UM/AM/etc, then exposing it from DevicePolicyManagerInternal is not safe because it may cause + * lock order inversion. Consider using {@link DevicePolicyCache} instead. + * * @hide Only for use within the system server. */ public abstract class DevicePolicyManagerInternal { @@ -81,6 +85,16 @@ public abstract class DevicePolicyManagerInternal { public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy); /** + * Checks if an app with given uid is the active supervision admin. + * + * <p>This takes the DPMS lock. DO NOT call from PM/UM/AM with their lock held. + * + * @param uid App uid. + * @return true if the uid is the active supervision app. + */ + public abstract boolean isActiveSupervisionApp(int uid); + + /** * Creates an intent to show the admin support dialog to say that an action is disallowed by * the device/profile owner. * diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 656f474f72ce..1f13a1e13d13 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -801,8 +801,8 @@ public final class UsageStatsManager { * {@link #EXTRA_TIME_USED}. Cannot be {@code null} unless the observer is * being registered with a {@code timeUsed} equal to or greater than * {@code timeLimit}. - * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE - * permissions. + * @throws SecurityException if the caller is neither the active supervision app nor does it + * have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions. * @hide */ @SystemApi @@ -827,8 +827,8 @@ public final class UsageStatsManager { * an observer that was already unregistered or never registered will have no effect. * * @param observerId The id of the observer that was previously registered. - * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE - * permissions. + * @throws SecurityException if the caller is neither the active supervision app nor does it + * have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions. * @hide */ @SystemApi diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c3ff28530b7f..05e83805f4c3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11320,6 +11320,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + @Override + public boolean isActiveSupervisionApp(int uid) { + synchronized (getLockObject()) { + final ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked( + null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid); + if (admin == null) { + return false; + } + + final String supervisionString = mContext.getResources().getString( + com.android.internal.R.string + .config_defaultSupervisionProfileOwnerComponent); + if (supervisionString == null) { + return false; + } + + final ComponentName supervisorComponent = ComponentName.unflattenFromString( + supervisionString); + return admin.info.getComponent().equals(supervisorComponent); + } + } + private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) { final List<OnCrossProfileWidgetProvidersChangeListener> listeners; synchronized (getLockObject()) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index a25e40f8cc13..9a1fd9cf0e12 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2650,6 +2650,21 @@ public class DevicePolicyManagerTest extends DpmTestBase { verifyStayOnWhilePluggedCleared(false); } + public void testIsActiveSupervisionApp() throws Exception { + when(mServiceContext.resources + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)) + .thenReturn(admin1.flattenToString()); + + final int PROFILE_USER = 15; + final int PROFILE_ADMIN = UserHandle.getUid(PROFILE_USER, 19436); + addManagedProfile(admin1, PROFILE_ADMIN, admin1); + mContext.binder.callingUid = PROFILE_ADMIN; + + final DevicePolicyManagerInternal dpmi = + LocalServices.getService(DevicePolicyManagerInternal.class); + assertTrue(dpmi.isActiveSupervisionApp(PROFILE_ADMIN)); + } + // Test if lock timeout on managed profile is handled correctly depending on whether profile // uses separate challenge. public void testSetMaximumTimeToLockProfile() throws Exception { diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 59d07357ff56..6a818c1f8135 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1729,10 +1729,13 @@ public class UsageStatsService extends SystemService implements public void registerAppUsageLimitObserver(int observerId, String[] packages, long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent, String callingPackage) { + final int callingUid = Binder.getCallingUid(); + final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (!hasPermissions(callingPackage, - Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) { - throw new SecurityException("Caller doesn't have both SUSPEND_APPS and " - + "OBSERVE_APP_USAGE permissions"); + Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE) + && (dpmInternal != null && !dpmInternal.isActiveSupervisionApp(callingUid))) { + throw new SecurityException("Caller must be the active supervision app or " + + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions"); } if (packages == null || packages.length == 0) { @@ -1741,7 +1744,6 @@ public class UsageStatsService extends SystemService implements if (callbackIntent == null && timeUsedMs < timeLimitMs) { throw new NullPointerException("callbackIntent can't be null"); } - final int callingUid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(callingUid); final long token = Binder.clearCallingIdentity(); try { @@ -1754,13 +1756,15 @@ public class UsageStatsService extends SystemService implements @Override public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) { + final int callingUid = Binder.getCallingUid(); + final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (!hasPermissions(callingPackage, - Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) { - throw new SecurityException("Caller doesn't have both SUSPEND_APPS and " - + "OBSERVE_APP_USAGE permissions"); + Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE) + && (dpmInternal != null && !dpmInternal.isActiveSupervisionApp(callingUid))) { + throw new SecurityException("Caller must be the active supervision app or " + + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions"); } - final int callingUid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(callingUid); final long token = Binder.clearCallingIdentity(); try { |