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.provision.worker; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; 22 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; 23 24 import static com.android.devicelockcontroller.common.DeviceLockConstants.REASON_UNSPECIFIED; 25 import static com.android.devicelockcontroller.common.DeviceLockConstants.USER_DEFERRED_DEVICE_PROVISIONING; 26 27 import android.content.Context; 28 import android.net.NetworkRequest; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.VisibleForTesting; 32 import androidx.work.BackoffPolicy; 33 import androidx.work.Constraints; 34 import androidx.work.Data; 35 import androidx.work.ExistingWorkPolicy; 36 import androidx.work.NetworkType; 37 import androidx.work.OneTimeWorkRequest; 38 import androidx.work.Operation; 39 import androidx.work.WorkManager; 40 import androidx.work.WorkerParameters; 41 42 import com.android.devicelockcontroller.provision.grpc.DeviceCheckInClient; 43 import com.android.devicelockcontroller.provision.grpc.PauseDeviceProvisioningGrpcResponse; 44 import com.android.devicelockcontroller.stats.StatsLogger; 45 import com.android.devicelockcontroller.stats.StatsLoggerProvider; 46 import com.android.devicelockcontroller.util.LogUtil; 47 48 import com.google.common.util.concurrent.FutureCallback; 49 import com.google.common.util.concurrent.Futures; 50 import com.google.common.util.concurrent.ListenableFuture; 51 import com.google.common.util.concurrent.ListeningExecutorService; 52 import com.google.common.util.concurrent.MoreExecutors; 53 54 /** 55 * Despite the naming, this worker class is only to report provision has been paused by user to 56 * backend server. 57 */ 58 public final class PauseProvisioningWorker extends AbstractCheckInWorker { 59 private static final String KEY_PAUSE_DEVICE_PROVISIONING_REASON = 60 "PAUSE_DEVICE_PROVISIONING_REASON"; 61 public static final String REPORT_PROVISION_PAUSED_BY_USER_WORK = 62 "report-provision-paused-by-user"; 63 64 private final StatsLogger mStatsLogger; 65 66 /** 67 * Report provision has been paused by user to backend server by running a work item. 68 */ reportProvisionPausedByUser(WorkManager workManager)69 public static void reportProvisionPausedByUser(WorkManager workManager) { 70 Data inputData = new Data.Builder() 71 .putInt(KEY_PAUSE_DEVICE_PROVISIONING_REASON, USER_DEFERRED_DEVICE_PROVISIONING) 72 .build(); 73 NetworkRequest request = new NetworkRequest.Builder() 74 .addCapability(NET_CAPABILITY_NOT_RESTRICTED) 75 .addCapability(NET_CAPABILITY_TRUSTED) 76 .addCapability(NET_CAPABILITY_INTERNET) 77 .addCapability(NET_CAPABILITY_NOT_VPN) 78 .build(); 79 Constraints constraints = new Constraints.Builder() 80 .setRequiredNetworkRequest(request, NetworkType.CONNECTED) 81 .build(); 82 OneTimeWorkRequest work = 83 new OneTimeWorkRequest.Builder(PauseProvisioningWorker.class) 84 .setConstraints(constraints) 85 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY) 86 .setInputData(inputData) 87 .build(); 88 ListenableFuture<Operation.State.SUCCESS> result = 89 workManager.enqueueUniqueWork(REPORT_PROVISION_PAUSED_BY_USER_WORK, 90 ExistingWorkPolicy.KEEP, work).getResult(); 91 Futures.addCallback(result, 92 new FutureCallback<>() { 93 @Override 94 public void onSuccess(Operation.State.SUCCESS result) { 95 // no-op 96 } 97 98 @Override 99 public void onFailure(Throwable t) { 100 // Log an error but don't reset the device (non critical failure). 101 LogUtil.e(TAG, "Failed to enqueue 'Report provision paused' work", t); 102 } 103 }, 104 MoreExecutors.directExecutor() 105 ); 106 } 107 PauseProvisioningWorker(@onNull Context context, @NonNull WorkerParameters workerParams, ListeningExecutorService executorService)108 public PauseProvisioningWorker(@NonNull Context context, 109 @NonNull WorkerParameters workerParams, ListeningExecutorService executorService) { 110 this(context, workerParams, null, executorService); 111 } 112 113 @VisibleForTesting PauseProvisioningWorker(@onNull Context context, @NonNull WorkerParameters workerParams, DeviceCheckInClient client, ListeningExecutorService executorService)114 PauseProvisioningWorker(@NonNull Context context, @NonNull WorkerParameters workerParams, 115 DeviceCheckInClient client, ListeningExecutorService executorService) { 116 super(context, workerParams, client, executorService); 117 StatsLoggerProvider loggerProvider = 118 (StatsLoggerProvider) context.getApplicationContext(); 119 mStatsLogger = loggerProvider.getStatsLogger(); 120 } 121 122 @NonNull 123 @Override startWork()124 public ListenableFuture<Result> startWork() { 125 return Futures.transform(mClient, client -> { 126 int reason = getInputData().getInt(KEY_PAUSE_DEVICE_PROVISIONING_REASON, 127 REASON_UNSPECIFIED); 128 PauseDeviceProvisioningGrpcResponse response = client.pauseDeviceProvisioning(reason); 129 if (response.hasRecoverableError()) { 130 LogUtil.w(TAG, "Report paused provisioning failed w/ recoverable error " + response 131 + "\nRetrying..."); 132 return Result.retry(); 133 } 134 if (response.isSuccessful()) { 135 mStatsLogger.logPauseDeviceProvisioning(); 136 return Result.success(); 137 } 138 LogUtil.e(TAG, "Pause provisioning request failed: " + response); 139 return Result.failure(); 140 }, mExecutorService); 141 } 142 } 143