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

Commit fa010049 authored by Darryl Johnson's avatar Darryl Johnson Committed by Android (Google) Code Review
Browse files

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

parents c1f7479e fa539d13
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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. -->
+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;