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 com.android.devicelockcontroller.storage.GlobalParametersClient;
29 
30 import com.google.common.util.concurrent.Futures;
31 import com.google.common.util.concurrent.ListenableFuture;
32 import com.google.common.util.concurrent.MoreExecutors;
33 
34 import java.util.concurrent.Executor;
35 
36 /** An implementation of the {@link DeviceStateController} */
37 public final class DeviceStateControllerImpl implements DeviceStateController {
38     private final ProvisionStateController mProvisionStateController;
39     private final DevicePolicyController mPolicyController;
40     private final GlobalParametersClient mGlobalParametersClient;
41     private final Executor mExecutor;
42     // Used to exercising APIs under CTS without actually applying any policies.
43     // This is not persistent across controller restarts, but should be good enough for the
44     // intended purpose.
45     private volatile @DeviceState int mPseudoDeviceState;
46 
DeviceStateControllerImpl(DevicePolicyController policyController, ProvisionStateController provisionStateController, Executor executor)47     public DeviceStateControllerImpl(DevicePolicyController policyController,
48             ProvisionStateController provisionStateController, Executor executor) {
49         mPolicyController = policyController;
50         mProvisionStateController = provisionStateController;
51         mGlobalParametersClient = GlobalParametersClient.getInstance();
52         mExecutor = executor;
53         mPseudoDeviceState = UNDEFINED;
54     }
55 
56     @Override
lockDevice()57     public ListenableFuture<Void> lockDevice() {
58         return setDeviceState(LOCKED);
59     }
60 
61     @Override
unlockDevice()62     public ListenableFuture<Void> unlockDevice() {
63         return setDeviceState(UNLOCKED);
64     }
65 
66     @Override
clearDevice()67     public ListenableFuture<Void> clearDevice() {
68         return setDeviceState(CLEARED);
69     }
70 
71     /**
72      * Set the global device state to be the input {@link DeviceState}. The returned
73      * {@link ListenableFuture} will complete when both the state change and policies enforcement
74      * for new state are done.
75      */
setDeviceState(@eviceState int deviceState)76     private ListenableFuture<Void> setDeviceState(@DeviceState int deviceState) {
77         if (deviceState == UNDEFINED) {
78             throw new IllegalArgumentException("Cannot set device state to UNDEFINED");
79         }
80         return Futures.transformAsync(mProvisionStateController.getState(),
81                 provisionState -> {
82                     final ListenableFuture<Void> maybeSetProvisioningSuccess;
83                     if (provisionState == KIOSK_PROVISIONED) {
84                         maybeSetProvisioningSuccess =
85                                 mProvisionStateController.setNextStateForEvent(PROVISION_SUCCESS);
86                     } else if (provisionState == PROVISION_SUCCEEDED) {
87                         maybeSetProvisioningSuccess = Futures.immediateVoidFuture();
88                     } else if (provisionState == UNPROVISIONED && (deviceState == LOCKED
89                             || deviceState == UNLOCKED)) {
90                         // During normal operation, we should not get lock/unlock requests in
91                         // the UNPROVISIONED state. Used for CTS compliance.
92                         mPseudoDeviceState = deviceState;
93                         // Do not apply any policies
94                         return Futures.immediateVoidFuture();
95                     } else {
96                         throw new RuntimeException(
97                                 "User has not been provisioned! Current state " + provisionState);
98                     }
99                     return Futures.transformAsync(maybeSetProvisioningSuccess,
100                             unused -> Futures.transformAsync(isCleared(),
101                                     isCleared -> {
102                                         if (isCleared) {
103                                             throw new IllegalStateException("Device has been "
104                                                     + "cleared!");
105                                         }
106                                         return Futures.transformAsync(
107                                                 mGlobalParametersClient.setDeviceState(deviceState),
108                                                 state -> mPolicyController.enforceCurrentPolicies(),
109                                                 mExecutor);
110                                     }, mExecutor),
111                             mExecutor);
112                 }, mExecutor);
113     }
114 
115     @Override
116     public ListenableFuture<Boolean> isLocked() {
117         return Futures.transformAsync(mProvisionStateController.getState(),
118                 provisionState -> {
119                     if (provisionState == UNPROVISIONED) {
120                         // Used for CTS compliance.
121                         return Futures.immediateFuture(mPseudoDeviceState == LOCKED);
122                     } else {
123                         return Futures.transform(mGlobalParametersClient.getDeviceState(),
124                                 s -> {
125                                     if (s == UNDEFINED) {
126                                         throw new IllegalStateException("isLocked called before "
127                                                 + "setting the locked state "
128                                                 + "(lockDevice/unlockDevice)");
129                                     }
130                                     return s == LOCKED;
131                                 }, mExecutor);
132                     }
133                 }, mExecutor);
134     }
135 
136     @Override
137     public ListenableFuture<Integer> getDeviceState() {
138         return mGlobalParametersClient.getDeviceState();
139     }
140 
141     @Override
142     public ListenableFuture<Boolean> isCleared() {
143         return Futures.transform(mGlobalParametersClient.getDeviceState(),
144                 s -> s == CLEARED, MoreExecutors.directExecutor());
145     }
146 }
147