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

Commit 3c7359cf authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add device state request APIs to DeviceStateManager." into sc-dev am: ef8d3b00

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13429196

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ica225f8399973c68be8139da18ca9829166d5b93
parents 4fb84b14 ef8d3b00
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ package android {
    field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
    field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
    field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
    field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
    field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
    field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
    field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
@@ -900,6 +901,40 @@ 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 @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);
  }

  public static interface DeviceStateManager.DeviceStateListener {
    method public void onDeviceStateChanged(int);
  }

  public final class DeviceStateRequest {
    method public int getFlags();
    method public int getState();
    method @NonNull public static android.hardware.devicestate.DeviceStateRequest.Builder newBuilder(int);
    field public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1; // 0x1
  }

  public static final class DeviceStateRequest.Builder {
    method @NonNull public android.hardware.devicestate.DeviceStateRequest build();
    method @NonNull public android.hardware.devicestate.DeviceStateRequest.Builder setFlags(int);
  }

  public static interface DeviceStateRequest.Callback {
    method public default void onRequestActivated(@NonNull android.hardware.devicestate.DeviceStateRequest);
    method public default void onRequestCanceled(@NonNull android.hardware.devicestate.DeviceStateRequest);
    method public default void onRequestSuspended(@NonNull android.hardware.devicestate.DeviceStateRequest);
  }

}

package android.hardware.display {

  public class AmbientDisplayConfiguration {
+67 −7
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@

package android.hardware.devicestate;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;

import java.util.concurrent.Executor;
@@ -28,13 +32,19 @@ import java.util.concurrent.Executor;
 *
 * @hide
 */
@TestApi
@SystemService(Context.DEVICE_STATE_SERVICE)
public final class DeviceStateManager {
    /** Invalid device state. */
    /**
     * Invalid device state.
     *
     * @hide
     */
    public static final int INVALID_DEVICE_STATE = -1;

    private DeviceStateManagerGlobal mGlobal;
    private final DeviceStateManagerGlobal mGlobal;

    /** @hide */
    public DeviceStateManager() {
        DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance();
        if (global == null) {
@@ -44,24 +54,74 @@ public final class DeviceStateManager {
        mGlobal = global;
    }

    /**
     * Returns the list of device states that are supported and can be requested with
     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
     */
    @NonNull
    public int[] getSupportedStates() {
        return mGlobal.getSupportedStates();
    }

    /**
     * Submits a {@link DeviceStateRequest request} to modify the device state.
     * <p>
     * By default, the request is kept active until a call to
     * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
     * <ul>
     *     <li>Another processes submits a request succeeding this request in which case the request
     *     will be suspended until the interrupting request is canceled.
     *     <li>The requested state has become unsupported.
     *     <li>The process submitting the request dies.
     * </ul>
     * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
     *
     * @throws IllegalArgumentException if the requested state is unsupported.
     * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE}
     * permission is not held.
     *
     * @see DeviceStateRequest
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
    public void requestState(@NonNull DeviceStateRequest request,
            @Nullable @CallbackExecutor Executor executor,
            @Nullable DeviceStateRequest.Callback callback) {
        mGlobal.requestState(request, callback, executor);
    }

    /**
     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
     * <p>
     * This method is noop if the {@code request} has not been submitted with a call to
     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
     *
     * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE}
     * permission is not held.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
    public void cancelRequest(@NonNull DeviceStateRequest request) {
        mGlobal.cancelRequest(request);
    }

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

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

+189 −2
Original line number Diff line number Diff line
@@ -20,9 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateListener;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,6 +69,9 @@ public final class DeviceStateManagerGlobal {

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

    @Nullable
    @GuardedBy("mLock")
    private Integer mLastReceivedState;
@@ -76,10 +81,85 @@ public final class DeviceStateManagerGlobal {
        mDeviceStateManager = deviceStateManager;
    }

    /**
     * Returns the set of supported device states.
     *
     * @see DeviceStateManager#getSupportedStates()
     */
    public int[] getSupportedStates() {
        try {
            return mDeviceStateManager.getSupportedDeviceStates();
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    /**
     * Submits a {@link DeviceStateRequest request} to modify the device state.
     *
     * @see DeviceStateManager#requestState(DeviceStateRequest,
     * Executor, DeviceStateRequest.Callback)
     * @see DeviceStateRequest
     */
    public void requestState(@NonNull DeviceStateRequest request,
            @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
        if (callback == null && executor != null) {
            throw new IllegalArgumentException("Callback must be supplied with executor.");
        } else if (executor == null && callback != null) {
            throw new IllegalArgumentException("Executor must be supplied with callback.");
        }

        synchronized (mLock) {
            registerCallbackIfNeededLocked();

            if (findRequestTokenLocked(request) != null) {
                // This request has already been submitted.
                return;
            }

            // Add the request wrapper to the mRequests array before requesting the state as the
            // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
            // same process as this instance.
            IBinder token = new Binder();
            mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor));

            try {
                mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
            } catch (RemoteException ex) {
                mRequests.remove(token);
                throw ex.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
     * {@link #requestState(DeviceStateRequest, DeviceStateRequest.Callback, Executor)}.
     *
     * @see DeviceStateManager#cancelRequest(DeviceStateRequest)
     */
    public void cancelRequest(@NonNull DeviceStateRequest request) {
        synchronized (mLock) {
            registerCallbackIfNeededLocked();

            final IBinder token = findRequestTokenLocked(request);
            if (token == null) {
                // This request has not been submitted.
                return;
            }

            try {
                mDeviceStateManager.cancelRequest(token);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Registers a listener to receive notifications about changes in device state.
     *
     * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor)
     * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
     */
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
@@ -112,7 +192,7 @@ public final class DeviceStateManagerGlobal {
     * Unregisters a listener previously registered with
     * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
     *
     * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor)
     * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
     */
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public void unregisterDeviceStateListener(DeviceStateListener listener) {
@@ -144,6 +224,17 @@ public final class DeviceStateManagerGlobal {
        return -1;
    }

    @Nullable
    private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
        for (int i = 0; i < mRequests.size(); i++) {
            if (mRequests.valueAt(i).mRequest.equals(request)) {
                return mRequests.keyAt(i);
            }
        }
        return null;
    }

    /** Handles a call from the server that the device state has changed. */
    private void handleDeviceStateChanged(int newDeviceState) {
        ArrayList<DeviceStateListenerWrapper> listeners;
        synchronized (mLock) {
@@ -156,11 +247,68 @@ public final class DeviceStateManagerGlobal {
        }
    }

    /**
     * Handles a call from the server that a request for the supplied {@code token} has become
     * active.
     */
    private void handleRequestActive(IBinder token) {
        DeviceStateRequestWrapper request;
        synchronized (mLock) {
            request = mRequests.get(token);
        }
        if (request != null) {
            request.notifyRequestActive();
        }
    }

    /**
     * Handles a call from the server that a request for the supplied {@code token} has become
     * suspended.
     */
    private void handleRequestSuspended(IBinder token) {
        DeviceStateRequestWrapper request;
        synchronized (mLock) {
            request = mRequests.get(token);
        }
        if (request != null) {
            request.notifyRequestSuspended();
        }
    }

    /**
     * Handles a call from the server that a request for the supplied {@code token} has become
     * canceled.
     */
    private void handleRequestCanceled(IBinder token) {
        DeviceStateRequestWrapper request;
        synchronized (mLock) {
            request = mRequests.remove(token);
        }
        if (request != null) {
            request.notifyRequestCanceled();
        }
    }

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

        @Override
        public void onRequestActive(IBinder token) {
            handleRequestActive(token);
        }

        @Override
        public void onRequestSuspended(IBinder token) {
            handleRequestSuspended(token);
        }

        @Override
        public void onRequestCanceled(IBinder token) {
            handleRequestCanceled(token);
        }
    }

    private static final class DeviceStateListenerWrapper {
@@ -176,4 +324,43 @@ public final class DeviceStateManagerGlobal {
            mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState));
        }
    }

    private static final class DeviceStateRequestWrapper {
        private final DeviceStateRequest mRequest;
        @Nullable
        private final DeviceStateRequest.Callback mCallback;
        @Nullable
        private final Executor mExecutor;

        DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
                @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
            mRequest = request;
            mCallback = callback;
            mExecutor = executor;
        }

        void notifyRequestActive() {
            if (mCallback == null) {
                return;
            }

            mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
        }

        void notifyRequestSuspended() {
            if (mCallback == null) {
                return;
            }

            mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
        }

        void notifyRequestCanceled() {
            if (mCallback == null) {
                return;
            }

            mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
        }
    }
}
+163 −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.TestApi;

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

/**
 * A request to alter the state of the device managed by {@link DeviceStateManager}.
 * <p>
 * Once constructed, a {@link DeviceStateRequest request} can be submitted with a call to
 * {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
 * DeviceStateRequest.Callback)}.
 * <p>
 * By default, the request is kept active until a call to
 * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
 * occurs:
 * <ul>
 *     <li>Another processes submits a request succeeding this request in which case the request
 *     will be suspended until the interrupting request is canceled.
 *     <li>The requested state has become unsupported.
 *     <li>The process submitting the request dies.
 * </ul>
 * However, this behavior can be changed by setting flags on the request. For example, the
 * {@link #FLAG_CANCEL_WHEN_BASE_CHANGES} flag will extend this behavior to also cancel the
 * request whenever the base (non-override) device state changes.
 *
 * @see DeviceStateManager
 *
 * @hide
 */
@TestApi
public final class DeviceStateRequest {
    /**
     * Flag that indicates the request should be canceled automatically when the base
     * (non-override) device state changes. Useful when the requestor only wants the request to
     * remain active while the base state remains constant and automatically cancel when the user
     * manipulates the device into a different state.
     */
    public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1 << 0;

    /** @hide */
    @IntDef(prefix = {"FLAG_"}, flag = true, value = {
            FLAG_CANCEL_WHEN_BASE_CHANGES,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RequestFlags {}

    /**
     * Creates a new {@link Builder} for a {@link DeviceStateRequest}. Must be one of the supported
     * states for the device which can be queried with a call to
     * {@link DeviceStateManager#getSupportedStates()}.
     *
     * @param requestedState the device state being requested.
     */
    @NonNull
    public static Builder newBuilder(int requestedState) {
        return new Builder(requestedState);
    }

    /**
     * Builder for {@link DeviceStateRequest}. An instance can be obtained through
     * {@link #newBuilder(int)}.
     */
    public static final class Builder {
        private final int mRequestedState;
        private int mFlags;

        private Builder(int requestedState) {
            mRequestedState = requestedState;
        }

        /**
         * Sets the flag bits provided within {@code flags} with all other bits remaining
         * unchanged.
         */
        @NonNull
        public Builder setFlags(@RequestFlags int flags) {
            mFlags |= flags;
            return this;
        }

        /**
         * Returns a new {@link DeviceStateRequest} object whose state matches the state set on the
         * builder.
         */
        @NonNull
        public DeviceStateRequest build() {
            return new DeviceStateRequest(mRequestedState, mFlags);
        }
    }

    /** Callback to track the status of a request. */
    public interface Callback {
        /**
         * Called to indicate the request has become active and the device state will match the
         * requested state.
         * <p>
         * Guaranteed to be called after a call to
         * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)} with a state
         * matching the requested state.
         */
        default void onRequestActivated(@NonNull DeviceStateRequest request) {}

        /**
         * Called to indicate the request has been temporarily suspended.
         * <p>
         * Guaranteed to be called before a call to
         * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
         */
        default void onRequestSuspended(@NonNull DeviceStateRequest request) {}

        /**
         * Called to indicate the request has been canceled. The request can be resubmitted with
         * another call to {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
         * DeviceStateRequest.Callback)}.
         * <p>
         * Guaranteed to be called before a call to
         * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
         * <p>
         * Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to
         * occur before this method.
         */
        default void onRequestCanceled(@NonNull DeviceStateRequest request) {}
    }

    private final int mRequestedState;
    @RequestFlags
    private final int mFlags;

    private DeviceStateRequest(int requestedState, @RequestFlags int flags) {
        mRequestedState = requestedState;
        mFlags = flags;
    }

    public int getState() {
        return mRequestedState;
    }

    @RequestFlags
    public int getFlags() {
        return mFlags;
    }
}
+40 −0
Original line number Diff line number Diff line
@@ -20,5 +20,45 @@ import android.hardware.devicestate.IDeviceStateManagerCallback;

/** @hide */
interface IDeviceStateManager {
    /**
     * Registers a callback to receive notifications from the device state manager. Only one
     * callback can be registered per-process.
     * <p>
     * As the callback mechanism is used to alert the caller of changes to request status a callback
     * <b>MUST</b> be registered before calling {@link #requestState(IBinder, int, int)} or
     * {@link #cancelRequest(IBinder)}. Otherwise an exception will be thrown.
     *
     * @throws SecurityException if a callback is already registered for the calling process.
     */
    void registerCallback(in IDeviceStateManagerCallback callback);

    /** Returns the array of supported device state identifiers. */
    int[] getSupportedDeviceStates();

    /**
     * Requests that the device enter the supplied {@code state}. A callback <b>MUST</b> have been
     * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
     * call to this method.
     *
     * @param token the request token previously registered with
     *        {@link #requestState(IBinder, int, int)}
     *
     * @throws IllegalStateException if a callback has not yet been registered for the calling
     *         process.
     * @throws IllegalStateException if the supplied {@code token} has already been registered.
     * @throws IllegalArgumentException if the supplied {@code state} is not supported.
     */
    void requestState(IBinder token, int state, int flags);

    /**
     * Cancels a request previously submitted with a call to
     * {@link #requestState(IBinder, int, int)}.
     *
     * @param token the request token previously registered with
     *        {@link #requestState(IBinder, int, int)}
     *
     * @throws IllegalStateException if the supplied {@code token} has not been previously
     *         requested.
     */
    void cancelRequest(IBinder token);
}
Loading