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.activities.ProvisioningActivity.EXTRA_SHOW_CRITICAL_PROVISION_FAILED_UI_ON_START;
20 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_FINANCING_PROVISIONING;
21 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_SUBSIDY_PROVISIONING;
22 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.CLEARED;
23 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.LOCKED;
24 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNDEFINED;
25 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNLOCKED;
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 import static com.android.devicelockcontroller.policy.StartLockTaskModeWorker.START_LOCK_TASK_MODE_WORK_NAME;
33 
34 import android.app.admin.DevicePolicyManager;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.PackageManager;
39 import android.content.pm.ResolveInfo;
40 import android.database.sqlite.SQLiteException;
41 import android.os.Build;
42 import android.os.UserManager;
43 
44 import androidx.annotation.GuardedBy;
45 import androidx.annotation.VisibleForTesting;
46 import androidx.work.ExistingWorkPolicy;
47 import androidx.work.OneTimeWorkRequest;
48 import androidx.work.Operation;
49 import androidx.work.OutOfQuotaPolicy;
50 import androidx.work.WorkManager;
51 
52 import com.android.devicelockcontroller.SystemDeviceLockManager;
53 import com.android.devicelockcontroller.activities.LandingActivity;
54 import com.android.devicelockcontroller.activities.ProvisioningActivity;
55 import com.android.devicelockcontroller.common.DeviceLockConstants;
56 import com.android.devicelockcontroller.common.DeviceLockConstants.ProvisioningType;
57 import com.android.devicelockcontroller.policy.DeviceStateController.DeviceState;
58 import com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState;
59 import com.android.devicelockcontroller.provision.worker.ReportDeviceProvisionStateWorker;
60 import com.android.devicelockcontroller.schedule.DeviceLockControllerScheduler;
61 import com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerProvider;
62 import com.android.devicelockcontroller.storage.GlobalParametersClient;
63 import com.android.devicelockcontroller.storage.SetupParametersClient;
64 import com.android.devicelockcontroller.util.LogUtil;
65 
66 import com.google.common.util.concurrent.FutureCallback;
67 import com.google.common.util.concurrent.Futures;
68 import com.google.common.util.concurrent.ListenableFuture;
69 import com.google.common.util.concurrent.MoreExecutors;
70 
71 import java.util.ArrayList;
72 import java.util.List;
73 import java.util.concurrent.Executor;
74 
75 /**
76  * An implementation of {@link DevicePolicyController}. This class guarantees thread safety by
77  * synchronizing policies enforcement on background threads in the order of when the API calls
78  * happen. That is, a pre-exist enforcement request will always blocks a incoming enforcement
79  * request until the former completes.
80  */
81 public final class DevicePolicyControllerImpl implements DevicePolicyController {
82     private static final String TAG = "DevicePolicyControllerImpl";
83 
84     private final List<PolicyHandler> mPolicyList = new ArrayList<>();
85     private final Context mContext;
86     private final DevicePolicyManager mDpm;
87     private final ProvisionStateController mProvisionStateController;
88     // A future that returns the current lock task type for the current provision/device state
89     // after policies enforcement are done.
90     @GuardedBy("this")
91     private ListenableFuture<@LockTaskType Integer> mCurrentEnforcedLockTaskTypeFuture =
92             Futures.immediateFuture(LockTaskType.UNDEFINED);
93     private final Executor mBgExecutor;
94     static final String ACTION_DEVICE_LOCK_KIOSK_SETUP =
95             "com.android.devicelock.action.KIOSK_SETUP";
96     private static final String DEVICE_LOCK_VERSION_EXTRA = "DEVICE_LOCK_VERSION";
97     private static final int DEVICE_LOCK_VERSION = 2;
98     private final UserManager mUserManager;
99 
100     /**
101      * Create a new policy controller.
102      *
103      * @param context The context used by this policy controller.
104      * @param devicePolicyManager  The device policy manager.
105      * @param userManager The user manager.
106      * @param systemDeviceLockManager The system device lock manager.
107      * @param provisionStateController The provision state controller.
108      * @param bgExecutor The background executor.
109      */
DevicePolicyControllerImpl(Context context, DevicePolicyManager devicePolicyManager, UserManager userManager, SystemDeviceLockManager systemDeviceLockManager, ProvisionStateController provisionStateController, Executor bgExecutor)110     public DevicePolicyControllerImpl(Context context,
111             DevicePolicyManager devicePolicyManager,
112             UserManager userManager,
113             SystemDeviceLockManager systemDeviceLockManager,
114             ProvisionStateController provisionStateController,
115             Executor bgExecutor) {
116         this(context,
117                 devicePolicyManager,
118                 userManager,
119                 new UserRestrictionsPolicyHandler(devicePolicyManager, userManager,
120                         Build.isDebuggable(),
121                         bgExecutor),
122                 new AppOpsPolicyHandler(systemDeviceLockManager, bgExecutor),
123                 new LockTaskModePolicyHandler(context, devicePolicyManager, bgExecutor),
124                 new PackagePolicyHandler(context, devicePolicyManager, bgExecutor),
125                 new RolePolicyHandler(systemDeviceLockManager, bgExecutor),
126                 new KioskKeepAlivePolicyHandler(systemDeviceLockManager, bgExecutor),
127                 new ControllerKeepAlivePolicyHandler(systemDeviceLockManager, bgExecutor),
128                 new NotificationsPolicyHandler(systemDeviceLockManager, bgExecutor),
129                 provisionStateController,
130                 bgExecutor);
131     }
132 
133     @VisibleForTesting
DevicePolicyControllerImpl(Context context, DevicePolicyManager devicePolicyManager, UserManager userManager, UserRestrictionsPolicyHandler userRestrictionsPolicyHandler, AppOpsPolicyHandler appOpsPolicyHandler, LockTaskModePolicyHandler lockTaskModePolicyHandler, PackagePolicyHandler packagePolicyHandler, RolePolicyHandler rolePolicyHandler, KioskKeepAlivePolicyHandler kioskKeepAlivePolicyHandler, ControllerKeepAlivePolicyHandler controllerKeepAlivePolicyHandler, NotificationsPolicyHandler notificationsPolicyHandler, ProvisionStateController provisionStateController, Executor bgExecutor)134     DevicePolicyControllerImpl(Context context,
135             DevicePolicyManager devicePolicyManager,
136             UserManager userManager,
137             UserRestrictionsPolicyHandler userRestrictionsPolicyHandler,
138             AppOpsPolicyHandler appOpsPolicyHandler,
139             LockTaskModePolicyHandler lockTaskModePolicyHandler,
140             PackagePolicyHandler packagePolicyHandler,
141             RolePolicyHandler rolePolicyHandler,
142             KioskKeepAlivePolicyHandler kioskKeepAlivePolicyHandler,
143             ControllerKeepAlivePolicyHandler controllerKeepAlivePolicyHandler,
144             NotificationsPolicyHandler notificationsPolicyHandler,
145             ProvisionStateController provisionStateController,
146             Executor bgExecutor) {
147         mContext = context;
148         mProvisionStateController = provisionStateController;
149         mBgExecutor = bgExecutor;
150         mDpm = devicePolicyManager;
151         mUserManager = userManager;
152         mPolicyList.add(userRestrictionsPolicyHandler);
153         mPolicyList.add(appOpsPolicyHandler);
154         mPolicyList.add(lockTaskModePolicyHandler);
155         mPolicyList.add(packagePolicyHandler);
156         mPolicyList.add(rolePolicyHandler);
157         mPolicyList.add(kioskKeepAlivePolicyHandler);
158         mPolicyList.add(controllerKeepAlivePolicyHandler);
159         mPolicyList.add(notificationsPolicyHandler);
160     }
161 
162     @Override
wipeDevice()163     public boolean wipeDevice() {
164         LogUtil.i(TAG, "Wiping device");
165         try {
166             mDpm.wipeDevice(DevicePolicyManager.WIPE_SILENTLY
167                     | DevicePolicyManager.WIPE_RESET_PROTECTION_DATA);
168         } catch (SecurityException e) {
169             LogUtil.e(TAG, "Cannot wipe device", e);
170             return false;
171         }
172         return true;
173     }
174 
175     @Override
enforceCurrentPolicies()176     public ListenableFuture<Void> enforceCurrentPolicies() {
177         return Futures.transform(enforceCurrentPoliciesAndResolveLockTaskType(
178                         /* failure= */ false),
179                 mode -> {
180                     startLockTaskModeIfNeeded(mode);
181                     return null;
182                 },
183                 mBgExecutor);
184     }
185 
186     @Override
enforceCurrentPoliciesForCriticalFailure()187     public ListenableFuture<Void> enforceCurrentPoliciesForCriticalFailure() {
188         return Futures.transform(enforceCurrentPoliciesAndResolveLockTaskType(
189                         /* failure= */ true),
190                 mode -> {
191                     startLockTaskModeIfNeeded(mode);
192                     handlePolicyEnforcementFailure();
193                     return null;
194                 },
195                 mBgExecutor);
196     }
197 
198     private void handlePolicyEnforcementFailure() {
199         final DeviceLockControllerSchedulerProvider schedulerProvider =
200                 (DeviceLockControllerSchedulerProvider) mContext.getApplicationContext();
201         final DeviceLockControllerScheduler scheduler =
202                 schedulerProvider.getDeviceLockControllerScheduler();
203         // Hard failure due to policy enforcement, treat it as mandatory reset device alarm.
204         scheduler.scheduleMandatoryResetDeviceAlarm();
205 
206         ReportDeviceProvisionStateWorker.reportSetupFailed(WorkManager.getInstance(mContext),
207                 DeviceLockConstants.ProvisionFailureReason.POLICY_ENFORCEMENT_FAILED);
208     }
209 
210     /**
211      * Enforce current policies and then return the resulting lock task type
212      *
213      * @param failure true if this enforcement is due to resetting policies in case of failure.
214      * @return A future for the lock task type corresponding to the current policies.
215      */
216     private ListenableFuture<@LockTaskType Integer> enforceCurrentPoliciesAndResolveLockTaskType(
217             boolean failure) {
218         synchronized (this) {
219             // current lock task type must be assigned to a local variable; otherwise, if
220             // retrieved down the execution flow, it will be returning the new type after execution.
221             ListenableFuture<@LockTaskType Integer> currentLockTaskType =
222                     mCurrentEnforcedLockTaskTypeFuture;
223             ListenableFuture<@LockTaskType Integer> policiesEnforcementFuture =
224                     Futures.transformAsync(
225                             currentLockTaskType,
226                             unused -> {
227                                 final ListenableFuture<@ProvisionState Integer> provisionState =
228                                         mProvisionStateController.getState();
229                                 final ListenableFuture<@DeviceState Integer> deviceState =
230                                         GlobalParametersClient.getInstance().getDeviceState();
231                                 return Futures.whenAllSucceed(provisionState, deviceState)
232                                         .callAsync(
233                                                 () -> enforcePoliciesForCurrentStates(
234                                                         Futures.getDone(provisionState),
235                                                         Futures.getDone(deviceState)),
236                                                 mBgExecutor
237                                 );
238                             },
239                             mBgExecutor);
240             if (failure) {
241                 mCurrentEnforcedLockTaskTypeFuture = Futures.immediateFuture(
242                         LockTaskType.CRITICAL_ERROR);
243                 return mCurrentEnforcedLockTaskTypeFuture;
244             } else {
245                 // To prevent exception propagate to future policies enforcement, catch any
246                 // exceptions that might happen during the execution and fallback to previous type
247                 // if exception happens.
248                 mCurrentEnforcedLockTaskTypeFuture = Futures.catchingAsync(
249                         policiesEnforcementFuture,
250                         Exception.class, unused -> currentLockTaskType,
251                         MoreExecutors.directExecutor());
252             }
253             return policiesEnforcementFuture;
254         }
255     }
256 
257     private ListenableFuture<@LockTaskType Integer> enforcePoliciesForCurrentStates(
258             @ProvisionState int provisionState, @DeviceState int deviceState) {
259         LogUtil.i(TAG, "Enforcing policies for provision state " + provisionState
260                 + " and device state " + deviceState);
261         if (provisionState == UNPROVISIONED) {
262             return Futures.immediateFuture(resolveLockTaskType(provisionState, deviceState));
263         }
264         List<ListenableFuture<Boolean>> futures = new ArrayList<>();
265         if (deviceState == CLEARED) {
266             // If device is cleared, then ignore provision state and add cleared policies
267             for (int i = 0, policyLen = mPolicyList.size(); i < policyLen; i++) {
268                 PolicyHandler policy = mPolicyList.get(i);
269                 futures.add(policy.onCleared());
270             }
271         } else if (provisionState == PROVISION_SUCCEEDED) {
272             // If provisioning has succeeded, then ignore provision state and add device state
273             // policies
274             for (int i = 0, policyLen = mPolicyList.size(); i < policyLen; i++) {
275                 PolicyHandler policy = mPolicyList.get(i);
276                 switch (deviceState) {
277                     case UNLOCKED:
278                         futures.add(policy.onUnlocked());
279                         break;
280                     case LOCKED:
281                         futures.add(policy.onLocked());
282                         break;
283                     case UNDEFINED:
284                         // No policies to enforce in this state.
285                         break;
286                     default:
287                         throw new IllegalArgumentException(
288                                 "Invalid device state to enforce: " + deviceState);
289                 }
290             }
291         } else {
292             for (int i = 0, policyLen = mPolicyList.size(); i < policyLen; i++) {
293                 PolicyHandler policy = mPolicyList.get(i);
294                 switch (provisionState) {
295                     case PROVISION_IN_PROGRESS:
296                         futures.add(policy.onProvisionInProgress());
297                         break;
298                     case KIOSK_PROVISIONED:
299                         futures.add(policy.onProvisioned());
300                         break;
301                     case PROVISION_PAUSED:
302                         futures.add(policy.onProvisionPaused());
303                         break;
304                     case PROVISION_FAILED:
305                         futures.add(policy.onProvisionFailed());
306                         break;
307                     default:
308                         throw new IllegalArgumentException(
309                                 "Invalid provision state to enforce: " + provisionState);
310                 }
311             }
312         }
313         return Futures.transform(Futures.allAsList(futures),
314                 results -> {
315                     if (results.stream().reduce(true, (a, r) -> a && r)) {
316                         return resolveLockTaskType(provisionState, deviceState);
317                     } else {
318                         throw new IllegalStateException(
319                                 "Failed to enforce policies for provision state " + provisionState
320                                         + " and device state " + deviceState);
321                     }
322                 },
323                 MoreExecutors.directExecutor());
324     }
325 
326     /**
327      * Determines the lock task type based on the current provision and device state
328      */
329     private @LockTaskType int resolveLockTaskType(int provisionState, int deviceState) {
330         if (provisionState == UNPROVISIONED || deviceState == CLEARED) {
331             return LockTaskType.NOT_IN_LOCK_TASK;
332         }
333         if (provisionState == PROVISION_IN_PROGRESS) {
334             return LockTaskType.LANDING_ACTIVITY;
335         }
336         if (provisionState == KIOSK_PROVISIONED) {
337             return LockTaskType.KIOSK_SETUP_ACTIVITY;
338         }
339         if (provisionState == PROVISION_SUCCEEDED && deviceState == LOCKED) {
340             return LockTaskType.KIOSK_LOCK_ACTIVITY;
341         }
342         return LockTaskType.NOT_IN_LOCK_TASK;
343     }
344 
345     private ListenableFuture<Intent> getLockScreenActivityIntent() {
346         return Futures.transform(
347                 SetupParametersClient.getInstance().getKioskPackage(),
348                 kioskPackage -> {
349                     if (kioskPackage == null) {
350                         throw new IllegalStateException("Missing kiosk package parameter!");
351                     }
352                     Intent homeIntent = new Intent(Intent.ACTION_MAIN)
353                             .addCategory(Intent.CATEGORY_HOME)
354                             .setPackage(kioskPackage);
355                     PackageManager pm = mContext.getPackageManager();
356                     ResolveInfo resolvedInfo = pm.resolveActivity(homeIntent,
357                             PackageManager.MATCH_DEFAULT_ONLY);
358                     if (resolvedInfo != null && resolvedInfo.activityInfo != null) {
359                         return homeIntent.setComponent(
360                                 new ComponentName(kioskPackage,
361                                         resolvedInfo.activityInfo.name));
362                     }
363                     // Kiosk app does not have an activity to handle the default
364                     // home intent. Fall back to the launch activity.
365                     // Note that in this case, Kiosk App can't be effectively set as
366                     // the default home activity.
367                     Intent launchIntent = pm.getLaunchIntentForPackage(kioskPackage);
368                     if (launchIntent == null) {
369                         throw new IllegalStateException(
370                                 "Failed to get launch intent for kiosk app!");
371                     }
372                     return launchIntent;
373                 }, mBgExecutor);
374     }
375 
376     private ListenableFuture<Intent> getLandingActivityIntent() {
377         SetupParametersClient client = SetupParametersClient.getInstance();
378         ListenableFuture<@ProvisioningType Integer> provisioningType =
379                 client.getProvisioningType();
380         return Futures.transform(provisioningType,
381                 type -> {
382                     Intent resultIntent = new Intent(mContext, LandingActivity.class);
383                     switch (type) {
384                         case ProvisioningType.TYPE_FINANCED:
385                             // TODO(b/288923554) this used to return an intent with action
386                             // ACTION_START_DEVICE_FINANCING_SECONDARY_USER_PROVISIONING
387                             // for secondary users. Rework once a decision has been made about
388                             // what to show to users.
389                             return resultIntent.setAction(
390                                     ACTION_START_DEVICE_FINANCING_PROVISIONING);
391                         case ProvisioningType.TYPE_SUBSIDY:
392                             return resultIntent.setAction(ACTION_START_DEVICE_SUBSIDY_PROVISIONING);
393                         case ProvisioningType.TYPE_UNDEFINED:
394                         default:
395                             throw new IllegalArgumentException("Provisioning type is unknown!");
396                     }
397                 }, mBgExecutor);
398     }
399 
400     private ListenableFuture<Intent> getKioskSetupActivityIntent() {
401         return Futures.transform(SetupParametersClient.getInstance().getKioskPackage(),
402                 kioskPackageName -> {
403                     if (kioskPackageName == null) {
404                         throw new IllegalStateException("Missing kiosk package parameter!");
405                     }
406                     final Intent kioskSetupIntent = new Intent(ACTION_DEVICE_LOCK_KIOSK_SETUP);
407                     kioskSetupIntent.setPackage(kioskPackageName);
408                     final ResolveInfo resolveInfo = mContext.getPackageManager()
409                             .resolveActivity(kioskSetupIntent, PackageManager.MATCH_DEFAULT_ONLY);
410                     if (resolveInfo == null || resolveInfo.activityInfo == null) {
411                         throw new IllegalStateException(
412                                 "Failed to get setup activity intent for kiosk app!");
413                     }
414                     kioskSetupIntent.putExtra(DEVICE_LOCK_VERSION_EXTRA, DEVICE_LOCK_VERSION);
415                     return kioskSetupIntent.setComponent(new ComponentName(kioskPackageName,
416                             resolveInfo.activityInfo.name));
417                 }, mBgExecutor);
418     }
419 
420     private ListenableFuture<Intent> getProvisioningActivityIntentForCriticalFailure() {
421         final Intent intent = new Intent(mContext, ProvisioningActivity.class)
422                 .putExtra(EXTRA_SHOW_CRITICAL_PROVISION_FAILED_UI_ON_START, true);
423         return Futures.immediateFuture(intent);
424     }
425 
426 
427     @Override
428     public ListenableFuture<Intent> getLaunchIntentForCurrentState() {
429         return Futures.transformAsync(getCurrentEnforcedLockTaskType(),
430                 type -> {
431                     switch (type) {
432                         case LockTaskType.NOT_IN_LOCK_TASK:
433                             return Futures.immediateFuture(null);
434                         case LockTaskType.LANDING_ACTIVITY:
435                             return getLandingActivityIntent();
436                         case LockTaskType.CRITICAL_ERROR:
437                             return getProvisioningActivityIntentForCriticalFailure();
438                         case LockTaskType.KIOSK_SETUP_ACTIVITY:
439                             return getKioskSetupActivityIntent();
440                         case LockTaskType.KIOSK_LOCK_ACTIVITY:
441                             return getLockScreenActivityIntent();
442                         default:
443                             throw new IllegalArgumentException("Invalid lock task type!");
444                     }
445                 }, mBgExecutor);
446     }
447 
448     /**
449      * Gets the currently enforced lock task type, enforcing current policies if they haven't been
450      * enforced yet.
451      */
452     private ListenableFuture<@LockTaskType Integer> getCurrentEnforcedLockTaskType() {
453         synchronized (this) {
454             return Futures.transformAsync(
455                     mCurrentEnforcedLockTaskTypeFuture,
456                     type -> type == LockTaskType.UNDEFINED
457                             ? Futures.transform(enforceCurrentPoliciesAndResolveLockTaskType(
458                                     /* failure= */ false),
459                                     mode -> {
460                                         startLockTaskModeIfNeeded(mode);
461                                         return mode;
462                                     }, mBgExecutor)
463                             : Futures.immediateFuture(type),
464                     mBgExecutor);
465         }
466     }
467 
468     @Override
469     public ListenableFuture<Void> onUserUnlocked() {
470         return Futures.transformAsync(mProvisionStateController.onUserUnlocked(),
471                 unused -> Futures.transform(getCurrentEnforcedLockTaskType(),
472                         mode -> {
473                             startLockTaskModeIfNeeded(mode);
474                             return null;
475                         },
476                         mBgExecutor),
477                 mBgExecutor);
478     }
479 
480     @Override
481     public ListenableFuture<Void> onUserSetupCompleted() {
482         return mProvisionStateController.onUserSetupCompleted();
483     }
484 
485     @Override
486     public ListenableFuture<Void> onAppCrashed(boolean isKiosk) {
487         final String crashedApp = isKiosk ? "kiosk" : "dlc";
488         LogUtil.i(TAG, "Controller notified about " + crashedApp
489                 + " having crashed while in lock task mode");
490         return Futures.transform(getCurrentEnforcedLockTaskType(),
491                 mode -> {
492                     startLockTaskModeIfNeeded(mode);
493                     return null;
494                 },
495                 mBgExecutor);
496     }
497 
498     private void startLockTaskModeIfNeeded(@LockTaskType Integer type) {
499         if (type == LockTaskType.NOT_IN_LOCK_TASK || !mUserManager.isUserUnlocked()) {
500             return;
501         }
502         WorkManager workManager = WorkManager.getInstance(mContext);
503         OneTimeWorkRequest startLockTask = new OneTimeWorkRequest.Builder(
504                 StartLockTaskModeWorker.class)
505                 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
506                 .build();
507         final ListenableFuture<Operation.State.SUCCESS> enqueueResult =
508                 workManager.enqueueUniqueWork(START_LOCK_TASK_MODE_WORK_NAME,
509                         ExistingWorkPolicy.REPLACE, startLockTask).getResult();
510         Futures.addCallback(enqueueResult, new FutureCallback<>() {
511             @Override
512             public void onSuccess(Operation.State.SUCCESS result) {
513                 // Enqueued
514             }
515 
516             @Override
517             public void onFailure(Throwable t) {
518                 LogUtil.e(TAG, "Failed to enqueue 'start lock task mode' work", t);
519                 if (t instanceof SQLiteException) {
520                     wipeDevice();
521                 } else {
522                     LogUtil.e(TAG, "Not wiping device (non SQL exception)");
523                 }
524             }
525         }, mBgExecutor);
526     }
527 }
528