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

Commit cda48cdc authored by Kenneth Ford's avatar Kenneth Ford
Browse files

Updates OverrideRequestController to allow for one override

request at max

To simplify orchestration around handling multiple override
requests, we are reducing the OverrideRequestController to
only having one override request at a time. If a new request
is added, the previous request will be cancelled and removed.

This change also means that we can remove the SUSPENDED status
of a request and the accompanying callbacks

Bug: 216495664, Bug: 207686851
Test: OverrideRequestControllerTest
Change-Id: I787f2ce8567812a633c0afdd5b3f684da4508249
parent 6c03192f
Loading
Loading
Loading
Loading
+0 −27
Original line number Diff line number Diff line
@@ -297,20 +297,6 @@ public final class DeviceStateManagerGlobal {
        }
    }

    /**
     * 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.
@@ -336,11 +322,6 @@ public final class DeviceStateManagerGlobal {
            handleRequestActive(token);
        }

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

        @Override
        public void onRequestCanceled(IBinder token) {
            handleRequestCanceled(token);
@@ -395,14 +376,6 @@ public final class DeviceStateManagerGlobal {
            mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
        }

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

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

        void notifyRequestCanceled() {
            if (mCallback == null) {
                return;
+0 −10
Original line number Diff line number Diff line
@@ -40,16 +40,6 @@ interface IDeviceStateManagerCallback {
     */
    oneway void onRequestActive(IBinder token);

    /**
     * Called to notify the callback that a request has become suspended. Guaranteed to be called
     * before a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request
     * becoming suspended resulted in a change of device state info.
     *
     * @param token the request token previously registered with
     *        {@link IDeviceStateManager#requestState(IBinder, int, int)}
     */
    oneway void onRequestSuspended(IBinder token);

    /**
     * Called to notify the callback that a request has become canceled. No further callbacks will
     * be triggered for this request. Guaranteed to be called before a subsequent call to
+1 −1
Original line number Diff line number Diff line
@@ -249,7 +249,7 @@ public final class DeviceStateManagerGlobalTest {
                final Request topRequest = mRequests.get(mRequests.size() - 1);
                for (IDeviceStateManagerCallback callback : mCallbacks) {
                    try {
                        callback.onRequestSuspended(topRequest.token);
                        callback.onRequestCanceled(topRequest.token);
                    } catch (RemoteException e) {
                        // Do nothing. Should never happen.
                    }
+2 −23
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;

import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -348,7 +347,7 @@ public final class DeviceStateManagerService extends SystemService {
            mBaseState = Optional.of(baseState);

            if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
                mOverrideRequestController.cancelOverrideRequests();
                mOverrideRequestController.cancelOverrideRequest();
            }
            mOverrideRequestController.handleBaseStateChanged();
            updatePendingStateLocked();
@@ -503,7 +502,7 @@ public final class DeviceStateManagerService extends SystemService {
            @OverrideRequestController.RequestStatus int status) {
        if (status == STATUS_ACTIVE) {
            mActiveOverride = Optional.of(request);
        } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) {
        } else if (status == STATUS_CANCELED) {
            if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
                mActiveOverride = Optional.empty();
            }
@@ -528,8 +527,6 @@ public final class DeviceStateManagerService extends SystemService {
                // Schedule the notification now.
                processRecord.notifyRequestActiveAsync(request.getToken());
            }
        } else if (status == STATUS_SUSPENDED) {
            processRecord.notifyRequestSuspendedAsync(request.getToken());
        } else {
            processRecord.notifyRequestCanceledAsync(request.getToken());
        }
@@ -716,24 +713,6 @@ public final class DeviceStateManagerService extends SystemService {
            });
        }

        public void notifyRequestSuspendedAsync(IBinder token) {
            @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
            if (lastStatus != null
                    && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) {
                return;
            }

            mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
            mHandler.post(() -> {
                try {
                    mCallback.onRequestSuspended(token);
                } catch (RemoteException ex) {
                    Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
                            ex);
                }
            });
        }

        public void notifyRequestCanceledAsync(IBinder token) {
            @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
            if (lastStatus != null && lastStatus == STATUS_CANCELED) {
+65 −144
Original line number Diff line number Diff line
@@ -18,15 +18,13 @@ package com.android.server.devicestate;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
import android.util.Slog;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * Manages the lifecycle of override requests.
@@ -41,24 +39,21 @@ import java.util.List;
 * </ul>
 */
final class OverrideRequestController {
    private static final String TAG = "OverrideRequestController";

    static final int STATUS_UNKNOWN = 0;
    /**
     * The request is the top-most request.
     */
    static final int STATUS_ACTIVE = 1;
    /**
     * The request is still present but is being superseded by another request.
     */
    static final int STATUS_SUSPENDED = 2;
    /**
     * The request is not longer valid.
     */
    static final int STATUS_CANCELED = 3;
    static final int STATUS_CANCELED = 2;

    @IntDef(prefix = {"STATUS_"}, value = {
            STATUS_UNKNOWN,
            STATUS_ACTIVE,
            STATUS_SUSPENDED,
            STATUS_CANCELED
    })
    @Retention(RetentionPolicy.SOURCE)
@@ -68,8 +63,6 @@ final class OverrideRequestController {
        switch (status) {
            case STATUS_ACTIVE:
                return "ACTIVE";
            case STATUS_SUSPENDED:
                return "SUSPENDED";
            case STATUS_CANCELED:
                return "CANCELED";
            case STATUS_UNKNOWN:
@@ -79,15 +72,13 @@ final class OverrideRequestController {
    }

    private final StatusChangeListener mListener;
    private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>();

    // List of override requests with the most recent override request at the end.
    private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
    // Handle to the current override request, null if none.
    private OverrideRequest mRequest;

    private boolean mStickyRequestsAllowed;
    // List of override requests that have outlived their process and will only be cancelled through
    // a call to cancelStickyRequests().
    private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
    // The current request has outlived their process.
    private boolean mStickyRequest;

    OverrideRequestController(@NonNull StatusChangeListener listener) {
        mListener = listener;
@@ -97,26 +88,26 @@ final class OverrideRequestController {
     * Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
     * to {@link #handleProcessDied(int)} will not result in the request being cancelled
     * immediately. Instead, the request will be marked sticky and must be cancelled with a call
     * to {@link #cancelStickyRequests()}.
     * to {@link #cancelStickyRequest()}.
     */
    void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
        mStickyRequestsAllowed = stickyRequestsAllowed;
        if (!mStickyRequestsAllowed) {
            cancelStickyRequests();
            cancelStickyRequest();
        }
    }

    /**
     * Adds a request to the top of the stack and notifies the listener of all changes to request
     * status as a result of this operation.
     * Sets the new request as active and cancels the previous override request, notifies the
     * listener of all changes to request status as a result of this operation.
     */
    void addRequest(@NonNull OverrideRequest request) {
        mRequests.add(request);
        OverrideRequest previousRequest = mRequest;
        mRequest = request;
        mListener.onStatusChanged(request, STATUS_ACTIVE);

        if (mRequests.size() > 1) {
            OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2);
            mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED);
        if (previousRequest != null) {
            cancelRequestLocked(previousRequest);
        }
    }

@@ -125,41 +116,31 @@ final class OverrideRequestController {
     * to request status as a result of this operation.
     */
    void cancelRequest(@NonNull IBinder token) {
        int index = getRequestIndex(token);
        if (index == -1) {
        // Either don't have an active request or attempting to cancel an already cancelled request
        if (!hasRequest(token)) {
            return;
        }

        OverrideRequest request = mRequests.remove(index);
        if (index == mRequests.size() && mRequests.size() > 0) {
            // We removed the current active request so we need to set the new active request
            // before cancelling this request.
            OverrideRequest newTop = getLast(mRequests);
            mListener.onStatusChanged(newTop, STATUS_ACTIVE);
        }
        mListener.onStatusChanged(request, STATUS_CANCELED);
        cancelCurrentRequestLocked();
    }

    /**
     * Cancels all requests that are currently marked sticky and notifies the listener of all
     * Cancels a request that is currently marked sticky and notifies the listener of all
     * changes to request status as a result of this operation.
     *
     * @see #setStickyRequestsAllowed(boolean)
     */
    void cancelStickyRequests() {
        mTmpRequestsToCancel.clear();
        mTmpRequestsToCancel.addAll(mStickyRequests);
        cancelRequestsLocked(mTmpRequestsToCancel);
    void cancelStickyRequest() {
        if (mStickyRequest) {
            cancelCurrentRequestLocked();
        }
    }

    /**
     * Cancels all override requests, this could be due to the device being put
     * into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS"
     */
    void cancelOverrideRequests() {
        mTmpRequestsToCancel.clear();
        mTmpRequestsToCancel.addAll(mRequests);
        cancelRequestsLocked(mTmpRequestsToCancel);
    void cancelOverrideRequest() {
        cancelCurrentRequestLocked();
    }

    /**
@@ -167,7 +148,7 @@ final class OverrideRequestController {
     * {@code token}, {@code false} otherwise.
     */
    boolean hasRequest(@NonNull IBinder token) {
        return getRequestIndex(token) != -1;
        return mRequest != null && token == mRequest.getToken();
    }

    /**
@@ -176,139 +157,79 @@ final class OverrideRequestController {
     * operation.
     */
    void handleProcessDied(int pid) {
        if (mRequests.isEmpty()) {
        if (mRequest == null) {
            return;
        }

        mTmpRequestsToCancel.clear();
        OverrideRequest prevActiveRequest = getLast(mRequests);
        for (OverrideRequest request : mRequests) {
            if (request.getPid() == pid) {
                mTmpRequestsToCancel.add(request);
            }
        }

        if (mRequest.getPid() == pid) {
            if (mStickyRequestsAllowed) {
                // Do not cancel the requests now because sticky requests are allowed. These
                // requests will be cancelled on a call to cancelStickyRequests().
            mStickyRequests.addAll(mTmpRequestsToCancel);
                mStickyRequest = true;
                return;
            }

        cancelRequestsLocked(mTmpRequestsToCancel);
            cancelCurrentRequestLocked();
        }
    }

    /**
     * Notifies the controller that the base state has changed. The controller will notify the
     * listener of all changes to request status as a result of this change.
     *
     * @return {@code true} if calling this method has lead to a new active request, {@code false}
     * otherwise.
     */
    boolean handleBaseStateChanged() {
        if (mRequests.isEmpty()) {
            return false;
    void handleBaseStateChanged() {
        if (mRequest == null) {
            return;
        }

        mTmpRequestsToCancel.clear();
        OverrideRequest prevActiveRequest = getLast(mRequests);
        for (int i = 0; i < mRequests.size(); i++) {
            OverrideRequest request = mRequests.get(i);
            if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
                mTmpRequestsToCancel.add(request);
            }
        if ((mRequest.getFlags()
                & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
            cancelCurrentRequestLocked();
        }

        final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
        return newActiveRequest;
    }

    /**
     * Notifies the controller that the set of supported states has changed. The controller will
     * notify the listener of all changes to request status as a result of this change.
     *
     * @return {@code true} if calling this method has lead to a new active request, {@code false}
     * otherwise.
     */
    boolean handleNewSupportedStates(int[] newSupportedStates) {
        if (mRequests.isEmpty()) {
            return false;
    void handleNewSupportedStates(int[] newSupportedStates) {
        if (mRequest == null) {
            return;
        }

        mTmpRequestsToCancel.clear();
        for (int i = 0; i < mRequests.size(); i++) {
            OverrideRequest request = mRequests.get(i);
            if (!contains(newSupportedStates, request.getRequestedState())) {
                mTmpRequestsToCancel.add(request);
        if (!contains(newSupportedStates, mRequest.getRequestedState())) {
            cancelCurrentRequestLocked();
        }
    }

        final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
        return newActiveRequest;
    }

    void dumpInternal(PrintWriter pw) {
        final int requestCount = mRequests.size();
        OverrideRequest overrideRequest = mRequest;
        final boolean requestActive = overrideRequest != null;
        pw.println();
        pw.println("Override requests: size=" + requestCount);
        for (int i = 0; i < requestCount; i++) {
            OverrideRequest overrideRequest = mRequests.get(i);
            int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED;
            pw.println("  " + i + ": mPid=" + overrideRequest.getPid()
        pw.println("Override Request active: " + requestActive);
        if (requestActive) {
            pw.println("Request: mPid=" + overrideRequest.getPid()
                    + ", mRequestedState=" + overrideRequest.getRequestedState()
                    + ", mFlags=" + overrideRequest.getFlags()
                    + ", mStatus=" + statusToString(status));
        }
                    + ", mStatus=" + statusToString(STATUS_ACTIVE));
        }

    /**
     * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
     * request becoming active this request will also be notified of its change in state.
     *
     * @return {@code true} if calling this method has lead to a new active request, {@code false}
     * otherwise.
     */
    private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
        if (requestsToCancel.isEmpty()) {
            return false;
    }

        OverrideRequest prevActiveRequest = getLast(mRequests);
        boolean causedNewRequestToBecomeActive = false;
        mRequests.removeAll(requestsToCancel);
        mStickyRequests.removeAll(requestsToCancel);
        if (!mRequests.isEmpty()) {
            OverrideRequest newActiveRequest = getLast(mRequests);
            if (newActiveRequest != prevActiveRequest) {
                mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
                causedNewRequestToBecomeActive = true;
            }
    private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
        mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
    }

        for (int i = 0; i < requestsToCancel.size(); i++) {
            mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
        }
        return causedNewRequestToBecomeActive;
    }

    private int getRequestIndex(@NonNull IBinder token) {
        final int numberOfRequests = mRequests.size();
        if (numberOfRequests == 0) {
            return -1;
        }

        for (int i = 0; i < numberOfRequests; i++) {
            OverrideRequest request = mRequests.get(i);
            if (request.getToken() == token) {
                return i;
            }
        }
        return -1;
    /**
     * Handles cancelling {@code mRequest}.
     * Notifies the listener of the canceled status as well.
     */
    private void cancelCurrentRequestLocked() {
        if (mRequest == null) {
            Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
            return;
        }

    @Nullable
    private static <T> T getLast(List<T> list) {
        return list.size() > 0 ? list.get(list.size() - 1) : null;
        mStickyRequest = false;
        mListener.onStatusChanged(mRequest, STATUS_CANCELED);
        mRequest = null;
    }

    private static boolean contains(int[] array, int value) {
Loading