1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.devicelockcontroller.policy;
18 
19 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED;
20 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED_UNREPORTED;
21 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNFINALIZED;
22 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNINITIALIZED;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.Nullable;
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState;
29 
30 import com.google.common.util.concurrent.ExecutionSequencer;
31 import com.google.common.util.concurrent.Futures;
32 import com.google.common.util.concurrent.ListenableFuture;
33 
34 import java.util.concurrent.Executor;
35 import java.util.concurrent.Executors;
36 
37 /**
38  * Dispatch queue for any changes to the finalization state.
39  *
40  * Guarantees serialization of any state changes so that callbacks happen sequentially in the
41  * order the state changes occurred.
42  */
43 final class FinalizationStateDispatchQueue {
44     private final ExecutionSequencer mExecutionSequencer;
45     private final Executor mBgExecutor;
46     private @Nullable StateChangeCallback mCallback;
47     private @FinalizationState int mState = UNINITIALIZED;
48 
FinalizationStateDispatchQueue()49     FinalizationStateDispatchQueue() {
50         this(ExecutionSequencer.create());
51     }
52 
53     @VisibleForTesting
FinalizationStateDispatchQueue(ExecutionSequencer sequencer)54     FinalizationStateDispatchQueue(ExecutionSequencer sequencer) {
55         mExecutionSequencer = sequencer;
56         mBgExecutor = Executors.newSingleThreadExecutor();
57     }
58 
59     /**
60      * Initializes the queue.
61      *
62      * @param callback callback that runs atomically with any state changes
63      */
init(@onNull StateChangeCallback callback)64     void init(@NonNull StateChangeCallback callback) {
65         mCallback = callback;
66     }
67 
68     /**
69      * Enqueue a state change to be handled after all previous state change requests have resolved.
70      *
71      * Attempting to return to a previous state in the finalization process will no-op.
72      *
73      * @param newState new state to go to
74      * @return future for when the state changes and any callbacks are complete
75      */
enqueueStateChange(@inalizationState int newState)76     ListenableFuture<Void> enqueueStateChange(@FinalizationState int newState) {
77         return mExecutionSequencer.submitAsync(() -> handleStateChange(newState), mBgExecutor);
78     }
79 
80     /**
81      * Handles a state change.
82      *
83      * @param newState state to change to
84      * @return future for when the state changes and any callbacks are complete
85      */
handleStateChange(@inalizationState int newState)86     private ListenableFuture<Void> handleStateChange(@FinalizationState int newState) {
87         final int oldState = mState;
88         if (oldState == newState) {
89             return Futures.immediateVoidFuture();
90         }
91         if (!isValidStateChange(oldState, newState)) {
92             return Futures.immediateVoidFuture();
93         }
94         mState = newState;
95         if (mCallback == null) {
96             return Futures.immediateVoidFuture();
97         }
98         return mCallback.onStateChanged(oldState, newState);
99     }
100 
isValidStateChange( @inalizationState int oldState, @FinalizationState int newState)101     private static boolean isValidStateChange(
102             @FinalizationState int oldState,
103             @FinalizationState int newState) {
104         if (oldState == UNINITIALIZED) {
105             return true;
106         }
107         if (newState == UNINITIALIZED) {
108             // Valid to reset state on multi-user switch
109             return true;
110         }
111         if (oldState == UNFINALIZED && newState == FINALIZED_UNREPORTED) {
112             return true;
113         }
114         if (oldState == UNFINALIZED && newState == FINALIZED) {
115             return true;
116         }
117         if (oldState == FINALIZED_UNREPORTED && newState == FINALIZED) {
118             return true;
119         }
120         return false;
121     }
122 
123     /**
124      * Callback for when the state has changed. Runs sequentially with {@link #mExecutionSequencer}.
125      */
126     interface StateChangeCallback {
127 
128         /**
129          * Called when the state has changed
130          *
131          * @param oldState the previous state
132          * @param newState the new state
133          * @return future for when state change callback has finished
134          */
onStateChanged(@inalizationState int oldState, @FinalizationState int newState)135         ListenableFuture<Void> onStateChanged(@FinalizationState int oldState,
136                 @FinalizationState int newState);
137     }
138 }
139