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.DeviceProvisionState.PROVISION_STATE_DISMISSIBLE_UI; 25 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_FACTORY_RESET; 26 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_PERSISTENT_UI; 27 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_RETRY; 28 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_SUCCESS; 29 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_UNSPECIFIED; 30 import static com.android.devicelockcontroller.common.DeviceLockConstants.ProvisionFailureReason.DEADLINE_PASSED; 31 32 import android.content.Context; 33 import android.net.NetworkRequest; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.VisibleForTesting; 37 import androidx.work.BackoffPolicy; 38 import androidx.work.Constraints; 39 import androidx.work.Data; 40 import androidx.work.ExistingWorkPolicy; 41 import androidx.work.NetworkType; 42 import androidx.work.OneTimeWorkRequest; 43 import androidx.work.Operation; 44 import androidx.work.WorkManager; 45 import androidx.work.WorkerParameters; 46 47 import com.android.devicelockcontroller.activities.DeviceLockNotificationManager; 48 import com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState; 49 import com.android.devicelockcontroller.common.DeviceLockConstants.ProvisionFailureReason; 50 import com.android.devicelockcontroller.provision.grpc.DeviceCheckInClient; 51 import com.android.devicelockcontroller.provision.grpc.ReportDeviceProvisionStateGrpcResponse; 52 import com.android.devicelockcontroller.schedule.DeviceLockControllerScheduler; 53 import com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerProvider; 54 import com.android.devicelockcontroller.stats.StatsLogger; 55 import com.android.devicelockcontroller.stats.StatsLoggerProvider; 56 import com.android.devicelockcontroller.storage.GlobalParametersClient; 57 import com.android.devicelockcontroller.storage.SetupParametersClient; 58 import com.android.devicelockcontroller.storage.UserParameters; 59 import com.android.devicelockcontroller.util.LogUtil; 60 61 import com.google.common.util.concurrent.FutureCallback; 62 import com.google.common.util.concurrent.Futures; 63 import com.google.common.util.concurrent.ListenableFuture; 64 import com.google.common.util.concurrent.ListeningExecutorService; 65 import com.google.common.util.concurrent.MoreExecutors; 66 67 /** 68 * A worker class dedicated to report state of provision for the device lock program. 69 */ 70 public final class ReportDeviceProvisionStateWorker extends AbstractCheckInWorker { 71 public static final String KEY_IS_PROVISION_SUCCESSFUL = "is-provision-successful"; 72 public static final String KEY_PROVISION_FAILURE_REASON = "provision-failure-reason"; 73 public static final String REPORT_PROVISION_STATE_WORK_NAME = "report-provision-state"; 74 @VisibleForTesting 75 static final String UNEXPECTED_PROVISION_STATE_ERROR_MESSAGE = "Unexpected provision state!"; 76 77 private final StatsLogger mStatsLogger; 78 79 /** 80 * Report provision failure and get next failed step 81 */ reportSetupFailed(WorkManager workManager, @ProvisionFailureReason int reason)82 public static void reportSetupFailed(WorkManager workManager, 83 @ProvisionFailureReason int reason) { 84 Data inputData = new Data.Builder() 85 .putBoolean(KEY_IS_PROVISION_SUCCESSFUL, false) 86 .putInt(KEY_PROVISION_FAILURE_REASON, reason) 87 .build(); 88 enqueueReportWork(inputData, workManager); 89 } 90 91 /** 92 * Report provision success 93 */ reportSetupCompleted(WorkManager workManager)94 public static void reportSetupCompleted(WorkManager workManager) { 95 Data inputData = new Data.Builder() 96 .putBoolean(KEY_IS_PROVISION_SUCCESSFUL, true) 97 .build(); 98 enqueueReportWork(inputData, workManager); 99 } 100 101 /** 102 * Schedule a work to report the current provision failed step to server. 103 */ reportCurrentFailedStep(WorkManager workManager)104 public static void reportCurrentFailedStep(WorkManager workManager) { 105 Data inputData = new Data.Builder() 106 .putBoolean(KEY_IS_PROVISION_SUCCESSFUL, false) 107 .putInt(KEY_PROVISION_FAILURE_REASON, DEADLINE_PASSED) 108 .build(); 109 enqueueReportWork(inputData, workManager); 110 } 111 enqueueReportWork(Data inputData, WorkManager workManager)112 private static void enqueueReportWork(Data inputData, WorkManager workManager) { 113 NetworkRequest request = new NetworkRequest.Builder() 114 .addCapability(NET_CAPABILITY_NOT_RESTRICTED) 115 .addCapability(NET_CAPABILITY_TRUSTED) 116 .addCapability(NET_CAPABILITY_INTERNET) 117 .addCapability(NET_CAPABILITY_NOT_VPN) 118 .build(); 119 Constraints constraints = new Constraints.Builder() 120 .setRequiredNetworkRequest(request, NetworkType.CONNECTED) 121 .build(); 122 OneTimeWorkRequest work = 123 new OneTimeWorkRequest.Builder(ReportDeviceProvisionStateWorker.class) 124 .setConstraints(constraints) 125 .setInputData(inputData) 126 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY) 127 .build(); 128 ListenableFuture<Operation.State.SUCCESS> result = 129 workManager.enqueueUniqueWork(REPORT_PROVISION_STATE_WORK_NAME, 130 ExistingWorkPolicy.APPEND_OR_REPLACE, work).getResult(); 131 Futures.addCallback(result, 132 new FutureCallback<>() { 133 @Override 134 public void onSuccess(Operation.State.SUCCESS result) { 135 // no-op 136 } 137 138 @Override 139 public void onFailure(Throwable t) { 140 // Log an error but don't reset the device (non critical failure). 141 LogUtil.e(TAG, "Failed to enqueue 'report provision state' work", t); 142 } 143 }, 144 MoreExecutors.directExecutor() 145 ); 146 } 147 ReportDeviceProvisionStateWorker(@onNull Context context, @NonNull WorkerParameters workerParams, ListeningExecutorService executorService)148 public ReportDeviceProvisionStateWorker(@NonNull Context context, 149 @NonNull WorkerParameters workerParams, ListeningExecutorService executorService) { 150 this(context, workerParams, /* client= */ null, 151 executorService); 152 } 153 154 @VisibleForTesting ReportDeviceProvisionStateWorker(@onNull Context context, @NonNull WorkerParameters workerParams, DeviceCheckInClient client, ListeningExecutorService executorService)155 ReportDeviceProvisionStateWorker(@NonNull Context context, 156 @NonNull WorkerParameters workerParams, DeviceCheckInClient client, 157 ListeningExecutorService executorService) { 158 super(context, workerParams, client, executorService); 159 StatsLoggerProvider loggerProvider = 160 (StatsLoggerProvider) context.getApplicationContext(); 161 mStatsLogger = loggerProvider.getStatsLogger(); 162 } 163 164 @NonNull 165 @Override startWork()166 public ListenableFuture<Result> startWork() { 167 GlobalParametersClient globalParametersClient = GlobalParametersClient.getInstance(); 168 ListenableFuture<Integer> lastState = 169 globalParametersClient.getLastReceivedProvisionState(); 170 ListenableFuture<Boolean> isMandatory = 171 SetupParametersClient.getInstance().isProvisionMandatory(); 172 DeviceLockControllerSchedulerProvider schedulerProvider = 173 (DeviceLockControllerSchedulerProvider) mContext; 174 DeviceLockControllerScheduler scheduler = 175 schedulerProvider.getDeviceLockControllerScheduler(); 176 return Futures.whenAllSucceed(mClient, lastState, isMandatory).call(() -> { 177 boolean isSuccessful = getInputData().getBoolean( 178 KEY_IS_PROVISION_SUCCESSFUL, /* defaultValue= */ false); 179 int failureReason = getInputData().getInt(KEY_PROVISION_FAILURE_REASON, 180 ProvisionFailureReason.UNKNOWN_REASON); 181 if (!isSuccessful && failureReason == ProvisionFailureReason.UNKNOWN_REASON) { 182 LogUtil.e(TAG, "Reporting failure with an unknown reason is not allowed"); 183 } 184 ReportDeviceProvisionStateGrpcResponse response = 185 Futures.getDone(mClient).reportDeviceProvisionState( 186 Futures.getDone(lastState), 187 isSuccessful, 188 failureReason); 189 if (response.hasRecoverableError()) { 190 LogUtil.w(TAG, "Report provision state failed w/ recoverable error " + response 191 + "\nRetrying..."); 192 return Result.retry(); 193 } 194 if (response.hasFatalError()) { 195 LogUtil.e(TAG, 196 "Report provision state failed: " + response + "\nRetry current step"); 197 scheduler.scheduleNextProvisionFailedStepAlarm(); 198 return Result.failure(); 199 } 200 int daysLeftUntilReset = response.getDaysLeftUntilReset(); 201 if (daysLeftUntilReset > 0) { 202 UserParameters.setDaysLeftUntilReset(mContext, daysLeftUntilReset); 203 } 204 int nextState = response.getNextClientProvisionState(); 205 Futures.getUnchecked(globalParametersClient.setLastReceivedProvisionState(nextState)); 206 mStatsLogger.logReportDeviceProvisionState(); 207 if (!Futures.getDone(isMandatory)) { 208 onNextProvisionStateReceived(nextState, daysLeftUntilReset); 209 if (nextState == PROVISION_STATE_FACTORY_RESET) { 210 scheduler.scheduleResetDeviceAlarm(); 211 } else if (nextState != PROVISION_STATE_SUCCESS) { 212 scheduler.scheduleNextProvisionFailedStepAlarm(); 213 } 214 } 215 return Result.success(); 216 }, mExecutorService); 217 } 218 onNextProvisionStateReceived(@eviceProvisionState int provisionState, int daysLeftUntilReset)219 private void onNextProvisionStateReceived(@DeviceProvisionState int provisionState, 220 int daysLeftUntilReset) { 221 switch (provisionState) { 222 case PROVISION_STATE_RETRY: 223 case PROVISION_STATE_SUCCESS: 224 case PROVISION_STATE_UNSPECIFIED: 225 case PROVISION_STATE_FACTORY_RESET: 226 // no-op 227 break; 228 case PROVISION_STATE_DISMISSIBLE_UI: 229 DeviceLockNotificationManager.getInstance() 230 .sendDeviceResetNotification(mContext, daysLeftUntilReset); 231 break; 232 case PROVISION_STATE_PERSISTENT_UI: 233 DeviceLockNotificationManager.getInstance() 234 .sendDeviceResetInOneDayOngoingNotification(mContext); 235 break; 236 default: 237 throw new IllegalStateException(UNEXPECTED_PROVISION_STATE_ERROR_MESSAGE); 238 } 239 } 240 } 241