1 /* 2 * Copyright (C) 2024 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.services; 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.SETUP_WIZARD_TIMEOUT_JOB_ID; 25 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent.PROVISION_READY; 26 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED; 27 28 import android.app.job.JobInfo; 29 import android.app.job.JobParameters; 30 import android.app.job.JobScheduler; 31 import android.app.job.JobService; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.net.NetworkRequest; 35 import android.provider.Settings; 36 37 import com.android.devicelockcontroller.policy.PolicyObjectsProvider; 38 import com.android.devicelockcontroller.policy.ProvisionStateController; 39 import com.android.devicelockcontroller.storage.GlobalParametersClient; 40 import com.android.devicelockcontroller.storage.UserParameters; 41 import com.android.devicelockcontroller.util.LogUtil; 42 43 import com.google.common.annotations.VisibleForTesting; 44 import com.google.common.util.concurrent.FutureCallback; 45 import com.google.common.util.concurrent.Futures; 46 import com.google.common.util.concurrent.ListenableFuture; 47 import com.google.common.util.concurrent.ListeningExecutorService; 48 import com.google.common.util.concurrent.MoreExecutors; 49 50 import java.util.concurrent.Executors; 51 import java.util.concurrent.TimeUnit; 52 53 public final class SetupWizardCompletionTimeoutJobService extends JobService { 54 private static final String TAG = "SetupWizardCompletionTimeoutJobService"; 55 private static final long TIMEOUT_MINUTES = 60; 56 57 private final ListeningExecutorService mListeningExecutorService = 58 MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); 59 private final Context mContext; 60 61 @VisibleForTesting 62 ListenableFuture<Void> mFuture; 63 64 /** 65 * Create an instance of the job. 66 */ SetupWizardCompletionTimeoutJobService()67 public SetupWizardCompletionTimeoutJobService() { 68 super(); 69 mContext = this; 70 } 71 72 @VisibleForTesting SetupWizardCompletionTimeoutJobService(Context context)73 SetupWizardCompletionTimeoutJobService(Context context) { 74 super(); 75 mContext = context; 76 } 77 78 @Override onStartJob(JobParameters params)79 public boolean onStartJob(JobParameters params) { 80 LogUtil.i(TAG, "Starting job"); 81 82 // If SUW is already finished, there's nothing left to do. 83 if (isUserSetupComplete()) { 84 return false; 85 } 86 87 // SUW did not finished in the allotted time. If the device is still unprovisioned 88 // and provisioning information is ready, start the provisioning flow. 89 Context appContext = mContext.getApplicationContext(); 90 PolicyObjectsProvider policyObjects = 91 (PolicyObjectsProvider) appContext; 92 ProvisionStateController provisionStateController = 93 policyObjects.getProvisionStateController(); 94 95 mFuture = Futures.transformAsync(provisionStateController.getState(), 96 state -> { 97 UserParameters.setSetupWizardTimedOut(appContext); 98 99 if (state != UNPROVISIONED) { 100 return Futures.immediateVoidFuture(); 101 } 102 103 GlobalParametersClient globalParametersClient = 104 GlobalParametersClient.getInstance(); 105 return Futures.transformAsync(globalParametersClient.isProvisionReady(), 106 isReady -> { 107 if (isReady) { 108 LogUtil.i(TAG, "Starting provisioning flow since " 109 + "SUW did not complete in " + TIMEOUT_MINUTES 110 + " minutes"); 111 return Futures.transform(provisionStateController 112 .setNextStateForEvent(PROVISION_READY), 113 unused -> null, 114 mListeningExecutorService); 115 } 116 return Futures.immediateVoidFuture(); 117 }, mListeningExecutorService); 118 }, mListeningExecutorService); 119 120 Futures.addCallback(mFuture, new FutureCallback<>() { 121 @Override 122 public void onSuccess(Void result) { 123 LogUtil.i(TAG, "Job completed"); 124 125 jobFinished(params, /* wantsReschedule= */ false); 126 } 127 128 @Override 129 public void onFailure(Throwable t) { 130 LogUtil.e(TAG, "Job failed", t); 131 132 jobFinished(params, /* wantsReschedule= */ true); 133 } 134 }, mListeningExecutorService); 135 136 return true; 137 } 138 139 @Override onStopJob(JobParameters params)140 public boolean onStopJob(JobParameters params) { 141 LogUtil.i(TAG, "Stopping job"); 142 143 if (mFuture != null) { 144 mFuture.cancel(true); 145 } 146 147 return true; 148 } 149 150 /** 151 * Schedule a job that starts the provisioning flow in case SetupWizard does not complete 152 * in the allotted time. 153 */ scheduleSetupWizardCompletionTimeoutJob(Context context)154 public static void scheduleSetupWizardCompletionTimeoutJob(Context context) { 155 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 156 157 if (jobScheduler.getPendingJob(SETUP_WIZARD_TIMEOUT_JOB_ID) != null) { 158 LogUtil.w(TAG, "Job already scheduled"); 159 160 return; 161 } 162 163 ComponentName componentName = 164 new ComponentName(context, SetupWizardCompletionTimeoutJobService.class); 165 long delayMillis = TimeUnit.MINUTES.toMillis(TIMEOUT_MINUTES); 166 167 NetworkRequest request = new NetworkRequest.Builder() 168 .addCapability(NET_CAPABILITY_NOT_RESTRICTED) 169 .addCapability(NET_CAPABILITY_TRUSTED) 170 .addCapability(NET_CAPABILITY_INTERNET) 171 .addCapability(NET_CAPABILITY_NOT_VPN) 172 .build(); 173 174 JobInfo jobInfo = new JobInfo.Builder(SETUP_WIZARD_TIMEOUT_JOB_ID, componentName) 175 .setMinimumLatency(delayMillis) 176 .setRequiredNetwork(request) 177 .build(); 178 179 int schedulingResult = jobScheduler.schedule(jobInfo); 180 181 if (schedulingResult == JobScheduler.RESULT_SUCCESS) { 182 LogUtil.i(TAG, "Job scheduled"); 183 } else { 184 LogUtil.e(TAG, "Failed to schedule job"); 185 } 186 } 187 188 /** 189 * Cancel the job that starts the provisioning flow if SetupWizard does not complete in 190 * the allotted time. 191 */ cancelSetupWizardCompletionTimeoutJob(Context context)192 public static void cancelSetupWizardCompletionTimeoutJob(Context context) { 193 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 194 195 jobScheduler.cancel(SETUP_WIZARD_TIMEOUT_JOB_ID); 196 197 LogUtil.i(TAG, "Job cancelled"); 198 } 199 isUserSetupComplete()200 private boolean isUserSetupComplete() { 201 return Settings.Secure.getInt( 202 mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; 203 } 204 } 205