• 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 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