diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 334750e39f1c3..5f08a0c529c57 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -983,10 +983,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { - Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker.getClass()); + Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker); mSafetyChecker = safetyChecker; } + /** + * Used by {@link OneTimeSafetyChecker} only. + */ + DevicePolicySafetyChecker getDevicePolicySafetyChecker() { + return mSafetyChecker; + } + /** * Checks if it's safe to execute the given {@code operation}. * @@ -994,6 +1001,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) { if (!canExecute(operation)) { + if (mSafetyChecker == null) { + // Happens on CTS after it's set just once (by OneTimeSafetyChecker) + throw new UnsafeStateException(operation); + } + // Let mSafetyChecker customize it (for example, by explaining how to retry) throw mSafetyChecker.newUnsafeStateException(operation); } } @@ -1005,6 +1017,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation); } + /** + * Used by {@code cmd device_policy} to set the result of the next safety operation check. + */ + void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); + Slog.i(LOG_TAG, "setNextOperationSafety(" + DevicePolicyManager.operationToString(operation) + + ", " + safe + ")"); + mSafetyChecker = new OneTimeSafetyChecker(this, operation, safe); + } + /** * Unit test will subclass it to inject mocks. */ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index 0b0aee9afa6df..22866b45911d3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -24,6 +24,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_SET_SAFE_OPERATION = "set-operation-safe"; private final DevicePolicyManagerService mService; @@ -48,6 +49,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { switch (cmd) { case CMD_IS_SAFE_OPERATION: return runIsSafeOperation(pw); + case CMD_SET_SAFE_OPERATION: + return runSetSafeOperation(pw); default: return onInvalidCommand(pw, cmd); } @@ -70,6 +73,9 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { 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_SET_SAFE_OPERATION); + pw.printf(" Emulates the result of the next call to check if the given operation is safe" + + " \n\n"); } private int runIsSafeOperation(PrintWriter pw) { @@ -79,4 +85,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { safe ? "SAFE" : "UNSAFE"); return 0; } + + private int runSetSafeOperation(PrintWriter pw) { + int operation = Integer.parseInt(getNextArgRequired()); + boolean safe = getNextArg().equals("true"); + mService.setNextOperationSafety(operation, safe); + pw.printf("Next call to check operation %s will return %s\n", + DevicePolicyManager.operationToString(operation), safe ? "SAFE" : "UNSAFE"); + return 0; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java new file mode 100644 index 0000000000000..b0f8bfb713ab9 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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 static android.app.admin.DevicePolicyManager.operationToString; + +import android.app.admin.DevicePolicyManager.DevicePolicyOperation; +import android.app.admin.DevicePolicySafetyChecker; +import android.util.Slog; + +import java.util.Objects; + +//TODO(b/172376923): add unit tests + +/** + * {@code DevicePolicySafetyChecker} implementation that overrides the real checker for just + * one command. + * + *

Used only for debugging and CTS tests. + */ +final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { + + private static final String TAG = OneTimeSafetyChecker.class.getSimpleName(); + + private final DevicePolicyManagerService mService; + private final DevicePolicySafetyChecker mRealSafetyChecker; + private final @DevicePolicyOperation int mOperation; + private final boolean mSafe; + + OneTimeSafetyChecker(DevicePolicyManagerService service, + @DevicePolicyOperation int operation, boolean safe) { + mService = Objects.requireNonNull(service); + mOperation = operation; + mSafe = safe; + mRealSafetyChecker = service.getDevicePolicySafetyChecker(); + Slog.i(TAG, "Saving real DevicePolicySafetyChecker as " + mRealSafetyChecker); + } + + @Override + public boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) { + String name = operationToString(operation); + boolean safe = true; + if (operation == mOperation) { + safe = mSafe; + } else { + Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name + + ", should be " + operationToString(mOperation)); + } + Slog.i(TAG, "isDevicePolicyOperationSafe(" + name + "): returning " + safe + + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker); + mService.setDevicePolicySafetyChecker(mRealSafetyChecker); + return safe; + } +}