Add DeviceStateManagerService shell commands to get and override state.

This also introduces the CONTROL_DEVICE_STATE permission to protect
against setting the device state. The permission is granted to the shell
to allow overriding via ADB.

Two ADB commands are introduced with this change:

-> adb shell cmd device_state print-states
will print the list of states supported by the device
Ex:
$ adb shell cmd device_state print-states
[ 0, 1, 2 ]

-> adb shell cmd device_state state [reset|OVERRIDE_DEVICE_STATE]
will print the current device state or override the current device
state with the supplied override state.
Ex:
$ adb shell cmd device_state 0

Ex:
$ adb shell cmd device_state state
Device state: 0
----------------------
Base state: 2
Override state: 0

Bug: 159401801
Test: atest DeviceStateManagerServiceTest
Test: adb shell cmd device_state print-states
Test: adb shell cmd device_state state

Change-Id: I8a74f1f2bf8189523bdae861fb38f86988c21b2a
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f2af514..85eaa65 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5151,6 +5151,12 @@
     <permission android:name="android.permission.INPUT_CONSUMER"
                 android:protectionLevel="signature" />
 
+    <!-- @hide Allows an application to control the system's device state managed by the
+         {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
+         devices this would grant access to toggle between the folded and unfolded states. -->
+    <permission android:name="android.permission.CONTROL_DEVICE_STATE"
+                android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 5f018a0..9cd69cc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -338,6 +338,9 @@
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
+    <!-- Allows overriding the system's device state from the shell -->
+    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 7e3c1ab..1a28717 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,10 +17,14 @@
 package com.android.server.devicestate;
 
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.devicestate.IDeviceStateManager;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.util.IntArray;
 import android.util.Slog;
 
@@ -29,6 +33,7 @@
 import com.android.server.SystemService;
 import com.android.server.policy.DeviceStatePolicyImpl;
 
+import java.io.FileDescriptor;
 import java.util.Arrays;
 
 /**
@@ -65,15 +70,20 @@
     // The current committed device state.
     @GuardedBy("mLock")
     private int mCommittedState = INVALID_DEVICE_STATE;
-    // The device state that is currently pending callback from the policy to be committed.
+    // The device state that is currently awaiting callback from the policy to be committed.
     @GuardedBy("mLock")
     private int mPendingState = INVALID_DEVICE_STATE;
     // Whether or not the policy is currently waiting to be notified of the current pending state.
     @GuardedBy("mLock")
     private boolean mIsPolicyWaitingForState = false;
     // The device state that is currently requested and is next to be configured and committed.
+    // Can be overwritten by an override state value if requested.
     @GuardedBy("mLock")
     private int mRequestedState = INVALID_DEVICE_STATE;
+    // The most recently requested override state, or INVALID_DEVICE_STATE if no override is
+    // requested.
+    @GuardedBy("mLock")
+    private int mRequestedOverrideState = INVALID_DEVICE_STATE;
 
     public DeviceStateManagerService(@NonNull Context context) {
         this(context, new DeviceStatePolicyImpl());
@@ -97,7 +107,6 @@
      *
      * @see #getPendingState()
      */
-    @VisibleForTesting
     int getCommittedState() {
         synchronized (mLock) {
             return mCommittedState;
@@ -119,13 +128,61 @@
      * Returns the requested state. The service will configure the device to match the requested
      * state when possible.
      */
-    @VisibleForTesting
     int getRequestedState() {
         synchronized (mLock) {
             return mRequestedState;
         }
     }
 
+    /**
+     * Overrides the current device state with the provided state.
+     *
+     * @return {@code true} if the override state is valid and supported, {@code false} otherwise.
+     */
+    boolean setOverrideState(int overrideState) {
+        if (getContext().checkCallingOrSelfPermission(CONTROL_DEVICE_STATE)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + CONTROL_DEVICE_STATE);
+        }
+
+        synchronized (mLock) {
+            if (overrideState != INVALID_DEVICE_STATE && !isSupportedStateLocked(overrideState)) {
+                return false;
+            }
+
+            mRequestedOverrideState = overrideState;
+            updatePendingStateLocked();
+        }
+
+        notifyPolicyIfNeeded();
+        return true;
+    }
+
+    /**
+     * Clears an override state set with {@link #setOverrideState(int)}.
+     */
+    void clearOverrideState() {
+        setOverrideState(INVALID_DEVICE_STATE);
+    }
+
+    /**
+     * Returns the current requested override state, or {@link #INVALID_DEVICE_STATE} is no override
+     * state is requested.
+     */
+    int getOverrideState() {
+        synchronized (mLock) {
+            return mRequestedOverrideState;
+        }
+    }
+
+    /** Returns the list of currently supported device states. */
+    int[] getSupportedStates() {
+        synchronized (mLock) {
+            // Copy array to prevent external modification of internal state.
+            return Arrays.copyOf(mSupportedDeviceStates.toArray(), mSupportedDeviceStates.size());
+        }
+    }
+
     private void updateSupportedStates(int[] supportedDeviceStates) {
         // Must ensure sorted as isSupportedStateLocked() impl uses binary search.
         Arrays.sort(supportedDeviceStates, 0, supportedDeviceStates.length);
@@ -135,11 +192,20 @@
             if (mRequestedState != INVALID_DEVICE_STATE
                     && !isSupportedStateLocked(mRequestedState)) {
                 // The current requested state is no longer valid. We'll clear it here, though
-                // we won't actually update the current state with a call to
-                // updatePendingStateLocked() as doing so will not have any effect.
+                // we won't actually update the current state until a callback comes from the
+                // provider with the most recent state.
                 mRequestedState = INVALID_DEVICE_STATE;
             }
+            if (mRequestedOverrideState != INVALID_DEVICE_STATE
+                    && !isSupportedStateLocked(mRequestedOverrideState)) {
+                // The current override state is no longer valid. We'll clear it here and update
+                // the committed state if necessary.
+                mRequestedOverrideState = INVALID_DEVICE_STATE;
+            }
+            updatePendingStateLocked();
         }
+
+        notifyPolicyIfNeeded();
     }
 
     /**
@@ -168,27 +234,34 @@
     }
 
     /**
-     * Tries to update the current configuring state with the current requested state. Must call
+     * Tries to update the current pending state with the current requested state. Must call
      * {@link #notifyPolicyIfNeeded()} to actually notify the policy that the state is being
      * changed.
      */
     private void updatePendingStateLocked() {
-        if (mRequestedState == INVALID_DEVICE_STATE) {
-            // No currently requested state.
-            return;
-        }
-
         if (mPendingState != INVALID_DEVICE_STATE) {
             // Have pending state, can not configure a new state until the state is committed.
             return;
         }
 
-        if (mRequestedState == mCommittedState) {
-            // No need to notify the policy as the committed state matches the requested state.
+        int stateToConfigure;
+        if (mRequestedOverrideState != INVALID_DEVICE_STATE) {
+            stateToConfigure = mRequestedOverrideState;
+        } else {
+            stateToConfigure = mRequestedState;
+        }
+
+        if (stateToConfigure == INVALID_DEVICE_STATE) {
+            // No currently requested state.
             return;
         }
 
-        mPendingState = mRequestedState;
+        if (stateToConfigure == mCommittedState) {
+            // The state requesting to be committed already matches the current committed state.
+            return;
+        }
+
+        mPendingState = stateToConfigure;
         mIsPolicyWaitingForState = true;
     }
 
@@ -271,6 +344,11 @@
 
     /** Implementation of {@link IDeviceStateManager} published as a binder service. */
     private final class BinderService extends IDeviceStateManager.Stub {
-
+        @Override // Binder call
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver result) {
+            new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
+                    .exec(this, in, out, err, args, callback, result);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
new file mode 100644
index 0000000..cf3b297
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -0,0 +1,128 @@
+/*
+ * 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.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for {@link DeviceStateManagerService}.
+ *
+ * Use with {@code adb shell cmd device_state ...}.
+ */
+public class DeviceStateManagerShellCommand extends ShellCommand {
+    private final DeviceStateManagerService mInternal;
+
+    public DeviceStateManagerShellCommand(DeviceStateManagerService service) {
+        mInternal = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+
+        switch (cmd) {
+            case "state":
+                return runState(pw);
+            case "print-states":
+                return runPrintStates(pw);
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    private void printState(PrintWriter pw) {
+        int committedState = mInternal.getCommittedState();
+        int requestedState = mInternal.getRequestedState();
+        int requestedOverrideState = mInternal.getOverrideState();
+
+        if (committedState == INVALID_DEVICE_STATE) {
+            pw.println("Device state: (invalid)");
+        } else {
+            pw.println("Device state: " + committedState);
+        }
+
+        if (requestedOverrideState != INVALID_DEVICE_STATE) {
+            pw.println("----------------------");
+            if (requestedState == INVALID_DEVICE_STATE) {
+                pw.println("Base state: (invalid)");
+            } else {
+                pw.println("Base state: " + requestedState);
+            }
+            pw.println("Override state: " + committedState);
+        }
+    }
+
+    private int runState(PrintWriter pw) {
+        final String nextArg = getNextArg();
+        if (nextArg == null) {
+            printState(pw);
+        } else if ("reset".equals(nextArg)) {
+            mInternal.clearOverrideState();
+        } else {
+            int requestedState;
+            try {
+                requestedState = Integer.parseInt(nextArg);
+            } catch (NumberFormatException e) {
+                getErrPrintWriter().println("Error: requested state should be an integer");
+                return -1;
+            }
+
+            boolean success = mInternal.setOverrideState(requestedState);
+            if (!success) {
+                getErrPrintWriter().println("Error: failed to set override state. Run:");
+                getErrPrintWriter().println("");
+                getErrPrintWriter().println("    print-states");
+                getErrPrintWriter().println("");
+                getErrPrintWriter().println("to get the list of currently supported device states");
+                return -1;
+            }
+        }
+        return 0;
+    }
+
+    private int runPrintStates(PrintWriter pw) {
+        int[] states = mInternal.getSupportedStates();
+        pw.print("Supported states: [ ");
+        for (int i = 0; i < states.length; i++) {
+            pw.print(states[i]);
+            if (i < states.length - 1) {
+                pw.print(", ");
+            }
+        }
+        pw.println(" ]");
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Device state manager (device_state) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  state [reset|OVERRIDE_DEVICE_STATE]");
+        pw.println("    Return or override device state.");
+        pw.println("  print-states");
+        pw.println("    Return list of currently supported device states.");
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 058794a..9b182a7 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -105,6 +105,30 @@
     }
 
     @Test
+    public void requestOverrideState() {
+        mService.setOverrideState(OTHER_DEVICE_STATE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+
+        // Committed state is set back to the requested state once the override is cleared.
+        mService.clearOverrideState();
+        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+    }
+
+    @Test
+    public void requestOverrideState_unsupportedState() {
+        mService.setOverrideState(UNSUPPORTED_DEVICE_STATE);
+        // Committed state remains the same as the override state is unsupported.
+        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+    }
+
+    @Test
     public void supportedStatesChanged() {
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
         assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
@@ -146,6 +170,23 @@
         assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
     }
 
+    @Test
+    public void supportedStatesChanged_unsupportedOverrideState() {
+        mService.setOverrideState(OTHER_DEVICE_STATE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+
+        mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
+
+        // Committed state is set back to the requested state as the override state is no longer
+        // supported.
+        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+    }
+
     private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
         private final DeviceStateProvider mProvider;
         private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;