Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fa539d13 authored by Darryl L Johnson's avatar Darryl L Johnson
Browse files

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
parent f7d857c0
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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. -->
+3 −0
Original line number Diff line number Diff line
@@ -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"
+92 −14
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+128 −0
Original line number Diff line number Diff line
/*
 * 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.");
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -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;