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

Commit 090b0afb authored by Darryl L Johnson's avatar Darryl L Johnson
Browse files

Don't report device state until hardware state is known.

The previous implementation of DeviceStateProviderImpl would report the
current device state as soon as a listener was registered leading to a
series of device state changes on boot as the state of the hardware
becomes known. This changes the implementation to await callbacks before
notifying the listener of a change in device state.

Fixes: 181671216
Test: atest DeviceStateProviderImplTest
Test: atest DeviceStateManagerServiceTest
Test: atest DeviceStateManagerGlobalTest

Change-Id: I3f133b6d7135b829b79c9f225ae52c71061faa3e
parent 5efdaa63
Loading
Loading
Loading
Loading
+14 −19
Original line number Original line Diff line number Diff line
@@ -179,8 +179,6 @@ public final class DeviceStateManagerGlobal {
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
    public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
            @NonNull Executor executor) {
            @NonNull Executor executor) {
        DeviceStateCallbackWrapper wrapper;
        DeviceStateInfo currentInfo;
        synchronized (mLock) {
        synchronized (mLock) {
            int index = findCallbackLocked(callback);
            int index = findCallbackLocked(callback);
            if (index != -1) {
            if (index != -1) {
@@ -189,25 +187,22 @@ public final class DeviceStateManagerGlobal {
            }
            }


            registerCallbackIfNeededLocked();
            registerCallbackIfNeededLocked();
            if (mLastReceivedInfo == null) {
                // Initialize the last received info with the current info if this is the first
                // callback being registered.
                try {
                    mLastReceivedInfo = mDeviceStateManager.getDeviceStateInfo();
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }


            currentInfo = new DeviceStateInfo(mLastReceivedInfo);
            // Add the callback wrapper to the mCallbacks array after registering the callback as

            // the callback could be triggered immediately if the mDeviceStateManager IBinder is in
            wrapper = new DeviceStateCallbackWrapper(callback, executor);
            // the same process as this instance.
            DeviceStateCallbackWrapper wrapper = new DeviceStateCallbackWrapper(callback, executor);
            mCallbacks.add(wrapper);
            mCallbacks.add(wrapper);
        }


        wrapper.notifySupportedStatesChanged(currentInfo.supportedStates);
            if (mLastReceivedInfo != null) {
        wrapper.notifyBaseStateChanged(currentInfo.baseState);
                // Copy the array to prevent the callback from modifying the internal state.
        wrapper.notifyStateChanged(currentInfo.currentState);
                final int[] supportedStates = Arrays.copyOf(mLastReceivedInfo.supportedStates,
                        mLastReceivedInfo.supportedStates.length);
                wrapper.notifySupportedStatesChanged(supportedStates);
                wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState);
                wrapper.notifyStateChanged(mLastReceivedInfo.currentState);
            }
        }
    }
    }


    /**
    /**
@@ -267,7 +262,7 @@ public final class DeviceStateManagerGlobal {
            callbacks = new ArrayList<>(mCallbacks);
            callbacks = new ArrayList<>(mCallbacks);
        }
        }


        final int diff = oldInfo == null ? 1 : info.diff(oldInfo);
        final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
        if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
        if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
            for (int i = 0; i < callbacks.size(); i++) {
            for (int i = 0; i < callbacks.size(); i++) {
                // Copy the array to prevent callbacks from modifying the internal state.
                // Copy the array to prevent callbacks from modifying the internal state.
+2 −1
Original line number Original line Diff line number Diff line
@@ -21,7 +21,8 @@ import android.hardware.devicestate.DeviceStateInfo;
/** @hide */
/** @hide */
interface IDeviceStateManagerCallback {
interface IDeviceStateManagerCallback {
    /**
    /**
     * Called in response to a change in {@link DeviceStateInfo}.
     * Called in response to a change in {@link DeviceStateInfo}. Guaranteed to be called once
     * after successful registration of the callback with the initial value.
     *
     *
     * @param info the new device state info.
     * @param info the new device state info.
     *
     *
+5 −0
Original line number Original line Diff line number Diff line
@@ -208,6 +208,11 @@ public final class DeviceStateManagerGlobalTest {
            }
            }


            mCallbacks.add(callback);
            mCallbacks.add(callback);
            try {
                callback.onDeviceStateInfoChanged(getInfo());
            } catch (RemoteException e) {
                // Do nothing. Should never happen.
            }
        }
        }


        @Override
        @Override
+47 −22
Original line number Original line Diff line number Diff line
@@ -78,9 +78,6 @@ import java.util.Optional;
public final class DeviceStateManagerService extends SystemService {
public final class DeviceStateManagerService extends SystemService {
    private static final String TAG = "DeviceStateManagerService";
    private static final String TAG = "DeviceStateManagerService";
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = false;
    // The device state to use as a placeholder before callback from the DeviceStateProvider occurs.
    private static final DeviceState UNSPECIFIED_DEVICE_STATE =
            new DeviceState(MINIMUM_DEVICE_STATE, "UNSPECIFIED");


    private final Object mLock = new Object();
    private final Object mLock = new Object();
    @NonNull
    @NonNull
@@ -92,11 +89,11 @@ public final class DeviceStateManagerService extends SystemService {
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
    private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();


    // The current committed device state. The default of UNSPECIFIED_DEVICE_STATE will be replaced
    // The current committed device state. Will be empty until the first device state provided by
    // by the current state after the initial callback from the DeviceStateProvider.
    // the DeviceStateProvider is committed.
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    @NonNull
    @NonNull
    private DeviceState mCommittedState = UNSPECIFIED_DEVICE_STATE;
    private Optional<DeviceState> mCommittedState = Optional.empty();
    // The device state that is currently awaiting callback from the policy to be committed.
    // The device state that is currently awaiting callback from the policy to be committed.
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    @NonNull
    @NonNull
@@ -105,10 +102,11 @@ public final class DeviceStateManagerService extends SystemService {
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private boolean mIsPolicyWaitingForState = false;
    private boolean mIsPolicyWaitingForState = false;


    // The device state that is set by the device state provider.
    // The device state that is set by the DeviceStateProvider. Will be empty until the first
    // callback from the provider and then will always contain the most recent value.
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    @NonNull
    @NonNull
    private DeviceState mBaseState = UNSPECIFIED_DEVICE_STATE;
    private Optional<DeviceState> mBaseState = Optional.empty();


    // List of processes registered to receive notifications about changes to device state and
    // List of processes registered to receive notifications about changes to device state and
    // request status indexed by process id.
    // request status indexed by process id.
@@ -142,11 +140,14 @@ public final class DeviceStateManagerService extends SystemService {
    /**
    /**
     * Returns the current state the system is in. Note that the system may be in the process of
     * Returns the current state the system is in. Note that the system may be in the process of
     * configuring a different state.
     * configuring a different state.
     * <p>
     * Note: This method will return {@link Optional#empty()} if called before the first state has
     * been committed, otherwise it will return the last committed state.
     *
     *
     * @see #getPendingState()
     * @see #getPendingState()
     */
     */
    @NonNull
    @NonNull
    DeviceState getCommittedState() {
    Optional<DeviceState> getCommittedState() {
        synchronized (mLock) {
        synchronized (mLock) {
            return mCommittedState;
            return mCommittedState;
        }
        }
@@ -167,11 +168,15 @@ public final class DeviceStateManagerService extends SystemService {
    /**
    /**
     * Returns the base state. The service will configure the device to match the base state when
     * Returns the base state. The service will configure the device to match the base state when
     * there is no active request to override the base state.
     * there is no active request to override the base state.
     * <p>
     * Note: This method will return {@link Optional#empty()} if called before a base state is
     * provided to the service by the {@link DeviceStateProvider}, otherwise it will return the
     * most recent provided value.
     *
     *
     * @see #getOverrideState()
     * @see #getOverrideState()
     */
     */
    @NonNull
    @NonNull
    DeviceState getBaseState() {
    Optional<DeviceState> getBaseState() {
        synchronized (mLock) {
        synchronized (mLock) {
            return mBaseState;
            return mBaseState;
        }
        }
@@ -223,9 +228,14 @@ public final class DeviceStateManagerService extends SystemService {


    @NonNull
    @NonNull
    private DeviceStateInfo getDeviceStateInfoLocked() {
    private DeviceStateInfo getDeviceStateInfoLocked() {
        if (!mBaseState.isPresent() || !mCommittedState.isPresent()) {
            throw new IllegalStateException("Trying to get the current DeviceStateInfo before the"
                    + " initial state has been committed.");
        }

        final int[] supportedStates = getSupportedStateIdentifiersLocked();
        final int[] supportedStates = getSupportedStateIdentifiersLocked();
        final int baseState = mBaseState.getIdentifier();
        final int baseState = mBaseState.get().getIdentifier();
        final int currentState = mCommittedState.getIdentifier();
        final int currentState = mCommittedState.get().getIdentifier();


        return new DeviceStateInfo(supportedStates, baseState, currentState);
        return new DeviceStateInfo(supportedStates, baseState, currentState);
    }
    }
@@ -237,6 +247,7 @@ public final class DeviceStateManagerService extends SystemService {


    private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
    private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
        boolean updatedPendingState;
        boolean updatedPendingState;
        boolean hasBaseState;
        synchronized (mLock) {
        synchronized (mLock) {
            final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
            final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();


@@ -260,9 +271,10 @@ public final class DeviceStateManagerService extends SystemService {
            }
            }


            updatedPendingState = updatePendingStateLocked();
            updatedPendingState = updatePendingStateLocked();
            hasBaseState = mBaseState.isPresent();
        }
        }


        if (!updatedPendingState) {
        if (hasBaseState && !updatedPendingState) {
            // If the change in the supported states didn't result in a change of the pending state
            // If the change in the supported states didn't result in a change of the pending state
            // commitPendingState() will never be called and the callbacks will never be notified
            // commitPendingState() will never be called and the callbacks will never be notified
            // of the change.
            // of the change.
@@ -306,11 +318,11 @@ public final class DeviceStateManagerService extends SystemService {
            }
            }


            final DeviceState baseState = baseStateOptional.get();
            final DeviceState baseState = baseStateOptional.get();
            if (mBaseState.equals(baseState)) {
            if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) {
                // Base state hasn't changed. Nothing to do.
                // Base state hasn't changed. Nothing to do.
                return;
                return;
            }
            }
            mBaseState = baseState;
            mBaseState = Optional.of(baseState);


            final int requestSize = mRequestRecords.size();
            final int requestSize = mRequestRecords.size();
            for (int i = 0; i < requestSize; i++) {
            for (int i = 0; i < requestSize; i++) {
@@ -351,9 +363,10 @@ public final class DeviceStateManagerService extends SystemService {
        final DeviceState stateToConfigure;
        final DeviceState stateToConfigure;
        if (!mRequestRecords.isEmpty()) {
        if (!mRequestRecords.isEmpty()) {
            stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState;
            stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState;
        } else if (isSupportedStateLocked(mBaseState.getIdentifier())) {
        } else if (mBaseState.isPresent()
                && isSupportedStateLocked(mBaseState.get().getIdentifier())) {
            // Base state could have recently become unsupported after a change in supported states.
            // Base state could have recently become unsupported after a change in supported states.
            stateToConfigure = mBaseState;
            stateToConfigure = mBaseState.get();
        } else {
        } else {
            stateToConfigure = null;
            stateToConfigure = null;
        }
        }
@@ -363,7 +376,7 @@ public final class DeviceStateManagerService extends SystemService {
            return false;
            return false;
        }
        }


        if (stateToConfigure.equals(mCommittedState)) {
        if (mCommittedState.isPresent() && stateToConfigure.equals(mCommittedState.get())) {
            // The state requesting to be committed already matches the current committed state.
            // The state requesting to be committed already matches the current committed state.
            return false;
            return false;
        }
        }
@@ -417,15 +430,15 @@ public final class DeviceStateManagerService extends SystemService {
    private void commitPendingState() {
    private void commitPendingState() {
        // Update the current state.
        // Update the current state.
        synchronized (mLock) {
        synchronized (mLock) {
            final DeviceState newState = mPendingState.get();
            if (DEBUG) {
            if (DEBUG) {
                Slog.d(TAG, "Committing state: " + mPendingState);
                Slog.d(TAG, "Committing state: " + newState);
            }
            }
            mCommittedState = mPendingState.get();


            if (!mRequestRecords.isEmpty()) {
            if (!mRequestRecords.isEmpty()) {
                final OverrideRequestRecord topRequest =
                final OverrideRequestRecord topRequest =
                        mRequestRecords.get(mRequestRecords.size() - 1);
                        mRequestRecords.get(mRequestRecords.size() - 1);
                if (topRequest.mRequestedState.getIdentifier() == mCommittedState.getIdentifier()) {
                if (topRequest.mRequestedState.getIdentifier() == newState.getIdentifier()) {
                    // The top request could have come in while the service was awaiting callback
                    // The top request could have come in while the service was awaiting callback
                    // from the policy. In that case we only set it to active if it matches the
                    // from the policy. In that case we only set it to active if it matches the
                    // current committed state, otherwise it will be set to active when its
                    // current committed state, otherwise it will be set to active when its
@@ -434,6 +447,7 @@ public final class DeviceStateManagerService extends SystemService {
                }
                }
            }
            }


            mCommittedState = Optional.of(newState);
            mPendingState = Optional.empty();
            mPendingState = Optional.empty();
            updatePendingStateLocked();
            updatePendingStateLocked();
        }
        }
@@ -503,19 +517,30 @@ public final class DeviceStateManagerService extends SystemService {
    }
    }


    private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
    private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
        DeviceStateInfo currentInfo;
        ProcessRecord record;
        // Grab the lock to register the callback and get the current state.
        synchronized (mLock) {
        synchronized (mLock) {
            if (mProcessRecords.contains(pid)) {
            if (mProcessRecords.contains(pid)) {
                throw new SecurityException("The calling process has already registered an"
                throw new SecurityException("The calling process has already registered an"
                        + " IDeviceStateManagerCallback.");
                        + " IDeviceStateManagerCallback.");
            }
            }


            ProcessRecord record = new ProcessRecord(callback, pid);
            record = new ProcessRecord(callback, pid);
            try {
            try {
                callback.asBinder().linkToDeath(record, 0);
                callback.asBinder().linkToDeath(record, 0);
            } catch (RemoteException ex) {
            } catch (RemoteException ex) {
                throw new RuntimeException(ex);
                throw new RuntimeException(ex);
            }
            }
            mProcessRecords.put(pid, record);
            mProcessRecords.put(pid, record);

            currentInfo = mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null;
        }

        if (currentInfo != null) {
            // If there is not a committed state we'll wait to notify the process of the initial
            // value.
            record.notifyDeviceStateInfoAsync(currentInfo);
        }
        }
    }
    }


+9 −4
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.devicestate;


import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;


import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager;
@@ -63,14 +64,14 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
    }
    }


    private void printState(PrintWriter pw) {
    private void printState(PrintWriter pw) {
        DeviceState committedState = mService.getCommittedState();
        Optional<DeviceState> committedState = mService.getCommittedState();
        DeviceState baseState = mService.getBaseState();
        Optional<DeviceState> baseState = mService.getBaseState();
        Optional<DeviceState> overrideState = mService.getOverrideState();
        Optional<DeviceState> overrideState = mService.getOverrideState();


        pw.println("Committed state: " + committedState);
        pw.println("Committed state: " + toString(committedState));
        if (overrideState.isPresent()) {
        if (overrideState.isPresent()) {
            pw.println("----------------------");
            pw.println("----------------------");
            pw.println("Base state: " + baseState);
            pw.println("Base state: " + toString(baseState));
            pw.println("Override state: " + overrideState.get());
            pw.println("Override state: " + overrideState.get());
        }
        }
    }
    }
@@ -143,4 +144,8 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
        pw.println("  print-states");
        pw.println("  print-states");
        pw.println("    Return list of currently supported device states.");
        pw.println("    Return list of currently supported device states.");
    }
    }

    private static String toString(@NonNull Optional<DeviceState> state) {
        return state.isPresent() ? state.get().toString() : "(none)";
    }
}
}
Loading