diff --git a/core/api/current.txt b/core/api/current.txt index 2531d818e5288..ac88e5bd1dc0d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6896,6 +6896,7 @@ package android.app.admin { method public void onLockTaskModeEntering(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String); method public void onLockTaskModeExiting(@NonNull android.content.Context, @NonNull android.content.Intent); method public void onNetworkLogsAvailable(@NonNull android.content.Context, @NonNull android.content.Intent, long, @IntRange(from=1) int); + method public void onOperationSafetyStateChanged(@NonNull android.content.Context, int, boolean); method @Deprecated public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent); method public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull android.os.UserHandle); method @Deprecated public void onPasswordExpiring(@NonNull android.content.Context, @NonNull android.content.Intent); @@ -7072,6 +7073,7 @@ package android.app.admin { method public boolean isProfileOwnerApp(String); method public boolean isProvisioningAllowed(@NonNull String); method public boolean isResetPasswordTokenActive(android.content.ComponentName); + method public boolean isSafeOperation(int); method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName); method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String); method public boolean isUniqueDeviceAttestationSupported(); @@ -7300,6 +7302,7 @@ package android.app.admin { field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1 field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2 field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning"; + field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1 field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000 field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000 field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000 @@ -7336,7 +7339,6 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 - field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1 field public static final int WIPE_EUICC = 4; // 0x4 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 @@ -7490,7 +7492,7 @@ package android.app.admin { public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable { method public int describeContents(); - method public int getReason(); + method @NonNull public java.util.List getReasons(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 909c8e4f1adc2..694507db6fe7f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -391,11 +391,11 @@ package android.app.admin { method public java.util.List getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); + method @NonNull public static String operationSafetyReasonToString(int); method @NonNull public static String operationToString(int); method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); - method @NonNull public static String unsafeOperationReasonToString(int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb @@ -425,6 +425,7 @@ package android.app.admin { field public static final int OPERATION_REMOVE_KEY_PAIR = 28; // 0x1c field public static final int OPERATION_REMOVE_USER = 6; // 0x6 field public static final int OPERATION_REQUEST_BUGREPORT = 29; // 0x1d + field public static final int OPERATION_SAFETY_REASON_NONE = -1; // 0xffffffff field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10 @@ -460,7 +461,6 @@ package android.app.admin { field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4 field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7 field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5 - field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff } public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index d175a66e90ea3..4dbff0c064237 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -16,6 +16,8 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.OperationSafetyReason; + import android.accounts.AccountManager; import android.annotation.BroadcastBehavior; import android.annotation.IntDef; @@ -35,6 +37,7 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.security.KeyChain; +import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -72,8 +75,8 @@ import java.lang.annotation.RetentionPolicy; * */ public class DeviceAdminReceiver extends BroadcastReceiver { - private static String TAG = "DevicePolicy"; - private static boolean localLOGV = false; + private static final String TAG = "DevicePolicy"; + private static final boolean LOCAL_LOGV = false; /** * This is the primary action that a device administrator must implement to be @@ -509,6 +512,36 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; + /** + * Broadcast action: notify the admin that the state of operations that can be unsafe because + * of a given reason (specified by the {@link #EXTRA_OPERATION_SAFETY_REASON} {@code int} extra) + * has changed (the new value is specified by the {@link #EXTRA_OPERATION_SAFETY_STATE} + * {@code boolean} extra). + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED = + "android.app.action.OPERATION_SAFETY_STATE_CHANGED"; + + /** + * An {@code int} extra specifying an {@link OperationSafetyReason}. + * + * @hide + */ + public static final String EXTRA_OPERATION_SAFETY_REASON = + "android.app.extra.OPERATION_SAFETY_REASON"; + + /** + * An {@code boolean} extra specifying whether an operation will fail due to a + * {@link OperationSafetyReason}. {@code true} means operations that rely on that reason are + * safe, while {@code false} means they're unsafe. + * + * @hide + */ + public static final String EXTRA_OPERATION_SAFETY_STATE = + "android.app.extra.OPERATION_SAFETY_STATE"; + private DevicePolicyManager mManager; private ComponentName mWho; @@ -1017,6 +1050,51 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @NonNull UserHandle user) { } + /** + * Called to notify the state of operations that can be unsafe to execute has changed. + * + *

Note:/b> notice that the operation safety state might change between the time this + * callback is received and the operation's method on {@link DevicePolicyManager} is called, so + * calls to the latter could still throw a {@link UnsafeStateException} even when this method + * is called with {@code isSafe} as {@code true} + * + * @param context the running context as per {@link #onReceive} + * @param reason the reason an operation could be unsafe. + * @param isSafe whether the operation is safe to be executed. + */ + public void onOperationSafetyStateChanged(@NonNull Context context, + @OperationSafetyReason int reason, boolean isSafe) { + if (LOCAL_LOGV) { + Log.v(TAG, String.format("onOperationSafetyStateChanged(): %s=%b", + DevicePolicyManager.operationSafetyReasonToString(reason), isSafe)); + } + } + + private void onOperationSafetyStateChanged(Context context, Intent intent) { + if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON) + || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) { + return; + } + + int reason = intent.getIntExtra(EXTRA_OPERATION_SAFETY_REASON, + DevicePolicyManager.OPERATION_SAFETY_REASON_NONE); + if (!DevicePolicyManager.isValidOperationSafetyReason(reason)) { + Log.wtf(TAG, "Received invalid reason on " + intent.getAction() + ": " + reason); + return; + } + boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE, + /* defaultValue=*/ false); + + onOperationSafetyStateChanged(context, reason, isSafe); + } + + private boolean hasRequiredExtra(Intent intent, String extra) { + if (intent.hasExtra(extra)) return true; + + Log.wtf(TAG, "Missing '" + extra + "' on intent " + intent); + return false; + } + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the @@ -1025,6 +1103,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { String action = intent.getAction(); + if (LOCAL_LOGV) { + Log.v(TAG, "onReceive(): received " + action + " on user " + context.getUserId()); + } if (ACTION_PASSWORD_CHANGED.equals(action)) { onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); @@ -1092,6 +1173,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { onTransferAffiliatedProfileOwnershipComplete(context, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) { + onOperationSafetyStateChanged(context, intent); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ff41d1c84e915..e95ab17da09ba 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2922,33 +2922,61 @@ public class DevicePolicyManager { return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation); } - private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_"; + private static final String PREFIX_OPERATION_SAFETY_REASON = "OPERATION_SAFETY_REASON_"; /** @hide */ - @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = { - UNSAFE_OPERATION_REASON_NONE, - UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION + @IntDef(prefix = PREFIX_OPERATION_SAFETY_REASON, value = { + OPERATION_SAFETY_REASON_NONE, + OPERATION_SAFETY_REASON_DRIVING_DISTRACTION }) @Retention(RetentionPolicy.SOURCE) - public static @interface UnsafeOperationReason { + public static @interface OperationSafetyReason { } /** @hide */ @TestApi - public static final int UNSAFE_OPERATION_REASON_NONE = -1; + public static final int OPERATION_SAFETY_REASON_NONE = -1; /** * Indicates that a {@link UnsafeStateException} was thrown because the operation would distract * the driver of the vehicle. */ - public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; + public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; /** @hide */ @NonNull @TestApi - public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) { + public static String operationSafetyReasonToString(@OperationSafetyReason int reason) { return DebugUtils.constantToString(DevicePolicyManager.class, - PREFIX_UNSAFE_OPERATION_REASON, reason); + PREFIX_OPERATION_SAFETY_REASON, reason); + } + + /** @hide */ + public static boolean isValidOperationSafetyReason(@OperationSafetyReason int reason) { + return reason == OPERATION_SAFETY_REASON_DRIVING_DISTRACTION; + } + + /** + * Checks if it's safe to run operations that can be affected by the given {@code reason}. + * + *

Note:/b> notice that the operation safety state might change between the time this + * method returns and the operation's method is called, so calls to the latter could still throw + * a {@link UnsafeStateException} even when this method returns {@code true}. + * + * @param reason currently, only supported reason is + * {@link #OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}. + * + * @return whether it's safe to run operations that can be affected by the given {@code reason}. + */ + // TODO(b/173541467): should it throw SecurityException if caller is not admin? + public boolean isSafeOperation(@OperationSafetyReason int reason) { + if (mService == null) return false; + + try { + return mService.isSafeOperation(reason); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @hide */ @@ -13157,7 +13185,7 @@ public class DevicePolicyManager { @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { if (mService != null) { try { mService.setNextOperationSafety(operation, reason); diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index a0d2977cf09a9..67f5c366bc141 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.content.ComponentName; import android.content.Intent; import android.os.UserHandle; @@ -255,4 +256,14 @@ public abstract class DevicePolicyManagerInternal { * {@link #supportsResetOp(int)} is true. */ public abstract void resetOp(int op, String packageName, @UserIdInt int userId); + + /** + * Notifies the system that an unsafe operation reason has changed. + * + * @throws IllegalArgumentException if {@code checker} is not the same as set on + * {@code DevicePolicyManagerService}. + */ + public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, + @OperationSafetyReason int reason, boolean isSafe); + } diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java index 6c6f2aa15ab72..17b74b1ab4001 100644 --- a/core/java/android/app/admin/DevicePolicySafetyChecker.java +++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java @@ -17,7 +17,7 @@ package android.app.admin; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import com.android.internal.os.IResultReceiver; @@ -31,15 +31,20 @@ public interface DevicePolicySafetyChecker { /** * Returns whether the given {@code operation} can be safely executed at the moment. */ - @UnsafeOperationReason + @OperationSafetyReason int getUnsafeOperationReason(@DevicePolicyOperation int operation); + /** + * Return whether it's safe to run operations that can be affected by the given {@code reason}. + */ + boolean isSafeOperation(@OperationSafetyReason int reason); + /** * Returns a new exception for when the given {@code operation} cannot be safely executed. */ @NonNull default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { return new UnsafeStateException(operation, reason); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 89f30cc821ab3..032cf2483cd3b 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -492,6 +492,7 @@ interface IDevicePolicyManager { boolean canProfileOwnerResetPasswordWhenLocked(int userId); void setNextOperationSafety(int operation, int reason); + boolean isSafeOperation(int reason); String getEnrollmentSpecificId(String callerPackage); void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId); diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java index 56eeb06e8cc09..f1f652601c66a 100644 --- a/core/java/android/app/admin/UnsafeStateException.java +++ b/core/java/android/app/admin/UnsafeStateException.java @@ -15,18 +15,20 @@ */ package android.app.admin; -import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION; -import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString; +import static android.app.admin.DevicePolicyManager.isValidOperationSafetyReason; import android.annotation.NonNull; import android.annotation.TestApi; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.Preconditions; +import java.util.Arrays; +import java.util.List; + /** * Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it * was not safe to be executed at that moment. @@ -39,17 +41,15 @@ import com.android.internal.util.Preconditions; public final class UnsafeStateException extends IllegalStateException implements Parcelable { private final @DevicePolicyOperation int mOperation; - private final @UnsafeOperationReason int mReason; + private final @OperationSafetyReason int mReason; /** @hide */ @TestApi public UnsafeStateException(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { super(); - Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION, - "invalid reason %d, must be %d (%s)", reason, - UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION, - unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION)); + Preconditions.checkArgument(isValidOperationSafetyReason(reason), "invalid reason %d", + reason); mOperation = operation; mReason = reason; } @@ -61,19 +61,20 @@ public final class UnsafeStateException extends IllegalStateException implements } /** - * Gets the reason the operation is unsafe. + * Gets the reasons the operation is unsafe. * * @return currently, only valid reason is - * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}. + * {@link android.app.admin.DevicePolicyManager#OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}. */ - public @UnsafeOperationReason int getReason() { - return mReason; + @NonNull + public List getReasons() { + return Arrays.asList(mReason); } /** @hide */ @Override public String getMessage() { - return DevicePolicyManager.unsafeOperationReasonToString(mReason); + return DevicePolicyManager.operationSafetyReasonToString(mReason); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 07eb7bf8f9a07..b063e6727f5b9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -62,6 +62,7 @@ import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; +import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; @@ -92,7 +93,6 @@ import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_REMOVE_N import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED; -import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -157,9 +157,9 @@ import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.DeviceStateCache; @@ -1101,7 +1101,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) { int reason = getUnsafeOperationReason(operation); - if (reason == UNSAFE_OPERATION_REASON_NONE) return; + if (reason == OPERATION_SAFETY_REASON_NONE) return; if (mSafetyChecker == null) { // Happens on CTS after it's set just once (by OneTimeSafetyChecker) @@ -1114,23 +1114,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Returns whether it's safe to execute the given {@code operation}, and why. */ - @UnsafeOperationReason + @OperationSafetyReason int getUnsafeOperationReason(@DevicePolicyOperation int operation) { - return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE + return mSafetyChecker == null ? OPERATION_SAFETY_REASON_NONE : mSafetyChecker.getUnsafeOperationReason(operation); } @Override public void setNextOperationSafety(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)", DevicePolicyManager.operationToString(operation), - DevicePolicyManager.unsafeOperationReasonToString(reason))); + DevicePolicyManager.operationSafetyReasonToString(reason))); mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason); } + @Override + public boolean isSafeOperation(@OperationSafetyReason int reason) { + return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason); + } + // Used by DevicePolicyManagerServiceShellCommand List listAllOwners() { Preconditions.checkCallAuthorization( @@ -7522,19 +7527,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent); } - private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) { - sendActiveAdminCommand(action, extras, userHandle, - mOwners.getProfileOwnerComponent(userHandle)); + private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) { + sendActiveAdminCommand(action, extras, userId, + mOwners.getProfileOwnerComponent(userId)); } private void sendActiveAdminCommand(String action, Bundle extras, - int userHandle, ComponentName receiverComponent) { + @UserIdInt int userId, ComponentName receiverComponent) { + if (VERBOSE_LOG) { + Slog.v(LOG_TAG, "sending intent " + action + " to " + + receiverComponent.flattenToShortString() + " on user " + userId); + } final Intent intent = new Intent(action); intent.setComponent(receiverComponent); if (extras != null) { intent.putExtras(extras); } - mContext.sendBroadcastAsUser(intent, UserHandle.of(userHandle)); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } private void sendOwnerChangedBroadcast(String broadcast, int userId) { @@ -12224,6 +12233,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { packageName, findInteractAcrossProfilesResetMode(packageName), userId); } + @Override + public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason, + boolean isSafe) { + // TODO(b/178494483): use EventLog instead + // TODO(b/178494483): log metrics? + if (VERBOSE_LOG) { + Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b", + DevicePolicyManager.operationSafetyReasonToString(reason), isSafe)); + } + + Preconditions.checkArgument(mSafetyChecker == checker, + "invalid checker: should be %s, was %s", mSafetyChecker, checker); + + Bundle extras = new Bundle(); + extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason); + extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe); + + // TODO(b/178494483): add CTS test + sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, + extras); + for (int profileOwnerId : mOwners.getProfileOwnerKeys()) { + sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, + extras, profileOwnerId); + } + } + private @Mode int findInteractAcrossProfilesResetMode(String packageName) { return getDefaultCrossProfilePackages().contains(packageName) ? AppOpsManager.MODE_ALLOWED diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index 222c987d906fa..5484a148b0b67 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -27,6 +27,7 @@ import java.util.Objects; final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe"; + private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason"; private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe"; private static final String CMD_LIST_OWNERS = "list-owners"; @@ -53,6 +54,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { switch (cmd) { case CMD_IS_SAFE_OPERATION: return runIsSafeOperation(pw); + case CMD_IS_SAFE_OPERATION_BY_REASON: + return runIsSafeOperationByReason(pw); case CMD_SET_SAFE_OPERATION: return runSetSafeOperation(pw); case CMD_LIST_OWNERS: @@ -73,12 +76,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { return -1; } - private void showHelp(PrintWriter pw) { pw.printf(" help\n"); pw.printf(" Prints this help text.\n\n"); pw.printf(" %s \n", CMD_IS_SAFE_OPERATION); pw.printf(" Checks if the give operation is safe \n\n"); + pw.printf(" %s \n", CMD_IS_SAFE_OPERATION_BY_REASON); + pw.printf(" Checks if the operations are safe for the given reason\n\n"); pw.printf(" %s \n", CMD_SET_SAFE_OPERATION); pw.printf(" Emulates the result of the next call to check if the given operation is safe" + " \n\n"); @@ -89,10 +93,19 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private int runIsSafeOperation(PrintWriter pw) { int operation = Integer.parseInt(getNextArgRequired()); int reason = mService.getUnsafeOperationReason(operation); - boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE; + boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; pw.printf("Operation %s is %b. Reason: %s\n", DevicePolicyManager.operationToString(operation), safe, - DevicePolicyManager.unsafeOperationReasonToString(reason)); + DevicePolicyManager.operationSafetyReasonToString(reason)); + return 0; + } + + private int runIsSafeOperationByReason(PrintWriter pw) { + int reason = Integer.parseInt(getNextArgRequired()); + boolean safe = mService.isSafeOperation(reason); + pw.printf("Operations affected by %s are %s\n", + DevicePolicyManager.operationSafetyReasonToString(reason), + (safe ? "SAFE" : "UNSAFE")); return 0; } @@ -102,7 +115,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { mService.setNextOperationSafety(operation, reason); pw.printf("Next call to check operation %s will return %s\n", DevicePolicyManager.operationToString(operation), - DevicePolicyManager.unsafeOperationReasonToString(reason)); + DevicePolicyManager.operationSafetyReasonToString(reason)); return 0; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index 883f95d930a08..7de1bd50a9eb0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -15,16 +15,18 @@ */ package com.android.server.devicepolicy; -import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE; +import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; +import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString; import static android.app.admin.DevicePolicyManager.operationToString; -import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; import android.util.Slog; import com.android.internal.os.IResultReceiver; +import com.android.server.LocalServices; import java.util.Objects; @@ -43,10 +45,10 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { private final DevicePolicyManagerService mService; private final DevicePolicySafetyChecker mRealSafetyChecker; private final @DevicePolicyOperation int mOperation; - private final @UnsafeOperationReason int mReason; + private final @OperationSafetyReason int mReason; OneTimeSafetyChecker(DevicePolicyManagerService service, - @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) { + @DevicePolicyOperation int operation, @OperationSafetyReason int reason) { mService = Objects.requireNonNull(service); mOperation = operation; mReason = reason; @@ -55,23 +57,41 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { } @Override - @UnsafeOperationReason + @OperationSafetyReason public int getUnsafeOperationReason(@DevicePolicyOperation int operation) { String name = operationToString(operation); - int reason = UNSAFE_OPERATION_REASON_NONE; + Slog.i(TAG, "getUnsafeOperationReason(" + name + ")"); + int reason = OPERATION_SAFETY_REASON_NONE; if (operation == mOperation) { reason = mReason; } else { Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name + ", should be " + operationToString(mOperation)); } - Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning " - + unsafeOperationReasonToString(reason) + String reasonName = operationSafetyReasonToString(reason); + DevicePolicyManagerInternal dpmi = LocalServices + .getService(DevicePolicyManagerInternal.class); + + Slog.i(TAG, "notifying " + reasonName + " is active"); + dpmi.notifyUnsafeOperationStateChanged(this, reason, true); + + Slog.i(TAG, "notifying " + reasonName + " is inactive"); + dpmi.notifyUnsafeOperationStateChanged(this, reason, false); + + Slog.i(TAG, "returning " + reasonName + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker); mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker); return reason; } + @Override + public boolean isSafeOperation(@OperationSafetyReason int reason) { + boolean safe = mReason != reason; + Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe); + + return safe; + } + @Override public void onFactoryReset(IResultReceiver callback) { throw new UnsupportedOperationException();