Merge changes from topic "protectedPackages"

* changes:
  Allow non-DPC admins to use DeviceAdminService
  Give precedence to protectedPackages set locally
  Resolve policies that could be set locally and globally
This commit is contained in:
Kholoud Mohamed
2023-01-07 17:51:58 +00:00
committed by Android (Google) Code Review
20 changed files with 930 additions and 196 deletions

View File

@@ -8160,6 +8160,7 @@ package android.app.admin {
field @NonNull public static final android.app.admin.TargetUser GLOBAL;
field @NonNull public static final android.app.admin.TargetUser LOCAL_USER;
field @NonNull public static final android.app.admin.TargetUser PARENT_USER;
field @NonNull public static final android.app.admin.TargetUser UNKNOWN_USER;
}
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {

View File

@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
// TODO(b/263363091): Restrict to DPC and holders of a new role permission and update javadocs
/**
* Base class for a service that device owner/profile owners can optionally have.
*
@@ -45,6 +46,11 @@ import android.os.IBinder;
*
* <p>Note the process may still be killed if the system is under heavy memory pressure, in which
* case the process will be re-started later.
*
* <p>Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
* non-DPC admins can also optionally implement this service using the details
* mentioned above to ensure they receive policy update broadcasts
* (see {@link PolicyUpdatesReceiver}).
*/
public class DeviceAdminService extends Service {
private final IDeviceAdminServiceImpl mImpl;

View File

@@ -4005,6 +4005,12 @@ public class DevicePolicyManager {
*/
public static final String LOCK_TASK_POLICY = "lockTask";
// TODO: Expose this as SystemAPI once we add the query API
/**
* @hide
*/
public static final String USER_CONTROL_DISABLED_PACKAGES = "userControlDisabledPackages";
/**
* This object is a single place to tack on invalidation and disable calls. All
* binder caches in this class derive from this Config, so all can be invalidated or

View File

@@ -32,7 +32,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
// TODO(b/261432333): Add more detailed javadocs on using DeviceAdminService.
/**
* Base class for implementing a policy update receiver. This class provides a convenience for
* interpreting the raw intent actions ({@link #ACTION_DEVICE_POLICY_SET_RESULT} and
@@ -43,6 +42,10 @@ import java.util.Objects;
*
* <p>When publishing your {@code PolicyUpdatesReceiver} subclass as a receiver, it must
* require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission.
*
* <p>Admins can implement {@link DeviceAdminService} to ensure they receive all policy updates
* (for policies they have set) via {@link #onPolicyChanged} by constantly being bound to by the
* system. For more information see {@link DeviceAdminService}.
*/
public abstract class PolicyUpdatesReceiver extends BroadcastReceiver {
private static String TAG = "PolicyUpdatesReceiver";

View File

@@ -43,6 +43,11 @@ public final class TargetUser {
*/
public static final int GLOBAL_USER_ID = -3;
/**
* @hide
*/
public static final int UNKNOWN_USER_ID = -3;
/**
* Indicates that the policy relates to the user the admin is installed on.
*/
@@ -61,6 +66,15 @@ public final class TargetUser {
@NonNull
public static final TargetUser GLOBAL = new TargetUser(GLOBAL_USER_ID);
/**
* Indicates that the policy relates to some unknown user on the device. For example, if Admin1
* has set a global policy on a device and Admin2 has set a conflicting local
* policy on some other secondary user, Admin1 will get a policy update callback with
* {@code UNKNOWN_USER} as the target user.
*/
@NonNull
public static final TargetUser UNKNOWN_USER = new TargetUser(UNKNOWN_USER_ID);
private final int mUserId;
/**

View File

@@ -375,10 +375,15 @@ public abstract class PackageManagerInternal {
int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners);
/**
* Marks packages as protected for a given user or all users in case of USER_ALL.
* Marks packages as protected for a given user or all users in case of USER_ALL. Setting
* {@code packageNames} to {@code null} means unset all existing protected packages for the
* given user.
*
* <p> Note that setting it if set for a specific user, it takes precedence over the packages
* set globally using USER_ALL.
*/
public abstract void setOwnerProtectedPackages(
@UserIdInt int userId, @NonNull List<String> packageNames);
@UserIdInt int userId, @Nullable List<String> packageNames);
/**
* Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.

View File

@@ -354,7 +354,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
@Override
@Deprecated
public final void setOwnerProtectedPackages(
@UserIdInt int userId, @NonNull List<String> packageNames) {
@UserIdInt int userId, @Nullable List<String> packageNames) {
getProtectedPackages().setOwnerProtectedPackages(userId, packageNames);
}

View File

@@ -16,7 +16,6 @@
package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
@@ -81,8 +80,8 @@ public class ProtectedPackages {
/** Sets packages protected by a device or profile owner. */
public synchronized void setOwnerProtectedPackages(
@UserIdInt int userId, @NonNull List<String> packageNames) {
if (packageNames.isEmpty()) {
@UserIdInt int userId, @Nullable List<String> packageNames) {
if (packageNames == null) {
mOwnerProtectedPackages.remove(userId);
} else {
mOwnerProtectedPackages.put(userId, new ArraySet<>(packageNames));
@@ -134,15 +133,21 @@ public class ProtectedPackages {
*/
private synchronized boolean isOwnerProtectedPackage(
@UserIdInt int userId, String packageName) {
return isPackageProtectedForUser(UserHandle.USER_ALL, packageName)
|| isPackageProtectedForUser(userId, packageName);
return hasProtectedPackages(userId)
? isPackageProtectedForUser(userId, packageName)
: isPackageProtectedForUser(UserHandle.USER_ALL, packageName);
}
private synchronized boolean isPackageProtectedForUser(int userId, String packageName) {
private synchronized boolean isPackageProtectedForUser(
@UserIdInt int userId, String packageName) {
int userIdx = mOwnerProtectedPackages.indexOfKey(userId);
return userIdx >= 0 && mOwnerProtectedPackages.valueAt(userIdx).contains(packageName);
}
private synchronized boolean hasProtectedPackages(@UserIdInt int userId) {
return mOwnerProtectedPackages.indexOfKey(userId) >= 0;
}
/**
* Returns {@code true} if a given package's state is protected. Otherwise, returns
* {@code false}.

View File

@@ -35,8 +35,11 @@ import com.android.server.am.PersistentConnection;
import com.android.server.appbinding.AppBindingUtils;
import com.android.server.utils.Slogf;
import java.util.HashMap;
import java.util.Map;
/**
* Manages connections to persistent services in owner packages.
* Manages connections to persistent services in admin packages.
*/
public class DeviceAdminServiceController {
static final String TAG = DevicePolicyManagerService.LOG_TAG;
@@ -76,7 +79,8 @@ public class DeviceAdminServiceController {
* User-ID -> {@link PersistentConnection}.
*/
@GuardedBy("mLock")
private final SparseArray<DevicePolicyServiceConnection> mConnections = new SparseArray<>();
private final SparseArray<Map<String, DevicePolicyServiceConnection>> mConnections =
new SparseArray<>();
public DeviceAdminServiceController(DevicePolicyManagerService service,
DevicePolicyConstants constants) {
@@ -104,9 +108,9 @@ public class DeviceAdminServiceController {
/**
* Find a service that handles {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE}
* in an owner package and connect to it.
* in an admin package and connect to it.
*/
public void startServiceForOwner(@NonNull String packageName, int userId,
public void startServiceForAdmin(@NonNull String packageName, int userId,
@NonNull String actionForLog) {
final long token = mInjector.binderClearCallingIdentity();
try {
@@ -114,15 +118,16 @@ public class DeviceAdminServiceController {
final ServiceInfo service = findService(packageName, userId);
if (service == null) {
if (DEBUG) {
Slogf.d(TAG, "Owner package %s on u%d has no service.", packageName,
Slogf.d(TAG, "Admin package %s on u%d has no service.", packageName,
userId);
}
disconnectServiceOnUserLocked(userId, actionForLog);
disconnectServiceOnUserLocked(packageName, userId, actionForLog);
return;
}
// See if it's already running.
final PersistentConnection<IDeviceAdminService> existing =
mConnections.get(userId);
mConnections.contains(userId)
? mConnections.get(userId).get(packageName) : null;
if (existing != null) {
// Note even when we're already connected to the same service, the binding
// would have died at this point due to a package update. So we disconnect
@@ -131,18 +136,21 @@ public class DeviceAdminServiceController {
Slogf.d("Disconnecting from existing service connection.", packageName,
userId);
}
disconnectServiceOnUserLocked(userId, actionForLog);
disconnectServiceOnUserLocked(packageName, userId, actionForLog);
}
if (DEBUG) {
Slogf.d("Owner package %s on u%d has service %s for %s", packageName, userId,
Slogf.d("Admin package %s on u%d has service %s for %s", packageName, userId,
service.getComponentName().flattenToShortString(), actionForLog);
}
final DevicePolicyServiceConnection conn =
new DevicePolicyServiceConnection(
userId, service.getComponentName());
mConnections.put(userId, conn);
if (!mConnections.contains(userId)) {
mConnections.put(userId, new HashMap<>());
}
mConnections.get(userId).put(packageName, conn);
conn.bind();
}
} finally {
@@ -151,9 +159,24 @@ public class DeviceAdminServiceController {
}
/**
* Stop an owner service on a given user.
* Stop an admin service on a given user.
*/
public void stopServiceForOwner(int userId, @NonNull String actionForLog) {
public void stopServiceForAdmin(
@NonNull String packageName, int userId, @NonNull String actionForLog) {
final long token = mInjector.binderClearCallingIdentity();
try {
synchronized (mLock) {
disconnectServiceOnUserLocked(packageName, userId, actionForLog);
}
} finally {
mInjector.binderRestoreCallingIdentity(token);
}
}
/**
* Stop all admin services on a given user.
*/
public void stopServicesForUser(int userId, @NonNull String actionForLog) {
final long token = mInjector.binderClearCallingIdentity();
try {
synchronized (mLock) {
@@ -165,34 +188,68 @@ public class DeviceAdminServiceController {
}
@GuardedBy("mLock")
private void disconnectServiceOnUserLocked(int userId, @NonNull String actionForLog) {
final DevicePolicyServiceConnection conn = mConnections.get(userId);
private void disconnectServiceOnUserLocked(
@NonNull String packageName, int userId, @NonNull String actionForLog) {
final DevicePolicyServiceConnection conn = mConnections.contains(userId)
? mConnections.get(userId).get(packageName) : null;
if (conn != null) {
if (DEBUG) {
Slogf.d(TAG, "Stopping service for u%d if already running for %s.", userId,
Slogf.d(TAG, "Stopping service for package %s on u%d if already running for %s.",
packageName,
userId,
actionForLog);
}
conn.unbind();
mConnections.remove(userId);
mConnections.get(userId).remove(packageName);
if (mConnections.get(userId).isEmpty()) {
mConnections.remove(userId);
}
}
}
@GuardedBy("mLock")
private void disconnectServiceOnUserLocked(int userId, @NonNull String actionForLog) {
if (!mConnections.contains(userId)) {
return;
}
for (String packageName : mConnections.get(userId).keySet()) {
DevicePolicyServiceConnection conn = mConnections.get(userId).get(packageName);
if (DEBUG) {
Slogf.d(TAG,
"Stopping service for package %s on u%d if already running for %s.",
packageName,
userId,
actionForLog);
}
conn.unbind();
}
mConnections.remove(userId);
}
/** dump content */
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
if (mConnections.size() == 0) {
return;
}
pw.println("Owner Services:");
pw.println("Admin Services:");
pw.increaseIndent();
for (int i = 0; i < mConnections.size(); i++) {
final int userId = mConnections.keyAt(i);
pw.print("User: "); pw.println(userId);
pw.print("User: ");
pw.println(userId);
for (String packageName : mConnections.get(userId).keySet()) {
pw.increaseIndent();
pw.print("Package: ");
pw.println(packageName);
final DevicePolicyServiceConnection con = mConnections.valueAt(i);
pw.increaseIndent();
con.dump("", pw);
pw.decreaseIndent();
final DevicePolicyServiceConnection con = mConnections.valueAt(i)
.get(packageName);
pw.increaseIndent();
con.dump("", pw);
pw.decreaseIndent();
pw.decreaseIndent();
}
}
pw.decreaseIndent();
}

View File

@@ -28,15 +28,19 @@ import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_SUCCESS;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PolicyUpdatesReceiver;
import android.app.admin.TargetUser;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
@@ -54,7 +58,9 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -68,6 +74,8 @@ final class DevicePolicyEngine {
static final String TAG = "DevicePolicyEngine";
private final Context mContext;
private final UserManager mUserManager;
// TODO(b/256849338): add more granular locks
private final Object mLock = new Object();
@@ -81,20 +89,30 @@ final class DevicePolicyEngine {
*/
private final Map<String, PolicyState<?>> mGlobalPolicies;
DevicePolicyEngine(@NonNull Context context) {
/**
* Map containing the current set of admins in each user with active policies.
*/
private final SparseArray<Set<EnforcingAdmin>> mEnforcingAdmins;
private final DeviceAdminServiceController mDeviceAdminServiceController;
DevicePolicyEngine(
@NonNull Context context,
@NonNull DeviceAdminServiceController deviceAdminServiceController) {
mContext = Objects.requireNonNull(context);
mDeviceAdminServiceController = Objects.requireNonNull(deviceAdminServiceController);
mUserManager = mContext.getSystemService(UserManager.class);
mLocalPolicies = new SparseArray<>();
mGlobalPolicies = new HashMap<>();
mEnforcingAdmins = new SparseArray<>();
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
/**
* Set the policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
* Returns {@code true} if the enforced policy has been changed.
*
*/
<V> boolean setLocalPolicy(
<V> void setLocalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
@NonNull V value,
@@ -105,45 +123,129 @@ final class DevicePolicyEngine {
Objects.requireNonNull(value);
synchronized (mLock) {
PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
boolean hasGlobalPolicies = hasGlobalPolicyLocked(policyDefinition);
boolean policyChanged;
if (hasGlobalPolicies) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
policyChanged = localPolicyState.addPolicy(
enforcingAdmin,
value,
globalPolicyState.getPoliciesSetByAdmins());
} else {
policyChanged = localPolicyState.addPolicy(enforcingAdmin, value);
}
if (policyChanged) {
enforcePolicy(
policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
sendPolicyChangedToAdmins(
policyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
userId == enforcingAdmin.getUserId()
? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
}
boolean wasAdminPolicyEnforced = Objects.equals(
policyState.getCurrentResolvedPolicy(), value);
boolean policyEnforced = Objects.equals(
localPolicyState.getCurrentResolvedPolicy(), value);
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
wasAdminPolicyEnforced,
policyEnforced,
// TODO: we're always sending this for now, should properly handle errors.
REASON_CONFLICTING_ADMIN_POLICY,
userId == enforcingAdmin.getUserId()
? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
userId);
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
write();
return policyChanged;
}
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
/**
* Removes any previously set policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
*/
<V> void removeLocalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
int userId) {
Objects.requireNonNull(policyDefinition);
Objects.requireNonNull(enforcingAdmin);
synchronized (mLock) {
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
return;
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
boolean policyChanged;
if (hasGlobalPolicyLocked(policyDefinition)) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
policyChanged = localPolicyState.removePolicy(
enforcingAdmin,
globalPolicyState.getPoliciesSetByAdmins());
} else {
policyChanged = localPolicyState.removePolicy(enforcingAdmin);
}
if (policyChanged) {
onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
}
// For a removePolicy to be enforced, it means no current policy exists
boolean policyEnforced = localPolicyState.getCurrentResolvedPolicy() == null;
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
policyEnforced,
// TODO: we're always sending this for now, should properly handle errors.
REASON_CONFLICTING_ADMIN_POLICY,
userId);
if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) {
removeLocalPolicyStateLocked(policyDefinition, userId);
}
updateDeviceAdminServiceOnPolicyRemoveLocked(enforcingAdmin);
write();
}
}
/**
* Enforces the new policy and notifies relevant admins.
*/
private <V> void onLocalPolicyChanged(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
int userId) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
enforcePolicy(
policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
// Send policy updates to admins who've set it locally
sendPolicyChangedToAdmins(
localPolicyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
// This policy change is only relevant to a single user, not the global
// policy value,
userId);
// Send policy updates to admins who've set it globally
if (hasGlobalPolicyLocked(policyDefinition)) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
sendPolicyChangedToAdmins(
globalPolicyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
userId);
}
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
/**
* Set the policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
* Returns {@code true} if the enforced policy has been changed.
*
*/
<V> boolean setGlobalPolicy(
<V> void setGlobalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
@NonNull V value) {
@@ -153,77 +255,29 @@ final class DevicePolicyEngine {
Objects.requireNonNull(value);
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
sendPolicyChangedToAdmins(
policyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
TargetUser.GLOBAL_USER_ID);
onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
}
boolean wasAdminPolicyEnforced = Objects.equals(
policyState.getCurrentResolvedPolicy(), value);
boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
policyDefinition, enforcingAdmin, value);
boolean policyEnforcedGlobally = Objects.equals(
globalPolicyState.getCurrentResolvedPolicy(), value);
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
wasAdminPolicyEnforced,
policyEnforcedGlobally && policyEnforcedOnAllUsers,
// TODO: we're always sending this for now, should properly handle errors.
REASON_CONFLICTING_ADMIN_POLICY,
TargetUser.GLOBAL_USER_ID);
UserHandle.USER_ALL);
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
write();
return policyChanged;
}
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
/**
* Removes any previously set policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
* Returns {@code true} if the enforced policy has been changed.
*
*/
<V> boolean removeLocalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
int userId) {
Objects.requireNonNull(policyDefinition);
Objects.requireNonNull(enforcingAdmin);
synchronized (mLock) {
PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
enforcePolicy(
policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
sendPolicyChangedToAdmins(
policyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
userId == enforcingAdmin.getUserId()
? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
}
// for a remove policy to be enforced, it means no current policy exists
boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
wasAdminPolicyEnforced,
// TODO: we're always sending this for now, should properly handle errors.
REASON_CONFLICTING_ADMIN_POLICY,
userId == enforcingAdmin.getUserId()
? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
write();
return policyChanged;
}
}
@@ -231,10 +285,8 @@ final class DevicePolicyEngine {
/**
* Removes any previously set policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
* Returns {@code true} if the enforced policy has been changed.
*
*/
<V> boolean removeGlobalPolicy(
<V> void removeGlobalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin) {
@@ -246,61 +298,170 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
sendPolicyChangedToAdmins(
policyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
TargetUser.GLOBAL_USER_ID);
onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
}
// for a remove policy to be enforced, it means no current policy exists
boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
policyDefinition, enforcingAdmin, /* value= */ null);
// For a removePolicy to be enforced, it means no current policy exists
boolean policyEnforcedGlobally = policyState.getCurrentResolvedPolicy() == null;
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
wasAdminPolicyEnforced,
policyEnforcedGlobally && policyEnforcedOnAllUsers,
// TODO: we're always sending this for now, should properly handle errors.
REASON_CONFLICTING_ADMIN_POLICY,
TargetUser.GLOBAL_USER_ID);
UserHandle.USER_ALL);
if (policyState.getPoliciesSetByAdmins().isEmpty()) {
removeGlobalPolicyStateLocked(policyDefinition);
}
updateDeviceAdminServiceOnPolicyRemoveLocked(enforcingAdmin);
write();
return policyChanged;
}
}
/**
* Retrieves policies set by all admins for the provided {@code policyDefinition}.
*
* Enforces the new policy globally and notifies relevant admins.
*/
<V> PolicyState<V> getLocalPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
private <V> void onGlobalPolicyChanged(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
sendPolicyChangedToAdmins(
policyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
UserHandle.USER_ALL);
}
/**
* Tries to enforce the global policy locally on all users that have the same policy set
* locally, this is only applicable to policies that can be set locally or globally
* (e.g. setCameraDisabled, setScreenCaptureDisabled) rather than
* policies that are global by nature (e.g. setting Wifi enabled/disabled).
*
* <p> A {@code null} policy value means the policy was removed
*
* <p>Returns {@code true} if the policy is enforced successfully on all users.
*/
private <V> boolean enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
@Nullable V value) {
// Global only policies can't be applied locally, return early.
if (policyDefinition.isGlobalOnlyPolicy()) {
return true;
}
boolean isAdminPolicyEnforced = true;
for (int i = 0; i < mLocalPolicies.size(); i++) {
int userId = mLocalPolicies.keyAt(i);
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
continue;
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
boolean policyChanged = localPolicyState.resolvePolicy(
globalPolicyState.getPoliciesSetByAdmins());
if (policyChanged) {
enforcePolicy(
policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
sendPolicyChangedToAdmins(
localPolicyState.getPoliciesSetByAdmins().keySet(),
enforcingAdmin,
policyDefinition,
// Even though this is caused by a global policy change, admins who've set
// it locally should only care about the local user state.
userId);
}
isAdminPolicyEnforced &= Objects.equals(
value, localPolicyState.getCurrentResolvedPolicy());
}
return isAdminPolicyEnforced;
}
/**
* Retrieves the resolved policy for the provided {@code policyDefinition} and {@code userId}.
*/
@Nullable
<V> V getResolvedPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
Objects.requireNonNull(policyDefinition);
synchronized (mLock) {
return getLocalPolicyStateLocked(policyDefinition, userId);
if (hasLocalPolicyLocked(policyDefinition, userId)) {
return getLocalPolicyStateLocked(
policyDefinition, userId).getCurrentResolvedPolicy();
}
if (hasGlobalPolicyLocked(policyDefinition)) {
return getGlobalPolicyStateLocked(policyDefinition).getCurrentResolvedPolicy();
}
return null;
}
}
/**
* Retrieves policies set by all admins for the provided {@code policyDefinition}.
*
* Retrieves the policy set by the admin for the provided {@code policyDefinition} and
* {@code userId} if one was set, otherwise returns {@code null}.
*/
<V> PolicyState<V> getGlobalPolicy(@NonNull PolicyDefinition<V> policyDefinition) {
@Nullable
<V> V getLocalPolicySetByAdmin(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
int userId) {
Objects.requireNonNull(policyDefinition);
Objects.requireNonNull(enforcingAdmin);
synchronized (mLock) {
return getGlobalPolicyStateLocked(policyDefinition);
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
return null;
}
return getLocalPolicyStateLocked(policyDefinition, userId)
.getPoliciesSetByAdmins().get(enforcingAdmin);
}
}
private <V> boolean hasLocalPolicyLocked(PolicyDefinition<V> policyDefinition, int userId) {
if (policyDefinition.isGlobalOnlyPolicy()) {
return false;
}
if (!mLocalPolicies.contains(userId)) {
return false;
}
if (!mLocalPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
return false;
}
return !mLocalPolicies.get(userId).get(policyDefinition.getPolicyKey())
.getPoliciesSetByAdmins().isEmpty();
}
private <V> boolean hasGlobalPolicyLocked(PolicyDefinition<V> policyDefinition) {
if (policyDefinition.isLocalOnlyPolicy()) {
return false;
}
if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
return false;
}
return !mGlobalPolicies.get(policyDefinition.getPolicyKey()).getPoliciesSetByAdmins()
.isEmpty();
}
@NonNull
private <V> PolicyState<V> getLocalPolicyStateLocked(
PolicyDefinition<V> policyDefinition, int userId) {
if (policyDefinition.isGlobalOnlyPolicy()) {
throw new IllegalArgumentException("Can't set global policy "
+ policyDefinition.getPolicyKey() + " locally.");
throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only"
+ "policy.");
}
if (!mLocalPolicies.contains(userId)) {
@@ -313,11 +474,19 @@ final class DevicePolicyEngine {
return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
}
private <V> void removeLocalPolicyStateLocked(
PolicyDefinition<V> policyDefinition, int userId) {
if (!mLocalPolicies.contains(userId)) {
return;
}
mLocalPolicies.get(userId).remove(policyDefinition.getPolicyKey());
}
@NonNull
private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
if (policyDefinition.isLocalOnlyPolicy()) {
throw new IllegalArgumentException("Can't set local policy "
+ policyDefinition.getPolicyKey() + " globally.");
throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only"
+ "policy.");
}
if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
@@ -327,6 +496,10 @@ final class DevicePolicyEngine {
return getPolicyState(mGlobalPolicies, policyDefinition);
}
private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
mGlobalPolicies.remove(policyDefinition.getPolicyKey());
}
private static <V> PolicyState<V> getPolicyState(
Map<String, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
try {
@@ -344,14 +517,14 @@ final class DevicePolicyEngine {
private <V> void enforcePolicy(
PolicyDefinition<V> policyDefinition, @Nullable V policyValue, int userId) {
// TODO: null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
// null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
policyDefinition.enforcePolicy(policyValue, mContext, userId);
}
private <V> void sendPolicyResultToAdmin(
EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, boolean success,
int reason, int targetUserId) {
int reason, int userId) {
Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
intent.setPackage(admin.getPackageName());
@@ -367,12 +540,13 @@ final class DevicePolicyEngine {
Bundle extras = new Bundle();
extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
if (policyDefinition.getCallbackArgs() != null
&& !policyDefinition.getCallbackArgs().isEmpty()) {
extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
}
extras.putInt(
EXTRA_POLICY_TARGET_USER_ID,
getTargetUser(admin.getUserId(), userId));
extras.putInt(
EXTRA_POLICY_SET_RESULT_KEY,
success ? POLICY_SET_RESULT_SUCCESS : POLICY_SET_RESULT_FAILURE);
@@ -380,29 +554,30 @@ final class DevicePolicyEngine {
if (!success) {
extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
}
intent.putExtras(extras);
maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
}
// TODO(b/261430877): Finalise the decision on which admins to send the updates to.
private <V> void sendPolicyChangedToAdmins(
Set<EnforcingAdmin> admins, EnforcingAdmin callingAdmin,
Set<EnforcingAdmin> admins,
EnforcingAdmin callingAdmin,
PolicyDefinition<V> policyDefinition,
int targetUserId) {
int userId) {
for (EnforcingAdmin admin: admins) {
// We're sending a separate broadcast for the calling admin with the result.
if (admin.equals(callingAdmin)) {
continue;
}
maybeSendOnPolicyChanged(
admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, targetUserId);
admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, userId);
}
}
private <V> void maybeSendOnPolicyChanged(
EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int reason,
int targetUserId) {
int userId) {
Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_CHANGED);
intent.setPackage(admin.getPackageName());
@@ -418,14 +593,17 @@ final class DevicePolicyEngine {
Bundle extras = new Bundle();
extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
if (policyDefinition.getCallbackArgs() != null
&& !policyDefinition.getCallbackArgs().isEmpty()) {
extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
}
extras.putInt(
EXTRA_POLICY_TARGET_USER_ID,
getTargetUser(admin.getUserId(), userId));
extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
intent.putExtras(extras);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
maybeSendIntentToAdminReceivers(
intent, UserHandle.of(admin.getUserId()), receivers);
}
@@ -447,6 +625,184 @@ final class DevicePolicyEngine {
}
}
private int getTargetUser(int adminUserId, int targetUserId) {
if (targetUserId == UserHandle.USER_ALL) {
return TargetUser.GLOBAL_USER_ID;
}
if (adminUserId == targetUserId) {
return TargetUser.LOCAL_USER_ID;
}
if (getProfileParentId(adminUserId) == targetUserId) {
return TargetUser.PARENT_USER_ID;
}
return TargetUser.UNKNOWN_USER_ID;
}
private int getProfileParentId(int userId) {
return Binder.withCleanCallingIdentity(() -> {
UserInfo parentUser = mUserManager.getProfileParent(userId);
return parentUser != null ? parentUser.id : userId;
});
}
/**
* Starts/Stops the services that handle {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE}
* in the enforcing admins for the given {@code userId}.
*/
private void updateDeviceAdminsServicesForUser(
int userId, boolean enable, @NonNull String actionForLog) {
if (!enable) {
mDeviceAdminServiceController.stopServicesForUser(
userId, actionForLog);
} else {
for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) {
// DPCs are handled separately in DPMS, no need to reestablish the connection here.
if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
continue;
}
mDeviceAdminServiceController.startServiceForAdmin(
admin.getPackageName(), userId, actionForLog);
}
}
}
/**
* Handles internal state related to a user getting started.
*/
void handleStartUser(int userId) {
updateDeviceAdminsServicesForUser(
userId, /* enable= */ true, /* actionForLog= */ "start-user");
}
/**
* Handles internal state related to a user getting started.
*/
void handleUnlockUser(int userId) {
updateDeviceAdminsServicesForUser(
userId, /* enable= */ true, /* actionForLog= */ "unlock-user");
}
/**
* Handles internal state related to a user getting stopped.
*/
void handleStopUser(int userId) {
updateDeviceAdminsServicesForUser(
userId, /* enable= */ false, /* actionForLog= */ "stop-user");
}
/**
* Handles internal state related to packages getting updated.
*/
void handlePackageChanged(@Nullable String updatedPackage, int userId) {
if (updatedPackage == null) {
return;
}
updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
}
/**
* Reestablishes the service that handles
* {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package
* was updated, as a package update results in the persistent connection getting reset.
*/
private void updateDeviceAdminServiceOnPackageChanged(
@NonNull String updatedPackage, int userId) {
for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) {
// DPCs are handled separately in DPMS, no need to reestablish the connection here.
if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
continue;
}
if (updatedPackage.equals(admin.getPackageName())) {
mDeviceAdminServiceController.startServiceForAdmin(
updatedPackage, userId, /* actionForLog= */ "package-broadcast");
}
}
}
/**
* Called after an admin policy has been added to start binding to the admin if a connection
* was not already established.
*/
private void updateDeviceAdminServiceOnPolicyAddLocked(@NonNull EnforcingAdmin enforcingAdmin) {
int userId = enforcingAdmin.getUserId();
// A connection is established with DPCs as soon as they are provisioned, so no need to
// connect when a policy is set.
if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
return;
}
if (mEnforcingAdmins.contains(userId)
&& mEnforcingAdmins.get(userId).contains(enforcingAdmin)) {
return;
}
if (!mEnforcingAdmins.contains(enforcingAdmin.getUserId())) {
mEnforcingAdmins.put(enforcingAdmin.getUserId(), new HashSet<>());
}
mEnforcingAdmins.get(enforcingAdmin.getUserId()).add(enforcingAdmin);
mDeviceAdminServiceController.startServiceForAdmin(
enforcingAdmin.getPackageName(),
userId,
/* actionForLog= */ "policy-added");
}
/**
* Called after an admin policy has been removed to stop binding to the admin if they no longer
* have any policies set.
*/
private void updateDeviceAdminServiceOnPolicyRemoveLocked(
@NonNull EnforcingAdmin enforcingAdmin) {
// TODO(b/263364434): centralise handling in one place.
// DPCs rely on a constant connection being established as soon as they are provisioned,
// so we shouldn't disconnect it even if they no longer have policies set.
if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
return;
}
if (doesAdminHavePolicies(enforcingAdmin)) {
return;
}
int userId = enforcingAdmin.getUserId();
if (mEnforcingAdmins.contains(userId)) {
mEnforcingAdmins.get(userId).remove(enforcingAdmin);
if (mEnforcingAdmins.get(userId).isEmpty()) {
mEnforcingAdmins.remove(enforcingAdmin.getUserId());
}
}
mDeviceAdminServiceController.stopServiceForAdmin(
enforcingAdmin.getPackageName(),
userId,
/* actionForLog= */ "policy-removed");
}
private boolean doesAdminHavePolicies(@NonNull EnforcingAdmin enforcingAdmin) {
for (String policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
return true;
}
}
for (int i = 0; i < mLocalPolicies.size(); i++) {
for (String policy : mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()) {
PolicyState<?> policyState = mLocalPolicies.get(
mLocalPolicies.keyAt(i)).get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
return true;
}
}
}
return false;
}
@NonNull
private Set<EnforcingAdmin> getEnforcingAdminsForUser(int userId) {
return mEnforcingAdmins.contains(userId)
? mEnforcingAdmins.get(userId) : Collections.emptySet();
}
private void write() {
Log.d(TAG, "Writing device policies to file.");
new DevicePoliciesReaderWriter().writeToFileLocked();
@@ -474,6 +830,7 @@ final class DevicePolicyEngine {
private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
private static final String ATTR_USER_ID = "user-id";
private static final String ATTR_POLICY_ID = "policy-id";
@@ -516,6 +873,7 @@ final class DevicePolicyEngine {
void writeInner(TypedXmlSerializer serializer) throws IOException {
writeLocalPoliciesInner(serializer);
writeGlobalPoliciesInner(serializer);
writeEnforcingAdminsInner(serializer);
}
private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
@@ -556,6 +914,19 @@ final class DevicePolicyEngine {
}
}
private void writeEnforcingAdminsInner(TypedXmlSerializer serializer) throws IOException {
if (mEnforcingAdmins != null) {
for (int i = 0; i < mEnforcingAdmins.size(); i++) {
int userId = mEnforcingAdmins.keyAt(i);
for (EnforcingAdmin admin : mEnforcingAdmins.get(userId)) {
serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMINS_ENTRY);
admin.saveToXml(serializer);
serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMINS_ENTRY);
}
}
}
}
void readFromFileLocked() {
if (!mFile.exists()) {
Log.d(TAG, "" + mFile + " doesn't exist");
@@ -590,6 +961,9 @@ final class DevicePolicyEngine {
case TAG_GLOBAL_POLICY_ENTRY:
readGlobalPoliciesInner(parser);
break;
case TAG_ENFORCING_ADMINS_ENTRY:
readEnforcingAdminsInner(parser);
break;
default:
Log.e(TAG, "Unknown tag " + tag);
}
@@ -625,6 +999,15 @@ final class DevicePolicyEngine {
}
}
private void readEnforcingAdminsInner(TypedXmlPullParser parser)
throws XmlPullParserException {
EnforcingAdmin admin = EnforcingAdmin.readFromXml(parser);
if (!mEnforcingAdmins.contains(admin.getUserId())) {
mEnforcingAdmins.put(admin.getUserId(), new HashSet<>());
}
mEnforcingAdmins.get(admin.getUserId()).add(admin);
}
@Nullable
private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {

View File

@@ -1276,6 +1276,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
&& (owner.getPackageName().equals(packageName))) {
startOwnerService(userHandle, "package-broadcast");
}
if (isCoexistenceFlagEnabled()) {
mDevicePolicyEngine.handlePackageChanged(packageName, userHandle);
}
// Persist updates if the removed package was an admin or delegate.
if (removedAdmin || removedDelegate) {
@@ -1919,7 +1922,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mUserData = new SparseArray<>();
mOwners = makeOwners(injector, pathProvider);
mDevicePolicyEngine = new DevicePolicyEngine(mContext);
mDevicePolicyEngine = new DevicePolicyEngine(mContext, mDeviceAdminServiceController);
if (!mHasFeature) {
// Skip the rest of the initialization
@@ -3286,6 +3289,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
startOwnerService(userId, "start-user");
if (isCoexistenceFlagEnabled()) {
mDevicePolicyEngine.handleStartUser(userId);
}
}
void pushUserControlDisabledPackagesLocked(int userId) {
@@ -3300,7 +3306,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
? Collections.emptyList() : owner.protectedPackages;
? null : owner.protectedPackages;
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
targetUserId, protectedPackages));
@@ -3309,6 +3315,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
if (isCoexistenceFlagEnabled()) {
mDevicePolicyEngine.handleUnlockUser(userId);
}
}
@Override
@@ -3319,22 +3328,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
void handleStopUser(int userId) {
updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT));
stopOwnerService(userId, "stop-user");
mDeviceAdminServiceController.stopServicesForUser(userId, /* actionForLog= */ "stop-user");
if (isCoexistenceFlagEnabled()) {
mDevicePolicyEngine.handleStopUser(userId);
}
}
private void startOwnerService(int userId, String actionForLog) {
final ComponentName owner = getOwnerComponent(userId);
if (owner != null) {
mDeviceAdminServiceController.startServiceForOwner(
mDeviceAdminServiceController.startServiceForAdmin(
owner.getPackageName(), userId, actionForLog);
invalidateBinderCaches();
}
}
private void stopOwnerService(int userId, String actionForLog) {
mDeviceAdminServiceController.stopServiceForOwner(userId, actionForLog);
}
private void cleanUpOldUsers() {
// This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled
// before reboot
@@ -8608,7 +8616,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// TODO Send to system too?
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
});
mDeviceAdminServiceController.startServiceForOwner(
mDeviceAdminServiceController.startServiceForAdmin(
admin.getPackageName(), userId, "set-device-owner");
Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
@@ -8973,7 +8981,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
private void clearDeviceOwnerLocked(ActiveAdmin admin, int userId) {
mDeviceAdminServiceController.stopServiceForOwner(userId, "clear-device-owner");
String ownersPackage = mOwners.getDeviceOwnerPackageName();
if (ownersPackage != null) {
mDeviceAdminServiceController.stopServiceForAdmin(
ownersPackage, userId, "clear-device-owner");
}
if (admin != null) {
admin.disableCamera = false;
@@ -9085,7 +9097,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
userHandle);
});
mDeviceAdminServiceController.startServiceForOwner(
mDeviceAdminServiceController.startServiceForAdmin(
who.getPackageName(), userHandle, "set-profile-owner");
return true;
}
@@ -9134,7 +9146,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
public void clearProfileOwnerLocked(ActiveAdmin admin, int userId) {
mDeviceAdminServiceController.stopServiceForOwner(userId, "clear-profile-owner");
String ownersPackage = mOwners.getProfileOwnerPackage(userId);
if (ownersPackage != null) {
mDeviceAdminServiceController.stopServiceForAdmin(
ownersPackage, userId, "clear-profile-owner");
}
if (admin != null) {
admin.disableCamera = false;
@@ -12644,9 +12660,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
admin,
caller.getUserId());
} else {
LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.LOCK_TASK,
caller.getUserId()).getPoliciesSetByAdmins().get(admin);
admin,
caller.getUserId());
LockTaskPolicy policy;
if (currentPolicy == null) {
policy = new LockTaskPolicy(Set.of(packages));
@@ -12689,8 +12706,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (isCoexistenceEnabled(caller)) {
LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.LOCK_TASK, userHandle);
if (policy == null) {
return new String[0];
} else {
@@ -12719,8 +12736,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// TODO(b/260560985): This is not the right check, as the flag could be enabled but there
// could be an admin that hasn't targeted U.
if (isCoexistenceFlagEnabled()) {
LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.LOCK_TASK, userId);
if (policy == null) {
return false;
}
@@ -12754,9 +12771,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (isCoexistenceEnabled(caller)) {
EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle);
LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.LOCK_TASK,
caller.getUserId()).getPoliciesSetByAdmins().get(admin);
admin,
caller.getUserId());
if (currentPolicy == null) {
throw new IllegalArgumentException("Can't set a lock task flags without setting "
+ "lock task packages first.");
@@ -12793,8 +12811,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (isCoexistenceEnabled(caller)) {
LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.LOCK_TASK, userHandle);
if (policy == null) {
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
@@ -16751,7 +16769,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mOwners.transferProfileOwner(target, profileOwnerUserId);
Slogf.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
mOwners.writeProfileOwner(profileOwnerUserId);
mDeviceAdminServiceController.startServiceForOwner(
mDeviceAdminServiceController.startServiceForAdmin(
target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
}
@@ -16763,7 +16781,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mOwners.transferDeviceOwnership(target);
Slogf.i(LOG_TAG, "Device owner set: " + target + " on user " + userId);
mOwners.writeDeviceOwner();
mDeviceAdminServiceController.startServiceForOwner(
mDeviceAdminServiceController.startServiceForAdmin(
target.getPackageName(), userId, "transfer-device-owner");
}
@@ -17672,12 +17690,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
if (!Objects.equals(owner.protectedPackages, packages)) {
owner.protectedPackages = packages.isEmpty() ? null : packages;
saveSettingsLocked(caller.getUserId());
pushUserControlDisabledPackagesLocked(caller.getUserId());
if (isCoexistenceEnabled(caller)) {
Binder.withCleanCallingIdentity(() -> {
if (packages.isEmpty()) {
removeUserControlDisabledPackages(caller);
} else {
addUserControlDisabledPackages(caller, new HashSet<>(packages));
}
});
} else {
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
if (!Objects.equals(owner.protectedPackages, packages)) {
owner.protectedPackages = packages.isEmpty() ? null : packages;
saveSettingsLocked(caller.getUserId());
pushUserControlDisabledPackagesLocked(caller.getUserId());
}
}
}
@@ -17688,6 +17716,52 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.write();
}
private void addUserControlDisabledPackages(CallerIdentity caller, Set<String> packages) {
if (isCallerDeviceOwner(caller)) {
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
// TODO(b/260573124): add correct enforcing admin when permission changes are
// merged.
EnforcingAdmin.createEnterpriseEnforcingAdmin(
caller.getComponentName(), caller.getUserId()),
packages);
} else {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
// TODO(b/260573124): add correct enforcing admin when permission changes are
// merged.
EnforcingAdmin.createEnterpriseEnforcingAdmin(
caller.getComponentName(), caller.getUserId()),
packages,
caller.getUserId());
}
}
private void removeUserControlDisabledPackages(CallerIdentity caller) {
if (isCallerDeviceOwner(caller)) {
mDevicePolicyEngine.removeGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
// TODO(b/260573124): add correct enforcing admin when permission changes are
// merged.
EnforcingAdmin.createEnterpriseEnforcingAdmin(
caller.getComponentName(), caller.getUserId()));
} else {
mDevicePolicyEngine.removeLocalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
// TODO(b/260573124): add correct enforcing admin when permission changes are
// merged.
EnforcingAdmin.createEnterpriseEnforcingAdmin(
caller.getComponentName(), caller.getUserId()),
caller.getUserId());
}
}
private boolean isCallerDeviceOwner(CallerIdentity caller) {
synchronized (getLockObject()) {
return getDeviceOwnerUserIdUncheckedLocked() == caller.getUserId();
}
}
@Override
public List<String> getUserControlDisabledPackages(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
@@ -17696,10 +17770,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller)
|| isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin deviceOwner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
return deviceOwner.protectedPackages != null
? deviceOwner.protectedPackages : Collections.emptyList();
if (isCoexistenceEnabled(caller)) {
// This retrieves the policy for the calling user only, DOs for example can't know
// what's enforced globally or on another user.
Set<String> packages = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
caller.getUserId());
return packages == null ? Collections.emptyList() : packages.stream().toList();
} else {
synchronized (getLockObject()) {
ActiveAdmin deviceOwner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
return deviceOwner.protectedPackages != null
? deviceOwner.protectedPackages : Collections.emptyList();
}
}
}

View File

@@ -227,4 +227,11 @@ final class EnforcingAdmin {
return new EnforcingAdmin(packageName, componentName, authorities, userId);
}
}
@Override
public String toString() {
return "EnforcingAdmin { mPackageName= " + mPackageName + ", mComponentName= "
+ mComponentName + ", mAuthorities= " + mAuthorities + ", mUserId= "
+ mUserId + ", mIsRoleAuthority= " + mIsRoleAuthority + " }";
}
}

View File

@@ -36,4 +36,9 @@ final class IntegerUnion extends ResolutionMechanism<Integer> {
}
return unionOfPolicies;
}
@Override
public String toString() {
return "IntegerUnion {}";
}
}

View File

@@ -44,4 +44,9 @@ final class MostRestrictive<V> extends ResolutionMechanism<V> {
Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
return policy.getValue();
}
@Override
public String toString() {
return "MostRestrictive { mMostToLeastRestrictive= " + mMostToLeastRestrictive + " }";
}
}

View File

@@ -33,6 +33,7 @@ import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class PolicyDefinition<V> {
private static final int POLICY_FLAG_NONE = 0;
@@ -98,10 +99,18 @@ final class PolicyDefinition<V> {
PolicyEnforcerCallbacks.setLockTask(value, context, userId),
new LockTaskPolicy.LockTaskPolicySerializer());
private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES = new PolicyDefinition<>(
DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES,
new SetUnion<>(),
(Set<String> value, Context context, Integer userId, Bundle args) ->
PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
new SetPolicySerializer<>());
private static final Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS,
DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK
DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK,
DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES, USER_CONTROLLED_DISABLED_PACKAGES
);
@@ -261,4 +270,11 @@ final class PolicyDefinition<V> {
V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
return mPolicySerializer.readFromXml(parser, attributeName);
}
@Override
public String toString() {
return "PolicyDefinition { mPolicyKey= " + mPolicyKey + ", mPolicyDefinitionKey= "
+ mPolicyDefinitionKey + ", mResolutionMechanism= " + mResolutionMechanism
+ ", mCallbackArgs= " + mCallbackArgs + ", mPolicyFlags= " + mPolicyFlags + " }";
}
}

View File

@@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.admin.PolicyUpdatesReceiver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle;
@@ -29,11 +30,13 @@ import android.permission.AdminPermissionControlParams;
import android.permission.PermissionControllerManager;
import android.provider.Settings;
import com.android.server.LocalServices;
import com.android.server.utils.Slogf;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -137,4 +140,14 @@ final class PolicyEnforcerCallbacks {
return mValue.get();
}
}
static boolean setUserControlDisabledPackages(
@Nullable Set<String> packages, int userId) {
Binder.withCleanCallingIdentity(() ->
LocalServices.getService(PackageManagerInternal.class)
.setOwnerProtectedPackages(
userId,
packages == null ? null : packages.stream().toList()));
return true;
}
}

View File

@@ -62,12 +62,32 @@ final class PolicyState<V> {
/**
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
boolean addPolicy(@NonNull EnforcingAdmin admin, @NonNull V policy) {
mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy));
return resolvePolicy();
}
/**
* Takes into account global policies set by the admin when resolving the policy, this is only
* relevant to local policies that can be applied globally as well.
*
* <p> Note that local policies set by an admin takes precedence over global policies set by the
* same admin.
*
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean addPolicy(
@NonNull EnforcingAdmin admin, @NonNull V policy,
LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy));
return resolvePolicy(globalPoliciesSetByAdmins);
}
/**
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean removePolicy(@NonNull EnforcingAdmin admin) {
Objects.requireNonNull(admin);
@@ -78,8 +98,52 @@ final class PolicyState<V> {
return resolvePolicy();
}
/**
* Takes into account global policies set by the admin when resolving the policy, this is only
* relevant to local policies that can be applied globally as well.
*
* <p> Note that local policies set by an admin takes precedence over global policies set by the
* same admin.
*
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean removePolicy(
@NonNull EnforcingAdmin admin,
LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
Objects.requireNonNull(admin);
if (mPoliciesSetByAdmins.remove(admin) == null) {
return false;
}
return resolvePolicy(globalPoliciesSetByAdmins);
}
/**
* Takes into account global policies set by the admin when resolving the policy, this is only
* relevant to local policies that can be applied globally as well.
*
* <p> Note that local policies set by an admin takes precedence over global policies set by the
* same admin.
*
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
// Add global policies first then override with local policies for the same admin.
LinkedHashMap<EnforcingAdmin, V> mergedPolicies =
new LinkedHashMap<>(globalPoliciesSetByAdmins);
mergedPolicies.putAll(mPoliciesSetByAdmins);
V resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies);
boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
mCurrentResolvedPolicy = resolvedPolicy;
return policyChanged;
}
@NonNull
LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
return mPoliciesSetByAdmins;
return new LinkedHashMap<>(mPoliciesSetByAdmins);
}
private boolean resolvePolicy() {
@@ -95,6 +159,13 @@ final class PolicyState<V> {
return mCurrentResolvedPolicy;
}
@Override
public String toString() {
return "PolicyState { mPolicyDefinition= " + mPolicyDefinition + ", mPoliciesSetByAdmins= "
+ mPoliciesSetByAdmins + ", mCurrentResolvedPolicy= " + mCurrentResolvedPolicy
+ " }";
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
mPolicyDefinition.saveToXml(serializer);

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;
// TODO(scottjonathan): Replace with actual implementation
final class SetPolicySerializer<V> extends PolicySerializer<Set<V>> {
@Override
void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Set<V> value)
throws IOException {
Objects.requireNonNull(value);
}
@Nullable
@Override
Set<V> readFromXml(TypedXmlPullParser parser, String attributeName) {
return null;
}
}

View File

@@ -37,4 +37,9 @@ final class SetUnion<V> extends ResolutionMechanism<Set<V>> {
}
return unionOfPolicies;
}
@Override
public String toString() {
return "SetUnion {}";
}
}

View File

@@ -49,4 +49,10 @@ final class TopPriority<V> extends ResolutionMechanism<V> {
Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
return policy.getValue();
}
@Override
public String toString() {
return "TopPriority { mHighestToLowestPriorityAuthorities= "
+ mHighestToLowestPriorityAuthorities + " }";
}
}