Merge "Add DeviceStateManagerService shell commands to get and override state."

This commit is contained in:
Darryl Johnson
2020-10-28 01:51:39 +00:00
committed by Android (Google) Code Review
5 changed files with 271 additions and 15 deletions

View File

@@ -5160,6 +5160,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. -->

View File

@@ -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"

View File

@@ -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.
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 @@ 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);
}
}
}

View File

@@ -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.");
}
}

View File

@@ -104,6 +104,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);
@@ -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;