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