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;
+    }
+}