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

Commit 2c35bdf9 authored by Darryl L Johnson's avatar Darryl L Johnson
Browse files

Add callbacks for change in supported device states and non-override

state.

This:
1. Renames DeviceStateListener to DeviceStateCallback as there is more
than one method in the callback.
2. Adds a callback to DeviceStateCallback to notify clients about a
change in supported device states.
3. Adds a callback to DeviceStateCallback to notify clients about a
change in non-override state (the state of the device without
considering override requests).
4. Restructures the IDeviceStateManager callback interface to return a
DeviceStateInfo object on successful registration of the callback
instead of triggering callbacks after registation. This simplifies the
callback code paths.

Bug: 159401801
Bug: 154038218
Fixes: 179291575
Test: atest DeviceStateManagerServiceTest
Test: atest DeviceStateManagerGlobalTest
Test: atest DeviceStateInfoTest

Change-Id: I42841de3e19ec0161a8c7b18c5baac0f2c2548bf
parent 25400d83
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -934,17 +934,19 @@ package android.hardware.camera2 {
package android.hardware.devicestate {

  public final class DeviceStateManager {
    method public void addDeviceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
    method @NonNull public int[] getSupportedStates();
    method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
    method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
    method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
    field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
    field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
  }

  public static interface DeviceStateManager.DeviceStateListener {
    method public void onDeviceStateChanged(int);
  public static interface DeviceStateManager.DeviceStateCallback {
    method public default void onBaseStateChanged(int);
    method public void onStateChanged(int);
    method public default void onSupportedStatesChanged(@NonNull int[]);
  }

  public final class DeviceStateRequest {
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.hardware.devicestate;

parcelable DeviceStateInfo;
+162 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.hardware.devicestate;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;


/**
 * Information about the state of the device.
 *
 * @hide
 */
public final class DeviceStateInfo implements Parcelable {
    /** Bit that indicates the {@link #supportedStates} field has changed. */
    public static final int CHANGED_SUPPORTED_STATES = 1 << 0;

    /** Bit that indicates the {@link #baseState} field has changed. */
    public static final int CHANGED_BASE_STATE = 1 << 1;

    /** Bit that indicates the {@link #currentState} field has changed. */
    public static final int CHANGED_CURRENT_STATE = 1 << 2;

    @IntDef(prefix = {"CHANGED_"}, flag = true, value = {
            CHANGED_SUPPORTED_STATES,
            CHANGED_BASE_STATE,
            CHANGED_CURRENT_STATE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ChangeFlags {}

    /**
     * The list of states supported by the device.
     */
    @NonNull
    public final int[] supportedStates;

    /**
     * The base (non-override) state of the device. The base state is the state of the device
     * ignoring any override requests made through a call to {@link DeviceStateManager#requestState(
     * DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
     */
    public final int baseState;

    /**
     * The state of the device.
     */
    public final int currentState;

    /**
     * Creates a new instance of {@link DeviceStateInfo}.
     * <p>
     * NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the
     * supplied parameters.
     */
    public DeviceStateInfo(@NonNull int[] supportedStates, int baseState, int state) {
        this.supportedStates = supportedStates;
        this.baseState = baseState;
        this.currentState = state;
    }

    /**
     * Creates a new instance of {@link DeviceStateInfo} copying the fields of {@code info} into
     * the fields of the returned instance.
     */
    public DeviceStateInfo(@NonNull DeviceStateInfo info) {
        this(Arrays.copyOf(info.supportedStates, info.supportedStates.length),
                info.baseState, info.currentState);
    }

    @Override
    public boolean equals(@Nullable Object other) {
        if (this == other) return true;
        if (other == null || getClass() != other.getClass()) return false;
        DeviceStateInfo that = (DeviceStateInfo) other;
        return baseState == that.baseState
                &&  currentState == that.currentState
                && Arrays.equals(supportedStates, that.supportedStates);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(baseState, currentState);
        result = 31 * result + Arrays.hashCode(supportedStates);
        return result;
    }

    /** Returns a bitmask of the differences between this instance and {@code other}. */
    @ChangeFlags
    public int diff(@NonNull DeviceStateInfo other) {
        int diff = 0;
        if (!Arrays.equals(supportedStates, other.supportedStates)) {
            diff |= CHANGED_SUPPORTED_STATES;
        }
        if (baseState != other.baseState) {
            diff |= CHANGED_BASE_STATE;
        }
        if (currentState != other.currentState) {
            diff |= CHANGED_CURRENT_STATE;
        }
        return diff;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(supportedStates.length);
        for (int i = 0; i < supportedStates.length; i++) {
            dest.writeInt(supportedStates[i]);
        }

        dest.writeInt(baseState);
        dest.writeInt(currentState);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<DeviceStateInfo>() {
        @Override
        public DeviceStateInfo createFromParcel(Parcel source) {
            final int numberOfSupportedStates = source.readInt();
            final int[] supportedStates = new int[numberOfSupportedStates];
            for (int i = 0; i < numberOfSupportedStates; i++) {
                supportedStates[i] = source.readInt();
            }
            final int baseState = source.readInt();
            final int currentState = source.readInt();

            return new DeviceStateInfo(supportedStates, baseState, currentState);
        }

        @Override
        public DeviceStateInfo[] newArray(int size) {
            return new DeviceStateInfo[size];
        }
    };
}
+43 −18
Original line number Diff line number Diff line
@@ -111,38 +111,63 @@ public final class DeviceStateManager {
    }

    /**
     * Registers a listener to receive notifications about changes in device state.
     * Registers a callback to receive notifications about changes in device state.
     *
     * @param executor the executor to process notifications.
     * @param listener the listener to register.
     * @param callback the callback to register.
     *
     * @see DeviceStateListener
     * @see DeviceStateCallback
     */
    public void addDeviceStateListener(@NonNull @CallbackExecutor Executor executor,
            @NonNull DeviceStateListener listener) {
        mGlobal.registerDeviceStateListener(listener, executor);
    public void registerCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull DeviceStateCallback callback) {
        mGlobal.registerDeviceStateCallback(callback, executor);
    }

    /**
     * Unregisters a listener previously registered with
     * {@link #addDeviceStateListener(Executor, DeviceStateListener)}.
     * Unregisters a callback previously registered with
     * {@link #registerCallback(Executor, DeviceStateCallback)}.
     */
    public void removeDeviceStateListener(@NonNull DeviceStateListener listener) {
        mGlobal.unregisterDeviceStateListener(listener);
    public void unregisterCallback(@NonNull DeviceStateCallback callback) {
        mGlobal.unregisterDeviceStateCallback(callback);
    }

    /** Callback to receive notifications about changes in device state. */
    public interface DeviceStateCallback {
        /**
     * Listens for changes in device states.
         * Called in response to a change in the states supported by the device.
         * <p>
         * Guaranteed to be called once on registration of the callback with the initial value and
         * then on every subsequent change in the supported states.
         *
         * @param supportedStates the new supported states.
         *
         * @see DeviceStateManager#getSupportedStates()
         */
        default void onSupportedStatesChanged(@NonNull int[] supportedStates) {}

        /**
         * Called in response to a change in the base device state.
         * <p>
         * The base state is the state of the device without considering any requests made through
         * calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}
         * from any client process. The base state is guaranteed to match the state provided with a
         * call to {@link #onStateChanged(int)} when there are no active requests from any process.
         * <p>
         * Guaranteed to be called once on registration of the callback with the initial value and
         * then on every subsequent change in the non-override state.
         *
         * @param state the new base device state.
         */
    public interface DeviceStateListener {
        default void onBaseStateChanged(int state) {}

        /**
         * Called in response to device state changes.
         * <p>
         * Guaranteed to be called once on registration of the listener with the
         * initial value and then on every subsequent change in device state.
         * Guaranteed to be called once on registration of the callback with the initial value and
         * then on every subsequent change in device state.
         *
         * @param deviceState the new device state.
         * @param state the new device state.
         */
        void onDeviceStateChanged(int deviceState);
        void onStateChanged(int state);
    }
}
+104 −51
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package android.hardware.devicestate;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateListener;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -31,12 +31,14 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Executor;

/**
 * Provides communication with the device state system service on behalf of applications.
 *
 * @see DeviceStateManager
 *
 * @hide
 */
@VisibleForTesting(visibility = Visibility.PACKAGE)
@@ -68,13 +70,13 @@ public final class DeviceStateManagerGlobal {
    private DeviceStateManagerCallback mCallback;

    @GuardedBy("mLock")
    private final ArrayList<DeviceStateListenerWrapper> mListeners = new ArrayList<>();
    private final ArrayList<DeviceStateCallbackWrapper> mCallbacks = new ArrayList<>();
    @GuardedBy("mLock")
    private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>();

    @Nullable
    @GuardedBy("mLock")
    private Integer mLastReceivedState;
    private DeviceStateInfo mLastReceivedInfo;

    @VisibleForTesting
    public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
@@ -87,18 +89,31 @@ public final class DeviceStateManagerGlobal {
     * @see DeviceStateManager#getSupportedStates()
     */
    public int[] getSupportedStates() {
        synchronized (mLock) {
            final DeviceStateInfo currentInfo;
            if (mLastReceivedInfo != null) {
                // If we have mLastReceivedInfo a callback is registered for this instance and it
                // is receiving the most recent info from the server. Use that info here.
                currentInfo = mLastReceivedInfo;
            } else {
                // If mLastReceivedInfo is null there is no registered callback so we manually
                // fetch the current info.
                try {
            return mDeviceStateManager.getSupportedDeviceStates();
                    currentInfo = mDeviceStateManager.getDeviceStateInfo();
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }

            return Arrays.copyOf(currentInfo.supportedStates, currentInfo.supportedStates.length);
        }
    }

    /**
     * Submits a {@link DeviceStateRequest request} to modify the device state.
     *
     * @see DeviceStateManager#requestState(DeviceStateRequest,
     * Executor, DeviceStateRequest.Callback)
     * @see DeviceStateManager#requestState(DeviceStateRequest, Executor,
     * DeviceStateRequest.Callback)
     * @see DeviceStateRequest
     */
    public void requestState(@NonNull DeviceStateRequest request,
@@ -157,49 +172,56 @@ public final class DeviceStateManagerGlobal {
    }

    /**
     * Registers a listener to receive notifications about changes in device state.
     * Registers a callback to receive notifications about changes in device state.
     *
     * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
     * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
     */
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
    public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
            @NonNull Executor executor) {
        Integer stateToReport;
        DeviceStateListenerWrapper wrapper;
        DeviceStateCallbackWrapper wrapper;
        DeviceStateInfo currentInfo;
        synchronized (mLock) {
            registerCallbackIfNeededLocked();

            int index = findListenerLocked(listener);
            int index = findCallbackLocked(callback);
            if (index != -1) {
                // This listener is already registered.
                // This callback is already registered.
                return;
            }

            wrapper = new DeviceStateListenerWrapper(listener, executor);
            mListeners.add(wrapper);
            stateToReport = mLastReceivedState;
            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();
                }
            }

        if (stateToReport != null) {
            // Notify the listener with the most recent device state from the server. If the state
            // to report is null this is likely the first listener added and we're still waiting
            // from the callback from the server.
            wrapper.notifyDeviceStateChanged(stateToReport);
            currentInfo = new DeviceStateInfo(mLastReceivedInfo);

            wrapper = new DeviceStateCallbackWrapper(callback, executor);
            mCallbacks.add(wrapper);
        }

        wrapper.notifySupportedStatesChanged(currentInfo.supportedStates);
        wrapper.notifyBaseStateChanged(currentInfo.baseState);
        wrapper.notifyStateChanged(currentInfo.currentState);
    }

    /**
     * Unregisters a listener previously registered with
     * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
     * Unregisters a callback previously registered with
     * {@link #registerDeviceStateCallback(DeviceStateCallback, Executor)}}.
     *
     * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
     * @see DeviceStateManager#unregisterCallback(DeviceStateCallback)
     */
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public void unregisterDeviceStateListener(DeviceStateListener listener) {
    public void unregisterDeviceStateCallback(@NonNull DeviceStateCallback callback) {
        synchronized (mLock) {
            int indexToRemove = findListenerLocked(listener);
            int indexToRemove = findCallbackLocked(callback);
            if (indexToRemove != -1) {
                mListeners.remove(indexToRemove);
                mCallbacks.remove(indexToRemove);
            }
        }
    }
@@ -210,14 +232,15 @@ public final class DeviceStateManagerGlobal {
            try {
                mDeviceStateManager.registerCallback(mCallback);
            } catch (RemoteException ex) {
                mCallback = null;
                throw ex.rethrowFromSystemServer();
            }
        }
    }

    private int findListenerLocked(DeviceStateListener listener) {
        for (int i = 0; i < mListeners.size(); i++) {
            if (mListeners.get(i).mDeviceStateListener.equals(listener)) {
    private int findCallbackLocked(DeviceStateCallback callback) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            if (mCallbacks.get(i).mDeviceStateCallback.equals(callback)) {
                return i;
            }
        }
@@ -234,16 +257,34 @@ public final class DeviceStateManagerGlobal {
        return null;
    }

    /** Handles a call from the server that the device state has changed. */
    private void handleDeviceStateChanged(int newDeviceState) {
        ArrayList<DeviceStateListenerWrapper> listeners;
    /** Handles a call from the server that the device state info has changed. */
    private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
        ArrayList<DeviceStateCallbackWrapper> callbacks;
        DeviceStateInfo oldInfo;
        synchronized (mLock) {
            mLastReceivedState = newDeviceState;
            listeners = new ArrayList<>(mListeners);
            oldInfo = mLastReceivedInfo;
            mLastReceivedInfo = info;
            callbacks = new ArrayList<>(mCallbacks);
        }

        for (int i = 0; i < listeners.size(); i++) {
            listeners.get(i).notifyDeviceStateChanged(newDeviceState);
        final int diff = oldInfo == null ? 1 : info.diff(oldInfo);
        if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
            for (int i = 0; i < callbacks.size(); i++) {
                // Copy the array to prevent callbacks from modifying the internal state.
                final int[] supportedStates = Arrays.copyOf(info.supportedStates,
                        info.supportedStates.length);
                callbacks.get(i).notifySupportedStatesChanged(supportedStates);
            }
        }
        if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).notifyBaseStateChanged(info.baseState);
            }
        }
        if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).notifyStateChanged(info.currentState);
            }
        }
    }

@@ -291,8 +332,8 @@ public final class DeviceStateManagerGlobal {

    private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
        @Override
        public void onDeviceStateChanged(int deviceState) {
            handleDeviceStateChanged(deviceState);
        public void onDeviceStateInfoChanged(DeviceStateInfo info) {
            handleDeviceStateInfoChanged(info);
        }

        @Override
@@ -311,17 +352,29 @@ public final class DeviceStateManagerGlobal {
        }
    }

    private static final class DeviceStateListenerWrapper {
        private final DeviceStateListener mDeviceStateListener;
    private static final class DeviceStateCallbackWrapper {
        @NonNull
        private final DeviceStateCallback mDeviceStateCallback;
        @NonNull
        private final Executor mExecutor;

        DeviceStateListenerWrapper(DeviceStateListener listener, Executor executor) {
            mDeviceStateListener = listener;
        DeviceStateCallbackWrapper(@NonNull DeviceStateCallback callback,
                @NonNull Executor executor) {
            mDeviceStateCallback = callback;
            mExecutor = executor;
        }

        void notifyDeviceStateChanged(int newDeviceState) {
            mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState));
        void notifySupportedStatesChanged(int[] newSupportedStates) {
            mExecutor.execute(() ->
                    mDeviceStateCallback.onSupportedStatesChanged(newSupportedStates));
        }

        void notifyBaseStateChanged(int newBaseState) {
            mExecutor.execute(() -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
        }

        void notifyStateChanged(int newDeviceState) {
            mExecutor.execute(() -> mDeviceStateCallback.onStateChanged(newDeviceState));
        }
    }

Loading