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

Commit a5ab448f authored by Darryl L Johnson's avatar Darryl L Johnson Committed by Kenneth Ford
Browse files

Refactor request lifecycle logic into OverrideRequestController.

This refactors the request lifecycle login into a controller,
OverrideRequestController, which simplies the login within
DeviceStateManagerService itself.

Bug: 192671286
Test: atest DeviceStateManagerServiceTest
Test: atest OverrideRequestControllerTest
Test: atest DeviceStateManagerTests
Change-Id: Ifaf04665bf8c84d62fa08b3b8883559c6726d6af
parent 8e97f220
Loading
Loading
Loading
Loading
+204 −286

File changed.

Preview size limit exceeded, changes collapsed.

+58 −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 com.android.server.devicestate;

import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;

/**
 * A request to override the state managed by {@link DeviceStateManagerService}.
 *
 * @see OverrideRequestController
 */
final class OverrideRequest {
    private final IBinder mToken;
    private final int mPid;
    private final int mRequestedState;
    @DeviceStateRequest.RequestFlags
    private final int mFlags;

    OverrideRequest(IBinder token, int pid, int requestedState,
            @DeviceStateRequest.RequestFlags int flags) {
        mToken = token;
        mPid = pid;
        mRequestedState = requestedState;
        mFlags = flags;
    }

    IBinder getToken() {
        return mToken;
    }

    int getPid() {
        return mPid;
    }

    int getRequestedState() {
        return mRequestedState;
    }

    @DeviceStateRequest.RequestFlags
    int getFlags() {
        return mFlags;
    }
}
+290 −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 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 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.
 * <p>
 * New requests are added with {@link #addRequest(OverrideRequest)} and are kept active until
 * either:
 * <ul>
 *     <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the
 *     request will become suspended.</li>
 *     <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect
 *     of other methods calls, such as {@link #handleProcessDied(int)}.</li>
 * </ul>
 */
final class 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;

    @IntDef(prefix = {"STATUS_"}, value = {
            STATUS_UNKNOWN,
            STATUS_ACTIVE,
            STATUS_SUSPENDED,
            STATUS_CANCELED
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface RequestStatus {}

    static String statusToString(@RequestStatus int status) {
        switch (status) {
            case STATUS_ACTIVE:
                return "ACTIVE";
            case STATUS_SUSPENDED:
                return "SUSPENDED";
            case STATUS_CANCELED:
                return "CANCELED";
            case STATUS_UNKNOWN:
                return "UNKNOWN";
        }
        throw new IllegalArgumentException("Unknown status: " + status);
    }

    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<>();

    OverrideRequestController(@NonNull StatusChangeListener listener) {
        mListener = listener;
    }

    /**
     * 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.
     */
    void addRequest(@NonNull OverrideRequest request) {
        mRequests.add(request);
        mListener.onStatusChanged(request, STATUS_ACTIVE);

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

    /**
     * Cancels the request with the specified {@code token} and notifies the listener of all changes
     * to request status as a result of this operation.
     */
    void cancelRequest(@NonNull IBinder token) {
        int index = getRequestIndex(token);
        if (index == -1) {
            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);
    }

    /**
     * Returns {@code true} if this controller is current managing a request with the specified
     * {@code token}, {@code false} otherwise.
     */
    boolean hasRequest(@NonNull IBinder token) {
        return getRequestIndex(token) != -1;
    }

    /**
     * Notifies the controller that the process with the specified {@code pid} has died. The
     * controller will notify the listener of all changes to request status as a result of this
     * operation.
     */
    void handleProcessDied(int pid) {
        if (mRequests.isEmpty()) {
            return;
        }

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

        mRequests.removeAll(mTmpRequestsToCancel);
        if (!mRequests.isEmpty()) {
            OverrideRequest newActiveRequest = getLast(mRequests);
            if (newActiveRequest != prevActiveRequest) {
                mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
            }
        }

        for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
            mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
        }
        mTmpRequestsToCancel.clear();
    }

    /**
     * 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;
        }

        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);
            }
        }

        mRequests.removeAll(mTmpRequestsToCancel);
        OverrideRequest newActiveRequest = null;
        if (!mRequests.isEmpty()) {
            newActiveRequest = getLast(mRequests);
            if (newActiveRequest != prevActiveRequest) {
                mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
            }
        }

        for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
            mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
        }
        mTmpRequestsToCancel.clear();

        return newActiveRequest != prevActiveRequest;
    }

    /**
     * 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;
        }

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

        mRequests.removeAll(mTmpRequestsToCancel);
        OverrideRequest newActiveRequest = null;
        if (!mRequests.isEmpty()) {
            newActiveRequest = getLast(mRequests);
            if (newActiveRequest != prevActiveRequest) {
                mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
            }
        }

        for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
            mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
        }
        mTmpRequestsToCancel.clear();

        return newActiveRequest != prevActiveRequest;
    }

    void dumpInternal(PrintWriter pw) {
        final int requestCount = mRequests.size();
        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()
                    + ", mRequestedState=" + overrideRequest.getRequestedState()
                    + ", mFlags=" + overrideRequest.getFlags()
                    + ", mStatus=" + statusToString(status));
        }
    }

    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;
    }

    @Nullable
    private static <T> T getLast(List<T> list) {
        return list.size() > 0 ? list.get(list.size() - 1) : null;
    }

    private static boolean contains(int[] array, int value) {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == value) {
                return true;
            }
        }
        return false;
    }

    public interface StatusChangeListener {
        /**
         * Notifies the listener of a change in request status. If a change within the controller
         * causes one request to become active and one to become either suspended or cancelled, this
         * method is guaranteed to be called with the active request first before the suspended or
         * cancelled request.
         */
        void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
    }
}
+48 −1
Original line number Diff line number Diff line
@@ -69,6 +69,17 @@ public final class DeviceStateManagerServiceTest {
        mProvider = new TestDeviceStateProvider();
        mPolicy = new TestDeviceStatePolicy(mProvider);
        mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
        flushHandler(); // Flush the handler to ensure the initial values are committed.
    }

    private void flushHandler() {
        flushHandler(1);
    }

    private void flushHandler(int count) {
        for (int i = 0; i < count; i++) {
            mService.getHandler().runWithScissors(() -> {}, 0);
        }
    }

    @Test
@@ -80,6 +91,7 @@ public final class DeviceStateManagerServiceTest {
                DEFAULT_DEVICE_STATE.getIdentifier());

        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
        flushHandler();
        assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
        assertEquals(mService.getPendingState(), Optional.empty());
        assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
@@ -92,6 +104,7 @@ public final class DeviceStateManagerServiceTest {
        mPolicy.blockConfigure();

        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
        flushHandler();
        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
        assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
        assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
@@ -99,6 +112,7 @@ public final class DeviceStateManagerServiceTest {
                OTHER_DEVICE_STATE.getIdentifier());

        mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
        flushHandler();
        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
        assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
        assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -106,6 +120,7 @@ public final class DeviceStateManagerServiceTest {
                OTHER_DEVICE_STATE.getIdentifier());

        mPolicy.resumeConfigure();
        flushHandler();
        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
        assertEquals(mService.getPendingState(), Optional.empty());
        assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -149,6 +164,7 @@ public final class DeviceStateManagerServiceTest {
        assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));

        mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
        flushHandler();

        // The current committed and requests states do not change because the current state remains
        // supported.
@@ -166,6 +182,7 @@ public final class DeviceStateManagerServiceTest {
        mService.getBinderService().registerCallback(callback);

        // An initial callback will be triggered on registration, so we clear it here.
        flushHandler();
        callback.clearLastNotifiedInfo();

        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -174,6 +191,7 @@ public final class DeviceStateManagerServiceTest {

        mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE,
                OTHER_DEVICE_STATE });
        flushHandler();

        // The current committed and requests states do not change because the current state remains
        // supported.
@@ -203,12 +221,14 @@ public final class DeviceStateManagerServiceTest {
        mService.getBinderService().registerCallback(callback);

        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
        flushHandler();
        assertEquals(callback.getLastNotifiedInfo().baseState,
                OTHER_DEVICE_STATE.getIdentifier());
        assertEquals(callback.getLastNotifiedInfo().currentState,
                OTHER_DEVICE_STATE.getIdentifier());

        mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
        flushHandler();
        assertEquals(callback.getLastNotifiedInfo().baseState,
                DEFAULT_DEVICE_STATE.getIdentifier());
        assertEquals(callback.getLastNotifiedInfo().currentState,
@@ -216,6 +236,7 @@ public final class DeviceStateManagerServiceTest {

        mPolicy.blockConfigure();
        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
        flushHandler();
        // The callback should not have been notified of the state change as the policy is still
        // pending callback.
        assertEquals(callback.getLastNotifiedInfo().baseState,
@@ -224,6 +245,7 @@ public final class DeviceStateManagerServiceTest {
                DEFAULT_DEVICE_STATE.getIdentifier());

        mPolicy.resumeConfigure();
        flushHandler();
        // Now that the policy is finished processing the callback should be notified of the state
        // change.
        assertEquals(callback.getLastNotifiedInfo().baseState,
@@ -236,6 +258,7 @@ public final class DeviceStateManagerServiceTest {
    public void registerCallback_emitsInitialValue() throws RemoteException {
        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
        mService.getBinderService().registerCallback(callback);
        flushHandler();
        assertNotNull(callback.getLastNotifiedInfo());
        assertEquals(callback.getLastNotifiedInfo().baseState,
                DEFAULT_DEVICE_STATE.getIdentifier());
@@ -247,6 +270,7 @@ public final class DeviceStateManagerServiceTest {
    public void requestState() throws RemoteException {
        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
        mService.getBinderService().registerCallback(callback);
        flushHandler();

        final IBinder token = new Binder();
        assertEquals(callback.getLastNotifiedStatus(token),
@@ -254,6 +278,10 @@ public final class DeviceStateManagerServiceTest {

        mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
                0 /* flags */);
        // Flush the handler twice. The first flush ensures the request is added and the policy is
        // notified, while the second flush ensures the callback is notified once the change is
        // committed.
        flushHandler(2 /* count */);

        assertEquals(callback.getLastNotifiedStatus(token),
                TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -271,6 +299,7 @@ public final class DeviceStateManagerServiceTest {
                OTHER_DEVICE_STATE.getIdentifier());

        mService.getBinderService().cancelRequest(token);
        flushHandler();

        assertEquals(callback.getLastNotifiedStatus(token),
                TestDeviceStateManagerCallback.STATUS_CANCELED);
@@ -291,6 +320,7 @@ public final class DeviceStateManagerServiceTest {
    public void requestState_pendingStateAtRequest() throws RemoteException {
        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
        mService.getBinderService().registerCallback(callback);
        flushHandler();

        mPolicy.blockConfigure();

@@ -303,6 +333,10 @@ public final class DeviceStateManagerServiceTest {

        mService.getBinderService().requestState(firstRequestToken,
                OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */);
        // Flush the handler twice. The first flush ensures the request is added and the policy is
        // notified, while the second flush ensures the callback is notified once the change is
        // committed.
        flushHandler(2 /* count */);

        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
        assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
@@ -312,8 +346,8 @@ public final class DeviceStateManagerServiceTest {

        mService.getBinderService().requestState(secondRequestToken,
                DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */);

        mPolicy.resumeConfigureOnce();
        flushHandler();

        // First request status is now suspended as there is another pending request.
        assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
@@ -330,6 +364,7 @@ public final class DeviceStateManagerServiceTest {
                DEFAULT_DEVICE_STATE.getIdentifier());

        mPolicy.resumeConfigure();
        flushHandler();

        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
        assertEquals(mService.getPendingState(), Optional.empty());
@@ -339,6 +374,7 @@ public final class DeviceStateManagerServiceTest {

        // Now cancel the second request to make the first request active.
        mService.getBinderService().cancelRequest(secondRequestToken);
        flushHandler();

        assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
                TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -356,6 +392,7 @@ public final class DeviceStateManagerServiceTest {
    public void requestState_sameAsBaseState() throws RemoteException {
        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
        mService.getBinderService().registerCallback(callback);
        flushHandler();

        final IBinder token = new Binder();
        assertEquals(callback.getLastNotifiedStatus(token),
@@ -363,6 +400,7 @@ public final class DeviceStateManagerServiceTest {

        mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
                0 /* flags */);
        flushHandler();

        assertEquals(callback.getLastNotifiedStatus(token),
                TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -372,6 +410,7 @@ public final class DeviceStateManagerServiceTest {
    public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
        mService.getBinderService().registerCallback(callback);
        flushHandler();

        final IBinder token = new Binder();
        assertEquals(callback.getLastNotifiedStatus(token),
@@ -379,6 +418,10 @@ public final class DeviceStateManagerServiceTest {

        mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
                DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES);
        // Flush the handler twice. The first flush ensures the request is added and the policy is
        // notified, while the second flush ensures the callback is notified once the change is
        // committed.
        flushHandler(2 /* count */);

        assertEquals(callback.getLastNotifiedStatus(token),
                TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -391,6 +434,7 @@ public final class DeviceStateManagerServiceTest {
                OTHER_DEVICE_STATE.getIdentifier());

        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
        flushHandler();

        // Request is canceled because the base state changed.
        assertEquals(callback.getLastNotifiedStatus(token),
@@ -407,6 +451,7 @@ public final class DeviceStateManagerServiceTest {
    public void requestState_becomesUnsupported() throws RemoteException {
        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
        mService.getBinderService().registerCallback(callback);
        flushHandler();

        final IBinder token = new Binder();
        assertEquals(callback.getLastNotifiedStatus(token),
@@ -414,6 +459,7 @@ public final class DeviceStateManagerServiceTest {

        mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
                0 /* flags */);
        flushHandler();

        assertEquals(callback.getLastNotifiedStatus(token),
                TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -425,6 +471,7 @@ public final class DeviceStateManagerServiceTest {
                OTHER_DEVICE_STATE.getIdentifier());

        mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
        flushHandler();

        // Request is canceled because the state is no longer supported.
        assertEquals(callback.getLastNotifiedStatus(token),
+204 −0

File added.

Preview size limit exceeded, changes collapsed.