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 static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.CLEARED; 20 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.LOCKED; 21 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNDEFINED; 22 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNLOCKED; 23 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_SUCCESS; 24 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.KIOSK_PROVISIONED; 25 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED; 26 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED; 27 28 import androidx.annotation.VisibleForTesting; 29 30 import com.android.devicelock.flags.Flags; 31 import com.android.devicelockcontroller.storage.GlobalParametersClient; 32 33 import com.google.common.util.concurrent.Futures; 34 import com.google.common.util.concurrent.ListenableFuture; 35 import com.google.common.util.concurrent.MoreExecutors; 36 37 import java.util.concurrent.Executor; 38 39 /** An implementation of the {@link DeviceStateController} */ 40 public final class DeviceStateControllerImpl implements DeviceStateController { 41 private final ProvisionStateController mProvisionStateController; 42 private final DevicePolicyController mPolicyController; 43 private final GlobalParametersClient mGlobalParametersClient; 44 private final Executor mExecutor; 45 // Used to exercising APIs under CTS without actually applying any policies. 46 // This is not persistent across controller restarts, but should be good enough for the 47 // intended purpose. 48 @VisibleForTesting 49 volatile @DeviceState int mPseudoDeviceState; 50 private boolean mClearingInProgress; 51 DeviceStateControllerImpl(DevicePolicyController policyController, ProvisionStateController provisionStateController, Executor executor)52 public DeviceStateControllerImpl(DevicePolicyController policyController, 53 ProvisionStateController provisionStateController, Executor executor) { 54 mPolicyController = policyController; 55 mProvisionStateController = provisionStateController; 56 mGlobalParametersClient = GlobalParametersClient.getInstance(); 57 mExecutor = executor; 58 mPseudoDeviceState = UNDEFINED; 59 mClearingInProgress = false; 60 } 61 62 @Override lockDevice()63 public ListenableFuture<Void> lockDevice() { 64 return setDeviceState(LOCKED); 65 } 66 67 @Override unlockDevice()68 public ListenableFuture<Void> unlockDevice() { 69 return setDeviceState(UNLOCKED); 70 } 71 72 @Override clearDevice()73 public ListenableFuture<Void> clearDevice() { 74 mClearingInProgress = true; 75 return setDeviceState(CLEARED); 76 } 77 78 /** 79 * Set the global device state to be the input {@link DeviceState}. The returned 80 * {@link ListenableFuture} will complete when both the state change and policies enforcement 81 * for new state are done. 82 */ setDeviceState(@eviceState int deviceState)83 private ListenableFuture<Void> setDeviceState(@DeviceState int deviceState) { 84 if (deviceState == UNDEFINED) { 85 throw new IllegalArgumentException("Cannot set device state to UNDEFINED"); 86 } 87 return Futures.transformAsync(mProvisionStateController.getState(), 88 provisionState -> { 89 final ListenableFuture<Void> maybeSetProvisioningSuccess; 90 if (provisionState == KIOSK_PROVISIONED) { 91 maybeSetProvisioningSuccess = 92 mProvisionStateController.setNextStateForEvent(PROVISION_SUCCESS); 93 } else if (provisionState == PROVISION_SUCCEEDED) { 94 maybeSetProvisioningSuccess = Futures.immediateVoidFuture(); 95 } else if (provisionState == UNPROVISIONED && (deviceState == LOCKED 96 || deviceState == UNLOCKED)) { 97 // During normal operation, we should not get lock/unlock requests in 98 // the UNPROVISIONED state. Used for CTS compliance. 99 mPseudoDeviceState = deviceState; 100 // Do not apply any policies 101 return Futures.immediateVoidFuture(); 102 } else if (Flags.clearDeviceRestrictions() 103 && (provisionState == UNPROVISIONED && deviceState == CLEARED)) { 104 // During normal operation, we should not get clear requests in 105 // the UNPROVISIONED state. Used for CTS compliance. 106 mPseudoDeviceState = deviceState; 107 // Do not apply any policies 108 return Futures.immediateVoidFuture(); 109 } else { 110 throw new RuntimeException( 111 "User has not been provisioned! Current state " + provisionState); 112 } 113 return Futures.transformAsync(maybeSetProvisioningSuccess, 114 unused -> Futures.transformAsync(isCleared(), 115 isCleared -> { 116 if (isClearingInProgress(deviceState) || isCleared) { 117 throw new IllegalStateException("Device has been " 118 + "cleared!"); 119 } 120 return Futures.transformAsync( 121 mGlobalParametersClient.setDeviceState(deviceState), 122 state -> mPolicyController.enforceCurrentPolicies(), 123 mExecutor); 124 }, mExecutor), 125 mExecutor); 126 }, mExecutor); 127 } 128 129 @Override 130 public ListenableFuture<Boolean> isLocked() { 131 return Futures.transformAsync(mProvisionStateController.getState(), 132 provisionState -> { 133 if (provisionState == UNPROVISIONED) { 134 // Used for CTS compliance. 135 return Futures.immediateFuture(mPseudoDeviceState == LOCKED); 136 } else { 137 return Futures.transform(mGlobalParametersClient.getDeviceState(), 138 s -> { 139 if (s == UNDEFINED) { 140 throw new IllegalStateException("isLocked called before " 141 + "setting the locked state " 142 + "(lockDevice/unlockDevice)"); 143 } 144 return s == LOCKED; 145 }, mExecutor); 146 } 147 }, mExecutor); 148 } 149 150 @Override 151 public ListenableFuture<Integer> getDeviceState() { 152 return mGlobalParametersClient.getDeviceState(); 153 } 154 155 @Override 156 public ListenableFuture<Boolean> isCleared() { 157 return Futures.transform(mGlobalParametersClient.getDeviceState(), 158 s -> s == CLEARED, MoreExecutors.directExecutor()); 159 } 160 161 // If a clear operation is immediately followed by an unlock command, sometimes a race 162 // condition occurs that results in the unlock state being enforced. This method is used to 163 // ensure that clear is always terminal. 164 // TODO: b/286324034 - these operations should be made thread safe 165 private boolean isClearingInProgress(@DeviceState int deviceStateBeingEnforced) { 166 return deviceStateBeingEnforced != CLEARED && mClearingInProgress; 167 } 168 } 169