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