New cmd to temporarily set safety operations.
Test: adb shell cmd device_policy set-operation-safe 1 false
Test: adb shell cmd device_policy is-operation-safe 1
Bug: 172376923
Change-Id: I877cd948b51032d0581cd41512a27a9a3a98a333
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 334750e..5f08a0c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -983,17 +983,29 @@
@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}.
*
* @throws UnsafeStateException if it's not safe to execute the operation.
*/
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);
}
}
@@ -1006,6 +1018,17 @@
}
/**
+ * 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.
*/
@VisibleForTesting
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 0b0aee9..22866b4 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 @@
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 @@
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 @@
pw.printf(" Prints this help text.\n\n");
pw.printf(" %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
pw.printf(" Checks if the give operation is safe \n\n");
+ pw.printf(" %s <OPERATION_ID> <true|false>\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 @@
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 0000000..b0f8bfb
--- /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.
+ *
+ * <p>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;
+ }
+}