/* * Copyright (C) 2023 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.devicelockcontroller.policy; import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED; import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED_UNREPORTED; import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNFINALIZED; import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNINITIALIZED; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState; import com.google.common.util.concurrent.ExecutionSequencer; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * Dispatch queue for any changes to the finalization state. * * Guarantees serialization of any state changes so that callbacks happen sequentially in the * order the state changes occurred. */ final class FinalizationStateDispatchQueue { private final ExecutionSequencer mExecutionSequencer; private final Executor mBgExecutor; private @Nullable StateChangeCallback mCallback; private @FinalizationState int mState = UNINITIALIZED; FinalizationStateDispatchQueue() { this(ExecutionSequencer.create()); } @VisibleForTesting FinalizationStateDispatchQueue(ExecutionSequencer sequencer) { mExecutionSequencer = sequencer; mBgExecutor = Executors.newSingleThreadExecutor(); } /** * Initializes the queue. * * @param callback callback that runs atomically with any state changes */ void init(@NonNull StateChangeCallback callback) { mCallback = callback; } /** * Enqueue a state change to be handled after all previous state change requests have resolved. * * Attempting to return to a previous state in the finalization process will no-op. * * @param newState new state to go to * @return future for when the state changes and any callbacks are complete */ ListenableFuture enqueueStateChange(@FinalizationState int newState) { return mExecutionSequencer.submitAsync(() -> handleStateChange(newState), mBgExecutor); } /** * Handles a state change. * * @param newState state to change to * @return future for when the state changes and any callbacks are complete */ private ListenableFuture handleStateChange(@FinalizationState int newState) { final int oldState = mState; if (oldState == newState) { return Futures.immediateVoidFuture(); } if (!isValidStateChange(oldState, newState)) { return Futures.immediateVoidFuture(); } mState = newState; if (mCallback == null) { return Futures.immediateVoidFuture(); } return mCallback.onStateChanged(oldState, newState); } private static boolean isValidStateChange( @FinalizationState int oldState, @FinalizationState int newState) { if (oldState == UNINITIALIZED) { return true; } if (newState == UNINITIALIZED) { // Valid to reset state on multi-user switch return true; } if (oldState == UNFINALIZED && newState == FINALIZED_UNREPORTED) { return true; } if (oldState == UNFINALIZED && newState == FINALIZED) { return true; } if (oldState == FINALIZED_UNREPORTED && newState == FINALIZED) { return true; } return false; } /** * Callback for when the state has changed. Runs sequentially with {@link #mExecutionSequencer}. */ interface StateChangeCallback { /** * Called when the state has changed * * @param oldState the previous state * @param newState the new state * @return future for when state change callback has finished */ ListenableFuture onStateChanged(@FinalizationState int oldState, @FinalizationState int newState); } }