blob: b860fe13c1d42207d6aae8c5f57ea7d4216aace5 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.devicelockcontroller.policy;
import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.CLEARED;
import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.LOCKED;
import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNDEFINED;
import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNLOCKED;
import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_SUCCESS;
import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.KIOSK_PROVISIONED;
import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED;
import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED;
import com.android.devicelockcontroller.storage.GlobalParametersClient;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.Executor;
/** An implementation of the {@link DeviceStateController} */
public final class DeviceStateControllerImpl implements DeviceStateController {
private final ProvisionStateController mProvisionStateController;
private final DevicePolicyController mPolicyController;
private final GlobalParametersClient mGlobalParametersClient;
private final Executor mExecutor;
// Used to exercising APIs under CTS without actually applying any policies.
// This is not persistent across controller restarts, but should be good enough for the
// intended purpose.
private volatile @DeviceState int mPseudoDeviceState;
public DeviceStateControllerImpl(DevicePolicyController policyController,
ProvisionStateController provisionStateController, Executor executor) {
mPolicyController = policyController;
mProvisionStateController = provisionStateController;
mGlobalParametersClient = GlobalParametersClient.getInstance();
mExecutor = executor;
mPseudoDeviceState = UNDEFINED;
}
@Override
public ListenableFuture<Void> lockDevice() {
return setDeviceState(LOCKED);
}
@Override
public ListenableFuture<Void> unlockDevice() {
return setDeviceState(UNLOCKED);
}
@Override
public ListenableFuture<Void> clearDevice() {
return setDeviceState(CLEARED);
}
/**
* Set the global device state to be the input {@link DeviceState}. The returned
* {@link ListenableFuture} will complete when both the state change and policies enforcement
* for new state are done.
*/
private ListenableFuture<Void> setDeviceState(@DeviceState int deviceState) {
if (deviceState == UNDEFINED) {
throw new IllegalArgumentException("Cannot set device state to UNDEFINED");
}
return Futures.transformAsync(mProvisionStateController.getState(),
provisionState -> {
final ListenableFuture<Void> maybeSetProvisioningSuccess;
if (provisionState == KIOSK_PROVISIONED) {
maybeSetProvisioningSuccess =
mProvisionStateController.setNextStateForEvent(PROVISION_SUCCESS);
} else if (provisionState == PROVISION_SUCCEEDED) {
maybeSetProvisioningSuccess = Futures.immediateVoidFuture();
} else if (provisionState == UNPROVISIONED && (deviceState == LOCKED
|| deviceState == UNLOCKED)) {
// During normal operation, we should not get lock/unlock requests in
// the UNPROVISIONED state. Used for CTS compliance.
mPseudoDeviceState = deviceState;
// Do not apply any policies
return Futures.immediateVoidFuture();
} else {
throw new RuntimeException("User has not been provisioned!");
}
return Futures.transformAsync(maybeSetProvisioningSuccess,
unused -> Futures.transformAsync(isCleared(),
isCleared -> {
if (isCleared) {
throw new RuntimeException("Device has been cleared!");
}
return Futures.transformAsync(
mGlobalParametersClient.setDeviceState(deviceState),
state -> mPolicyController.enforceCurrentPolicies(),
mExecutor);
}, mExecutor),
mExecutor);
}, mExecutor);
}
@Override
public ListenableFuture<Boolean> isLocked() {
return Futures.transformAsync(mProvisionStateController.getState(),
provisionState -> {
if (provisionState == UNDEFINED) {
// Used for CTS compliance.
return Futures.immediateFuture(mPseudoDeviceState == LOCKED);
} else {
return Futures.transform(mGlobalParametersClient.getDeviceState(),
s -> {
if (s == UNDEFINED) {
throw new IllegalStateException("isLocked called before "
+ "setting the locked state "
+ "(lockDevice/unlockDevice)");
}
return s == LOCKED;
}, mExecutor);
}
}, mExecutor);
}
private ListenableFuture<Boolean> isCleared() {
return Futures.transform(mGlobalParametersClient.getDeviceState(),
s -> s == CLEARED, MoreExecutors.directExecutor());
}
}