• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.content.Context;
20 
21 import androidx.annotation.VisibleForTesting;
22 
23 import com.android.devicelockcontroller.storage.UserParameters;
24 import com.android.devicelockcontroller.util.LogUtil;
25 
26 import com.google.common.util.concurrent.Futures;
27 import com.google.common.util.concurrent.ListenableFuture;
28 import com.google.common.util.concurrent.MoreExecutors;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Locale;
33 
34 /**
35  * State machine for device lock controller.
36  */
37 public final class DeviceStateControllerImpl implements DeviceStateController {
38     private static final String TAG = "DeviceStateControllerImpl";
39     private final Context mContext;
40     private final ArrayList<StateListener> mListeners = new ArrayList<>();
41     private int mState;
42 
43     /**
44      * Create a new state machine.
45      *
46      * @param context The context used for the state machine.
47      */
DeviceStateControllerImpl(Context context)48     public DeviceStateControllerImpl(Context context) {
49         mState = UserParameters.getDeviceState(context);
50         LogUtil.i(TAG, String.format(Locale.US, "Starting state is %d", mState));
51         mContext = context;
52     }
53 
54     /**
55      * Enforce all policies for the current state.
56      * This method is used to initially enforce policies.
57      * Note that policies are also automatically enforced on state transitions.
58      */
59     @Override
enforcePoliciesForCurrentState()60     public ListenableFuture<Void> enforcePoliciesForCurrentState() {
61         final List<ListenableFuture<Void>> onStateChangedTasks = new ArrayList<>();
62         synchronized (mListeners) {
63             for (StateListener listener : mListeners) {
64                 onStateChangedTasks.add(listener.onStateChanged(mState));
65             }
66         }
67         return Futures.whenAllSucceed(onStateChangedTasks).call((() -> null),
68                 MoreExecutors.directExecutor());
69     }
70 
71     @Override
setNextStateForEvent(@eviceEvent int event)72     public ListenableFuture<Void> setNextStateForEvent(@DeviceEvent int event) {
73         try {
74             updateState(getNextState(event));
75         } catch (StateTransitionException e) {
76             return Futures.immediateFailedFuture(e);
77         }
78         LogUtil.i(TAG, String.format(Locale.US, "handleEvent %d, newState %d", event, mState));
79 
80         return enforcePoliciesForCurrentState();
81     }
82 
83     @Override
getState()84     public int getState() {
85         return mState;
86     }
87 
88     @Override
isLocked()89     public boolean isLocked() {
90         return mState == DeviceState.SETUP_IN_PROGRESS
91                 || mState == DeviceState.SETUP_SUCCEEDED
92                 || mState == DeviceState.KIOSK_SETUP
93                 || mState == DeviceState.LOCKED
94                 || mState == DeviceState.PSEUDO_LOCKED;
95     }
96 
97     @Override
isCheckInNeeded()98     public boolean isCheckInNeeded() {
99         return mState == DeviceState.UNPROVISIONED || mState == DeviceState.PSEUDO_LOCKED
100                 || mState == DeviceState.PSEUDO_UNLOCKED;
101     }
102 
103     @Override
isInSetupState()104     public boolean isInSetupState() {
105         return mState == DeviceState.SETUP_IN_PROGRESS
106                 || mState == DeviceState.SETUP_SUCCEEDED
107                 || mState == DeviceState.SETUP_FAILED;
108     }
109 
110     @Override
addCallback(StateListener listener)111     public void addCallback(StateListener listener) {
112         synchronized (mListeners) {
113             mListeners.add(listener);
114         }
115     }
116 
117     @Override
removeCallback(StateListener listener)118     public void removeCallback(StateListener listener) {
119         synchronized (mListeners) {
120             mListeners.remove(listener);
121         }
122     }
123 
124     @VisibleForTesting
125     @DeviceState
getNextState(@eviceEvent int event)126     int getNextState(@DeviceEvent int event) throws StateTransitionException {
127         switch (event) {
128             case DeviceEvent.PROVISIONING_SUCCESS:
129                 if (mState == DeviceState.UNPROVISIONED || mState == DeviceState.SETUP_FAILED
130                         || mState == DeviceState.PSEUDO_LOCKED
131                         || mState == DeviceState.PSEUDO_UNLOCKED) {
132                     return DeviceState.SETUP_IN_PROGRESS;
133                 }
134                 break;
135             case DeviceEvent.SETUP_SUCCESS:
136                 if (mState == DeviceState.SETUP_IN_PROGRESS) {
137                     return DeviceState.SETUP_SUCCEEDED;
138                 }
139                 break;
140             case DeviceEvent.SETUP_FAILURE:
141                 if (mState == DeviceState.SETUP_IN_PROGRESS) {
142                     return DeviceState.SETUP_FAILED;
143                 }
144                 break;
145             case DeviceEvent.SETUP_COMPLETE:
146                 if (mState == DeviceState.SETUP_SUCCEEDED) {
147                     return DeviceState.KIOSK_SETUP;
148                 }
149                 break;
150             case DeviceEvent.LOCK_DEVICE:
151                 if (mState == DeviceState.UNPROVISIONED || mState == DeviceState.PSEUDO_UNLOCKED
152                         || mState == DeviceState.PSEUDO_LOCKED) {
153                     return DeviceState.PSEUDO_LOCKED;
154                 }
155                 if (mState == DeviceState.UNLOCKED || mState == DeviceState.LOCKED) {
156                     return DeviceState.LOCKED;
157                 }
158                 break;
159             case DeviceEvent.UNLOCK_DEVICE:
160                 if (mState == DeviceState.PSEUDO_LOCKED || mState == DeviceState.PSEUDO_UNLOCKED) {
161                     return DeviceState.PSEUDO_UNLOCKED;
162                 }
163                 if (mState == DeviceState.LOCKED || mState == DeviceState.UNLOCKED
164                         || mState == DeviceState.KIOSK_SETUP) {
165                     return DeviceState.UNLOCKED;
166                 }
167                 break;
168             case DeviceEvent.CLEAR:
169                 if (mState == DeviceState.LOCKED
170                         || mState == DeviceState.UNLOCKED
171                         || mState == DeviceState.KIOSK_SETUP) {
172                     return DeviceState.CLEARED;
173                 }
174                 break;
175             default:
176                 break;
177         }
178 
179         throw new StateTransitionException(mState, event);
180     }
181 
updateState(@eviceState int newState)182     private void updateState(@DeviceState int newState) {
183         UserParameters.setDeviceState(mContext, newState);
184         mState = newState;
185     }
186 }
187