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