diff options
| author | 2020-09-22 16:21:59 -0700 | |
|---|---|---|
| committer | 2020-10-14 15:47:09 -0700 | |
| commit | fa539d136efbf172124fca21a6a42bf9fef5b24d (patch) | |
| tree | 268f9b445474c5d8f8887de8ff06d68344b38199 | |
| parent | f7d857c0b6cfd1057f32ed0766f91e10d47da99b (diff) | |
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
5 files changed, 270 insertions, 14 deletions
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f2af514c08ba..85eaa65e2d3f 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 5f018a0322a3..9cd69ccabf13 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 7e3c1ab50ad5..1a2871781116 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.internal.annotations.VisibleForTesting; import com.android.server.SystemService; import com.android.server.policy.DeviceStatePolicyImpl; +import java.io.FileDescriptor; import java.util.Arrays; /** @@ -65,15 +70,20 @@ public final class DeviceStateManagerService extends SystemService { // 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 @@ public final class DeviceStateManagerService extends SystemService { * * @see #getPendingState() */ - @VisibleForTesting int getCommittedState() { synchronized (mLock) { return mCommittedState; @@ -119,13 +128,61 @@ public final class DeviceStateManagerService extends SystemService { * 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 @@ public final class DeviceStateManagerService extends SystemService { 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 @@ public final class DeviceStateManagerService extends SystemService { } /** - * 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. + if (mPendingState != INVALID_DEVICE_STATE) { + // Have pending state, can not configure a new state until the state is committed. return; } - if (mPendingState != INVALID_DEVICE_STATE) { - // Have pending state, can not configure a new state until the state is committed. + int stateToConfigure; + if (mRequestedOverrideState != INVALID_DEVICE_STATE) { + stateToConfigure = mRequestedOverrideState; + } else { + stateToConfigure = mRequestedState; + } + + if (stateToConfigure == INVALID_DEVICE_STATE) { + // No currently requested state. return; } - if (mRequestedState == mCommittedState) { - // No need to notify the policy as the committed state matches the requested state. + if (stateToConfigure == mCommittedState) { + // The state requesting to be committed already matches the current committed state. return; } - mPendingState = mRequestedState; + mPendingState = stateToConfigure; mIsPolicyWaitingForState = true; } @@ -271,6 +344,11 @@ public final class DeviceStateManagerService extends SystemService { /** 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 000000000000..cf3b297545dc --- /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 058794a3b9e9..9b182a71f419 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 @@ public final class DeviceStateManagerServiceTest { } @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 @@ public final class DeviceStateManagerServiceTest { 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; |