1 /* 2 * Copyright (C) 2023 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.ProvisionStateController.ProvisionEvent.PROVISION_FAILURE; 20 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_KIOSK; 21 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_PAUSE; 22 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_READY; 23 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_RESUME; 24 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_RETRY; 25 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_SUCCESS; 26 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.KIOSK_PROVISIONED; 27 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_FAILED; 28 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_IN_PROGRESS; 29 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_PAUSED; 30 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED; 31 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED; 32 33 import android.app.admin.DevicePolicyManager; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.pm.PackageManager; 37 import android.os.SystemClock; 38 import android.os.UserManager; 39 import android.provider.Settings; 40 41 import androidx.annotation.GuardedBy; 42 import androidx.annotation.NonNull; 43 import androidx.annotation.VisibleForTesting; 44 45 import com.android.devicelockcontroller.SystemDeviceLockManagerImpl; 46 import com.android.devicelockcontroller.provision.worker.SetupWizardCompletionTimeoutWorker; 47 import com.android.devicelockcontroller.receivers.LockedBootCompletedReceiver; 48 import com.android.devicelockcontroller.stats.StatsLoggerProvider; 49 import com.android.devicelockcontroller.storage.GlobalParametersClient; 50 import com.android.devicelockcontroller.storage.UserParameters; 51 import com.android.devicelockcontroller.util.LogUtil; 52 53 import com.google.common.util.concurrent.FluentFuture; 54 import com.google.common.util.concurrent.FutureCallback; 55 import com.google.common.util.concurrent.Futures; 56 import com.google.common.util.concurrent.ListenableFuture; 57 import com.google.common.util.concurrent.MoreExecutors; 58 59 import java.util.concurrent.Executor; 60 import java.util.concurrent.Executors; 61 62 /** 63 * An implementation of the {@link ProvisionStateController}. This class guarantees thread safety 64 * by synchronizing read/write operations of the state value on background threads in the order of 65 * when the API calls happen. That is, a pre-exist state value read/write operation will always 66 * block an incoming read/write request until the former completes. 67 */ 68 public final class ProvisionStateControllerImpl implements ProvisionStateController { 69 70 public static final String TAG = "ProvisionStateControllerImpl"; 71 private final Context mContext; 72 private final DevicePolicyController mPolicyController; 73 private final DeviceStateController mDeviceStateController; 74 private final Executor mBgExecutor; 75 76 @GuardedBy("this") 77 private ListenableFuture<@ProvisionState Integer> mCurrentStateFuture; 78 ProvisionStateControllerImpl(Context context)79 public ProvisionStateControllerImpl(Context context) { 80 mContext = context; 81 mBgExecutor = Executors.newCachedThreadPool(); 82 mPolicyController = 83 new DevicePolicyControllerImpl(context, 84 context.getSystemService(DevicePolicyManager.class), 85 context.getSystemService(UserManager.class), 86 SystemDeviceLockManagerImpl.getInstance(), 87 this, 88 mBgExecutor); 89 mDeviceStateController = new DeviceStateControllerImpl(mPolicyController, this, 90 mBgExecutor); 91 } 92 93 @VisibleForTesting ProvisionStateControllerImpl(Context context, DevicePolicyController policyController, DeviceStateController stateController, Executor bgExecutor)94 ProvisionStateControllerImpl(Context context, DevicePolicyController policyController, 95 DeviceStateController stateController, Executor bgExecutor) { 96 mContext = context; 97 mPolicyController = policyController; 98 mDeviceStateController = stateController; 99 mBgExecutor = bgExecutor; 100 } 101 102 @Override getState()103 public ListenableFuture<@ProvisionState Integer> getState() { 104 synchronized (this) { 105 if (mCurrentStateFuture == null) { 106 mCurrentStateFuture = Futures.submit( 107 () -> UserParameters.getProvisionState(mContext), 108 mBgExecutor); 109 } 110 return mCurrentStateFuture; 111 } 112 } 113 114 @Override postSetNextStateForEventRequest(@rovisionEvent int event)115 public void postSetNextStateForEventRequest(@ProvisionEvent int event) { 116 Futures.addCallback(setNextStateForEvent(event), 117 getFutureCallback("Set state for event: " + event), 118 MoreExecutors.directExecutor()); 119 } 120 121 @Override setNextStateForEvent(@rovisionEvent int event)122 public ListenableFuture<Void> setNextStateForEvent(@ProvisionEvent int event) { 123 synchronized (this) { 124 // getState() must be called here and assigned to a local variable, otherwise, if 125 // retrieved down the execution flow, it will be returning the new state after 126 // execution. 127 ListenableFuture<@ProvisionState Integer> currentStateFuture = getState(); 128 ListenableFuture<@ProvisionState Integer> stateTransitionFuture = 129 Futures.transform( 130 currentStateFuture, 131 currentState -> { 132 int newState = getNextState(currentState, event); 133 UserParameters.setProvisionState(mContext, newState); 134 handleNewState(newState); 135 // We treat when the event is PROVISION_READY as the start of the 136 // provisioning time. 137 if (PROVISION_READY == event) { 138 UserParameters.setProvisioningStartTimeMillis(mContext, 139 SystemClock.elapsedRealtime()); 140 } 141 if (PROVISION_SUCCESS == event) { 142 ((StatsLoggerProvider) mContext.getApplicationContext()) 143 .getStatsLogger().logSuccessfulProvisioning(); 144 } 145 return newState; 146 }, mBgExecutor); 147 // To prevent exception propagate to future state transitions, catch any exceptions 148 // that might happen during the execution and fallback to previous state if exception 149 // happens. 150 mCurrentStateFuture = Futures.catchingAsync(stateTransitionFuture, Exception.class, 151 input -> currentStateFuture, mBgExecutor); 152 return Futures.transformAsync(stateTransitionFuture, 153 newState -> Futures.catchingAsync(mPolicyController.enforceCurrentPolicies(), 154 Exception.class, ex -> { 155 // Policy enforcement failed, try to restore previous policies and 156 // report critical error. 157 synchronized (this) { 158 mCurrentStateFuture = currentStateFuture; 159 LogUtil.e(TAG, "Enforcement failed so restoring previous state " 160 + currentStateFuture, ex); 161 } 162 return Futures.transformAsync(mPolicyController 163 .enforceCurrentPoliciesForCriticalFailure(), 164 unused -> Futures.immediateFailedFuture(ex), 165 mBgExecutor); 166 }, mBgExecutor), 167 mBgExecutor); 168 } 169 } 170 171 @Override 172 public ListenableFuture<Void> notifyProvisioningReady() { 173 return FluentFuture.from(isUserSetupCompleteOrTimedOut()) 174 .transformAsync(userSetupCompleteOrTimedOut -> { 175 if (userSetupCompleteOrTimedOut) { 176 return setNextStateForEvent(PROVISION_READY); 177 } 178 179 return Futures.immediateVoidFuture(); 180 }, mBgExecutor) 181 .catchingAsync(Throwable.class, t -> { 182 LogUtil.e(TAG, "Failed to get user setup complete state", t); 183 // Since we cannot determine the state, start the provisioning flow. 184 return setNextStateForEvent(PROVISION_READY); 185 }, mBgExecutor); 186 } 187 188 @NonNull 189 private FutureCallback<Void> getFutureCallback(String message) { 190 return new FutureCallback<>() { 191 @Override 192 public void onSuccess(Void unused) { 193 LogUtil.i(TAG, message); 194 } 195 196 @Override 197 public void onFailure(Throwable t) { 198 throw new RuntimeException(t); 199 } 200 }; 201 } 202 203 private void handleNewState(@ProvisionState int state) { 204 if (state == PROVISION_IN_PROGRESS) { 205 mContext.getPackageManager().setComponentEnabledSetting( 206 new ComponentName(mContext, LockedBootCompletedReceiver.class), 207 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); 208 } 209 } 210 211 @VisibleForTesting 212 @ProvisionState 213 static int getNextState(@ProvisionState int state, @ProvisionEvent int event) { 214 switch (event) { 215 case PROVISION_READY: 216 if (state == UNPROVISIONED) { 217 return PROVISION_IN_PROGRESS; 218 } 219 throw new StateTransitionException(state, event); 220 case ProvisionEvent.PROVISION_PAUSE: 221 if (state == PROVISION_IN_PROGRESS) { 222 return PROVISION_PAUSED; 223 } 224 throw new StateTransitionException(state, event); 225 case PROVISION_RESUME: 226 if (state == PROVISION_PAUSED) { 227 return PROVISION_IN_PROGRESS; 228 } 229 throw new StateTransitionException(state, event); 230 case ProvisionEvent.PROVISION_KIOSK: 231 if (state == PROVISION_IN_PROGRESS) { 232 return KIOSK_PROVISIONED; 233 } 234 throw new StateTransitionException(state, event); 235 case ProvisionEvent.PROVISION_FAILURE: 236 if (state == PROVISION_IN_PROGRESS) { 237 return PROVISION_FAILED; 238 } 239 throw new StateTransitionException(state, event); 240 case ProvisionEvent.PROVISION_RETRY: 241 if (state == PROVISION_FAILED) { 242 return PROVISION_IN_PROGRESS; 243 } 244 throw new StateTransitionException(state, event); 245 case ProvisionEvent.PROVISION_SUCCESS: 246 if (state == KIOSK_PROVISIONED) { 247 return PROVISION_SUCCEEDED; 248 } 249 throw new StateTransitionException(state, event); 250 default: 251 throw new IllegalArgumentException("Input state is invalid"); 252 } 253 } 254 255 @Override 256 public DeviceStateController getDeviceStateController() { 257 return mDeviceStateController; 258 } 259 260 @Override 261 public DevicePolicyController getDevicePolicyController() { 262 return mPolicyController; 263 } 264 265 @Override 266 public ListenableFuture<Void> onUserUnlocked() { 267 return Futures.transformAsync(getState(), 268 state -> { 269 if (state == UNPROVISIONED) { 270 if (!isUserSetupComplete()) { 271 SetupWizardCompletionTimeoutWorker 272 .scheduleSetupWizardCompletionTimeoutWork(mContext); 273 } 274 return checkReadyToStartProvisioning(); 275 } else { 276 return mPolicyController.enforceCurrentPolicies(); 277 } 278 }, 279 mBgExecutor); 280 } 281 282 @Override 283 public ListenableFuture<Void> onUserSetupCompleted() { 284 SetupWizardCompletionTimeoutWorker.cancelSetupWizardCompletionTimeoutWork(mContext); 285 return checkReadyToStartProvisioning(); 286 } 287 288 289 private ListenableFuture<Void> checkReadyToStartProvisioning() { 290 return Futures.transformAsync(isUserSetupCompleteOrTimedOut(), userSetupComplete -> { 291 if (!userSetupComplete) { 292 return Futures.immediateVoidFuture(); 293 } 294 295 return Futures.transformAsync(getState(), 296 state -> { 297 if (state != UNPROVISIONED) { 298 return Futures.immediateVoidFuture(); 299 } 300 GlobalParametersClient globalParametersClient = 301 GlobalParametersClient.getInstance(); 302 return Futures.transformAsync(globalParametersClient.isProvisionReady(), 303 isReady -> { 304 if (isReady) { 305 return notifyProvisioningReady(); 306 } 307 return Futures.immediateVoidFuture(); 308 }, 309 mBgExecutor); 310 }, 311 mBgExecutor); 312 313 }, mBgExecutor); 314 } 315 316 private boolean isUserSetupComplete() { 317 return Settings.Secure.getInt( 318 mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; 319 } 320 321 private ListenableFuture<Boolean> isUserSetupCompleteOrTimedOut() { 322 return Futures.submit( 323 () -> UserParameters.isSetupWizardTimedOut(mContext) || Settings.Secure.getInt( 324 mContext.getContentResolver(), 325 Settings.Secure.USER_SETUP_COMPLETE, 0) != 0, 326 mBgExecutor); 327 } 328 329 /** 330 * A RuntimeException thrown when state transition is not allowed 331 */ 332 public static class StateTransitionException extends RuntimeException { 333 public StateTransitionException(@ProvisionState int currentState, 334 @ProvisionEvent int event) { 335 super("Can not handle event: " + eventToString(event) 336 + " in state: " + stateToString(currentState)); 337 } 338 339 private static String stateToString(@ProvisionState int state) { 340 switch (state) { 341 case UNPROVISIONED: 342 return "UNPROVISIONED"; 343 case PROVISION_IN_PROGRESS: 344 return "PROVISION_IN_PROGRESS"; 345 case PROVISION_PAUSED: 346 return "PROVISION_PAUSED"; 347 case PROVISION_FAILED: 348 return "PROVISION_FAILED"; 349 case PROVISION_SUCCEEDED: 350 return "PROVISION_SUCCEEDED"; 351 case KIOSK_PROVISIONED: 352 return "KIOSK_PROVISIONED"; 353 default: 354 return "UNKNOWN_STATE"; 355 } 356 } 357 358 private static String eventToString(@ProvisionEvent int event) { 359 switch (event) { 360 case PROVISION_READY: 361 return "PROVISION_READY"; 362 case PROVISION_PAUSE: 363 return "PROVISION_PAUSE"; 364 case PROVISION_SUCCESS: 365 return "PROVISION_SUCCESS"; 366 case PROVISION_FAILURE: 367 return "PROVISION_FAILURE"; 368 case PROVISION_KIOSK: 369 return "PROVISION_KIOSK"; 370 case PROVISION_RESUME: 371 return "PROVISION_RESUME"; 372 case PROVISION_RETRY: 373 return "PROVISION_RETRY"; 374 default: 375 return "UNKNOWN_EVENT"; 376 } 377 } 378 379 } 380 381 } 382