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.debug; 18 19 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_UNSPECIFIED; 20 import static com.android.devicelockcontroller.common.DeviceLockConstants.MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE; 21 import static com.android.devicelockcontroller.common.DeviceLockConstants.NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE; 22 import static com.android.devicelockcontroller.common.DeviceLockConstants.READY_FOR_PROVISION; 23 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.CLEARED; 24 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.LOCKED; 25 import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNLOCKED; 26 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED; 27 import static com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerImpl.PROVISION_PAUSED_MINUTES_DEFAULT; 28 import static com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerImpl.PROVISION_STATE_REPORT_INTERVAL_DEFAULT_MINUTES; 29 30 import static java.lang.annotation.RetentionPolicy.SOURCE; 31 32 import android.app.AlarmManager; 33 import android.app.PendingIntent; 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.os.Build; 38 import android.os.UserManager; 39 import android.text.TextUtils; 40 41 import androidx.annotation.StringDef; 42 import androidx.work.WorkManager; 43 44 import com.android.devicelockcontroller.FcmRegistrationTokenProvider; 45 import com.android.devicelockcontroller.WorkManagerExceptionHandler.WorkFailureAlarmReceiver; 46 import com.android.devicelockcontroller.policy.DevicePolicyController; 47 import com.android.devicelockcontroller.policy.DeviceStateController; 48 import com.android.devicelockcontroller.policy.DeviceStateController.DeviceState; 49 import com.android.devicelockcontroller.policy.PolicyObjectsProvider; 50 import com.android.devicelockcontroller.policy.ProvisionHelperImpl; 51 import com.android.devicelockcontroller.policy.ProvisionStateController; 52 import com.android.devicelockcontroller.provision.grpc.DeviceCheckInClient; 53 import com.android.devicelockcontroller.provision.grpc.DeviceFinalizeClient; 54 import com.android.devicelockcontroller.provision.worker.DeviceCheckInWorker; 55 import com.android.devicelockcontroller.provision.worker.PauseProvisioningWorker; 56 import com.android.devicelockcontroller.provision.worker.ReportDeviceLockProgramCompleteWorker; 57 import com.android.devicelockcontroller.provision.worker.ReportDeviceProvisionStateWorker; 58 import com.android.devicelockcontroller.receivers.NextProvisionFailedStepReceiver; 59 import com.android.devicelockcontroller.receivers.ResetDeviceReceiver; 60 import com.android.devicelockcontroller.receivers.ResumeProvisionReceiver; 61 import com.android.devicelockcontroller.schedule.DeviceLockControllerScheduler; 62 import com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerImpl; 63 import com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerProvider; 64 import com.android.devicelockcontroller.storage.GlobalParametersClient; 65 import com.android.devicelockcontroller.storage.SetupParametersClient; 66 import com.android.devicelockcontroller.storage.UserParameters; 67 import com.android.devicelockcontroller.util.LogUtil; 68 69 import com.google.common.util.concurrent.FutureCallback; 70 import com.google.common.util.concurrent.Futures; 71 import com.google.common.util.concurrent.ListenableFuture; 72 import com.google.common.util.concurrent.MoreExecutors; 73 74 import java.lang.annotation.Retention; 75 import java.util.Objects; 76 77 /** 78 * A {@link BroadcastReceiver} that can handle reset, lock, unlock command. 79 * <p> 80 * Note: 81 * Reboot device are {@link DeviceLockCommandReceiver#onReceive(Context, Intent)} has been called to 82 * take effect. 83 */ 84 public final class DeviceLockCommandReceiver extends BroadcastReceiver { 85 86 private static final String TAG = "DeviceLockCommandReceiver"; 87 private static final String EXTRA_COMMAND = "command"; 88 private static final String EXTRA_CHECK_IN_STATUS = "check-in-status"; 89 private static final String EXTRA_CHECK_IN_RETRY_DELAY = "check-in-retry-delay"; 90 private static final String EXTRA_FORCE_PROVISION = "force-provision"; 91 private static final String EXTRA_IS_IN_APPROVED_COUNTRY = "is-in-approved-country"; 92 private static final String EXTRA_NEXT_PROVISION_STATE = "next-provision-state"; 93 private static final String EXTRA_DAYS_LEFT_UNTIL_RESET = "days-left-until-reset"; 94 private static final String EXTRA_PAUSED_MINUTES = "paused-minutes"; 95 private static final String EXTRA_REPORT_INTERVAL_MINUTES = "report-interval-minutes"; 96 private static final String EXTRA_RESET_DEVICE_MINUTES = "reset-device-minutes"; 97 private static final String EXTRA_MANDATORY_RESET_DEVICE_MINUTES = 98 "mandatory-reset-device-minutes"; 99 private static final String EXTRA_HOST_NAME = "host-name"; 100 public static final String EXTRA_RESET_INCLUDE_SETUP_PARAMETERS_AND_DEBUG_SETUPS = 101 "include-setup-params-and-debug-setups"; 102 103 @Retention(SOURCE) 104 @StringDef({ 105 Commands.RESET, 106 Commands.LOCK, 107 Commands.UNLOCK, 108 Commands.CHECK_IN, 109 Commands.CLEAR, 110 Commands.DUMP, 111 Commands.FCM, 112 Commands.ENABLE_DEBUG_CLIENT, 113 Commands.DISABLE_DEBUG_CLIENT, 114 Commands.SET_DEBUG_CLIENT_RESPONSE, 115 Commands.DUMP_DEBUG_CLIENT_RESPONSE, 116 Commands.SET_DEBUG_CLIENT_RESPONSE, 117 Commands.DUMP_DEBUG_SCHEDULER, 118 }) 119 private @interface Commands { 120 String RESET = "reset"; 121 String LOCK = "lock"; 122 String UNLOCK = "unlock"; 123 String CHECK_IN = "check-in"; 124 String CLEAR = "clear"; 125 String DUMP = "dump"; 126 String FCM = "fcm"; 127 String ENABLE_DEBUG_CLIENT = "enable-debug-client"; 128 String DISABLE_DEBUG_CLIENT = "disable-debug-client"; 129 String SET_CHECK_IN_HOST_NAME = "set-check-in-host-name"; 130 String SET_FINALIZE_HOST_NAME = "set-finalize-host-name"; 131 String SET_DEBUG_CLIENT_RESPONSE = "set-debug-client-response"; 132 String DUMP_DEBUG_CLIENT_RESPONSE = "dump-debug-client-response"; 133 String SET_UP_DEBUG_SCHEDULER = "set-up-debug-scheduler"; 134 String DUMP_DEBUG_SCHEDULER = "dump-debug-scheduler"; 135 String ENABLE_PREINSTALLED_KIOSK = "enable-preinstalled-kiosk"; 136 String DISABLE_PREINSTALLED_KIOSK = "disable-preinstalled-kiosk"; 137 } 138 139 @Override onReceive(Context context, Intent intent)140 public void onReceive(Context context, Intent intent) { 141 if (!Build.isDebuggable()) { 142 throw new SecurityException("This should never be run in production build!"); 143 } 144 145 if (!TextUtils.equals(intent.getComponent().getClassName(), getClass().getName())) { 146 throw new IllegalArgumentException("Intent does not match this class!"); 147 } 148 149 final boolean isUserProfile = 150 context.getSystemService(UserManager.class).isProfile(); 151 if (isUserProfile) { 152 LogUtil.w(TAG, "Broadcast should not target user profiles"); 153 return; 154 } 155 156 Context appContext = context.getApplicationContext(); 157 158 ProvisionStateController provisionStateController = 159 ((PolicyObjectsProvider) appContext).getProvisionStateController(); 160 DeviceStateController deviceStateController = 161 provisionStateController.getDeviceStateController(); 162 163 @Commands 164 String command = String.valueOf(intent.getStringExtra(EXTRA_COMMAND)); 165 switch (command) { 166 case Commands.RESET: 167 forceReset(appContext, intent.getBooleanExtra( 168 EXTRA_RESET_INCLUDE_SETUP_PARAMETERS_AND_DEBUG_SETUPS, false)); 169 break; 170 case Commands.LOCK: 171 Futures.addCallback(deviceStateController.lockDevice(), 172 getSetStateCallBack(LOCKED), MoreExecutors.directExecutor()); 173 break; 174 case Commands.UNLOCK: 175 Futures.addCallback(deviceStateController.unlockDevice(), 176 getSetStateCallBack(UNLOCKED), MoreExecutors.directExecutor()); 177 break; 178 case Commands.CLEAR: 179 Futures.addCallback(deviceStateController.clearDevice(), 180 getSetStateCallBack(CLEARED), MoreExecutors.directExecutor()); 181 break; 182 case Commands.CHECK_IN: 183 tryCheckIn(appContext); 184 break; 185 case Commands.DUMP: 186 dumpStorage(context); 187 break; 188 case Commands.FCM: 189 logFcmToken(appContext); 190 break; 191 case Commands.ENABLE_DEBUG_CLIENT: 192 DeviceCheckInClientDebug.setDebugClientEnabled(context, true); 193 break; 194 case Commands.DISABLE_DEBUG_CLIENT: 195 DeviceCheckInClientDebug.setDebugClientEnabled(context, false); 196 break; 197 case Commands.SET_CHECK_IN_HOST_NAME: 198 setCheckInHostName(context, intent); 199 break; 200 case Commands.SET_FINALIZE_HOST_NAME: 201 setFinalizeHostName(context, intent); 202 break; 203 case Commands.SET_DEBUG_CLIENT_RESPONSE: 204 setDebugCheckInClientResponse(context, intent); 205 break; 206 case Commands.DUMP_DEBUG_CLIENT_RESPONSE: 207 DeviceCheckInClientDebug.dumpDebugCheckInClientResponses(context); 208 break; 209 case Commands.SET_UP_DEBUG_SCHEDULER: 210 setUpDebugScheduler(context, intent); 211 break; 212 case Commands.DUMP_DEBUG_SCHEDULER: 213 DeviceLockControllerSchedulerImpl.dumpDebugScheduler(context); 214 break; 215 case Commands.ENABLE_PREINSTALLED_KIOSK: 216 ProvisionHelperImpl.setPreinstalledKioskAllowed(context, true); 217 break; 218 case Commands.DISABLE_PREINSTALLED_KIOSK: 219 ProvisionHelperImpl.setPreinstalledKioskAllowed(context, false); 220 break; 221 default: 222 throw new IllegalArgumentException("Unsupported command: " + command); 223 } 224 } 225 setCheckInHostName(Context context, Intent intent)226 private static void setCheckInHostName(Context context, Intent intent) { 227 if (!intent.hasExtra(EXTRA_HOST_NAME)) { 228 throw new IllegalArgumentException("Missing host name override!"); 229 } 230 DeviceCheckInClient.setHostNameOverride(context, intent.getStringExtra(EXTRA_HOST_NAME)); 231 } 232 setFinalizeHostName(Context context, Intent intent)233 private static void setFinalizeHostName(Context context, Intent intent) { 234 if (!intent.hasExtra(EXTRA_HOST_NAME)) { 235 throw new IllegalArgumentException("Missing host name override!"); 236 } 237 DeviceFinalizeClient.setHostNameOverride(context, intent.getStringExtra(EXTRA_HOST_NAME)); 238 } 239 setDebugCheckInClientResponse(Context context, Intent intent)240 private static void setDebugCheckInClientResponse(Context context, Intent intent) { 241 if (intent.hasExtra(EXTRA_CHECK_IN_STATUS)) { 242 DeviceCheckInClientDebug.setDebugCheckInStatus(context, 243 intent.getIntExtra(EXTRA_CHECK_IN_STATUS, READY_FOR_PROVISION)); 244 245 } 246 if (intent.hasExtra(EXTRA_FORCE_PROVISION)) { 247 DeviceCheckInClientDebug.setDebugForceProvisioning(context, 248 intent.getBooleanExtra(EXTRA_FORCE_PROVISION, false)); 249 } 250 if (intent.hasExtra(EXTRA_IS_IN_APPROVED_COUNTRY)) { 251 DeviceCheckInClientDebug.setDebugApprovedCountry(context, 252 intent.getBooleanExtra(EXTRA_IS_IN_APPROVED_COUNTRY, true)); 253 } 254 if (intent.hasExtra(EXTRA_NEXT_PROVISION_STATE)) { 255 DeviceCheckInClientDebug.setDebugNextProvisionState(context, 256 intent.getIntExtra(EXTRA_NEXT_PROVISION_STATE, 257 PROVISION_STATE_UNSPECIFIED)); 258 } 259 if (intent.hasExtra(EXTRA_DAYS_LEFT_UNTIL_RESET)) { 260 DeviceCheckInClientDebug.setDebugDaysLeftUntilReset( 261 context, intent.getIntExtra(EXTRA_DAYS_LEFT_UNTIL_RESET, /* days_left*/ 1)); 262 } 263 if (intent.hasExtra(EXTRA_CHECK_IN_RETRY_DELAY)) { 264 DeviceCheckInClientDebug.setDebugCheckInRetryDelay(context, 265 intent.getIntExtra(EXTRA_CHECK_IN_RETRY_DELAY, /* delay_minute= */ 1)); 266 } 267 } 268 setUpDebugScheduler(Context context, Intent intent)269 private static void setUpDebugScheduler(Context context, Intent intent) { 270 if (intent.hasExtra(EXTRA_PAUSED_MINUTES)) { 271 DeviceLockControllerSchedulerImpl.setDebugProvisionPausedMinutes(context, 272 intent.getIntExtra(EXTRA_PAUSED_MINUTES, PROVISION_PAUSED_MINUTES_DEFAULT)); 273 } 274 if (intent.hasExtra(EXTRA_REPORT_INTERVAL_MINUTES)) { 275 DeviceLockControllerSchedulerImpl.setDebugReportIntervalMinutes(context, 276 intent.getLongExtra(EXTRA_REPORT_INTERVAL_MINUTES, 277 PROVISION_STATE_REPORT_INTERVAL_DEFAULT_MINUTES)); 278 } 279 if (intent.hasExtra(EXTRA_RESET_DEVICE_MINUTES)) { 280 DeviceLockControllerSchedulerImpl.setDebugResetDeviceMinutes(context, 281 intent.getIntExtra(EXTRA_RESET_DEVICE_MINUTES, 282 NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE)); 283 } 284 if (intent.hasExtra(EXTRA_MANDATORY_RESET_DEVICE_MINUTES)) { 285 DeviceLockControllerSchedulerImpl.setDebugMandatoryResetDeviceMinutes(context, 286 intent.getIntExtra(EXTRA_MANDATORY_RESET_DEVICE_MINUTES, 287 MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE)); 288 } 289 } 290 dumpStorage(Context context)291 private static void dumpStorage(Context context) { 292 Futures.addCallback( 293 Futures.transformAsync(SetupParametersClient.getInstance().dump(), 294 unused -> GlobalParametersClient.getInstance().dump(), 295 MoreExecutors.directExecutor()), 296 new FutureCallback<>() { 297 @Override 298 public void onSuccess(Void result) { 299 UserParameters.dump(context); 300 LogUtil.i(TAG, "Successfully dumped storage"); 301 } 302 303 @Override 304 public void onFailure(Throwable t) { 305 LogUtil.e(TAG, "Error encountered when dumping storage", t); 306 } 307 }, MoreExecutors.directExecutor()); 308 } 309 tryCheckIn(Context appContext)310 private static void tryCheckIn(Context appContext) { 311 if (!appContext.getSystemService(UserManager.class).isSystemUser()) { 312 LogUtil.e(TAG, "Only system user can perform a check-in"); 313 return; 314 } 315 DeviceLockControllerSchedulerProvider schedulerProvider = 316 (DeviceLockControllerSchedulerProvider) appContext; 317 DeviceLockControllerScheduler scheduler = 318 schedulerProvider.getDeviceLockControllerScheduler(); 319 320 Futures.addCallback(GlobalParametersClient.getInstance().isProvisionReady(), 321 new FutureCallback<>() { 322 @Override 323 public void onSuccess(Boolean provisioningInfoReady) { 324 if (!provisioningInfoReady) { 325 scheduler.scheduleInitialCheckInWork(); 326 } else { 327 LogUtil.e(TAG, 328 "Can not check in when provisioning info has already been " 329 + "received. Use the \"reset\" command to reset " 330 + "DLC first."); 331 } 332 } 333 334 @Override 335 public void onFailure(Throwable t) { 336 LogUtil.e(TAG, "Failed to determine if provisioning info is ready", t); 337 } 338 }, MoreExecutors.directExecutor()); 339 } 340 forceReset(Context context, boolean shouldCleanSetupParametersAndDebugSetups)341 private static void forceReset(Context context, 342 boolean shouldCleanSetupParametersAndDebugSetups) { 343 // Cancel provision works 344 LogUtil.d(TAG, "cancelling works"); 345 WorkManager workManager = WorkManager.getInstance(context); 346 workManager.cancelAllWorkByTag(DeviceCheckInWorker.class.getName()); 347 workManager.cancelAllWorkByTag(PauseProvisioningWorker.class.getName()); 348 workManager.cancelAllWorkByTag( 349 ReportDeviceProvisionStateWorker.class.getName()); 350 workManager.cancelAllWorkByTag( 351 ReportDeviceLockProgramCompleteWorker.class.getName()); 352 353 // Cancel All alarms 354 AlarmManager alarmManager = Objects.requireNonNull( 355 context.getSystemService(AlarmManager.class)); 356 alarmManager.cancel( 357 PendingIntent.getBroadcast( 358 context, /* ignored */ 0, 359 new Intent(context, ResetDeviceReceiver.class), 360 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)); 361 alarmManager.cancel(PendingIntent.getBroadcast( 362 context, /* ignored */ 0, 363 new Intent(context, NextProvisionFailedStepReceiver.class), 364 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)); 365 alarmManager.cancel(PendingIntent.getBroadcast( 366 context, /* ignored */ 0, 367 new Intent(context, ResumeProvisionReceiver.class), 368 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)); 369 alarmManager.cancel(PendingIntent.getBroadcast( 370 context, /* ignored */ 0, 371 new Intent(context, NextProvisionFailedStepReceiver.class), 372 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)); 373 alarmManager.cancel(PendingIntent.getBroadcast( 374 context, /* ignored */ 0, 375 new Intent(context, WorkFailureAlarmReceiver.class), 376 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)); 377 378 PolicyObjectsProvider policyObjectsProvider = 379 (PolicyObjectsProvider) context.getApplicationContext(); 380 policyObjectsProvider.destroyObjects(); 381 UserParameters.setProvisionState(context, PROVISION_SUCCEEDED); 382 GlobalParametersClient.getInstance().setDeviceState(CLEARED); 383 DevicePolicyController policyController = policyObjectsProvider.getPolicyController(); 384 ListenableFuture<Void> clearPolicies = Futures.catching( 385 policyController.enforceCurrentPolicies(), 386 RuntimeException.class, unused -> null, 387 MoreExecutors.directExecutor()); 388 Futures.addCallback(Futures.transformAsync(clearPolicies, 389 unused -> clearStorage(context, shouldCleanSetupParametersAndDebugSetups), 390 MoreExecutors.directExecutor()), 391 new FutureCallback<>() { 392 @Override 393 public void onSuccess(Void result) { 394 LogUtil.i(TAG, "Reset device state."); 395 } 396 397 @Override 398 public void onFailure(Throwable t) { 399 throw new RuntimeException(t); 400 } 401 }, MoreExecutors.directExecutor()); 402 } 403 logFcmToken(Context appContext)404 private static void logFcmToken(Context appContext) { 405 final ListenableFuture<String> fcmRegistrationToken = 406 ((FcmRegistrationTokenProvider) appContext).getFcmRegistrationToken(); 407 Futures.addCallback(fcmRegistrationToken, new FutureCallback<>() { 408 @Override 409 public void onSuccess(String token) { 410 LogUtil.i(TAG, 411 "FCM Registration Token: " + (token == null ? "Not set" : token)); 412 } 413 414 @Override 415 public void onFailure(Throwable t) { 416 LogUtil.e(TAG, "Unable to get FCM registration token", t); 417 } 418 }, MoreExecutors.directExecutor()); 419 } 420 getSetStateCallBack(@eviceState int state)421 private static FutureCallback<Void> getSetStateCallBack(@DeviceState int state) { 422 423 return new FutureCallback<>() { 424 425 @Override 426 public void onSuccess(Void unused) { 427 LogUtil.i(TAG, "Successfully set state to: " + state); 428 } 429 430 @Override 431 public void onFailure(Throwable t) { 432 LogUtil.e(TAG, "Unsuccessfully set state to: " + state, t); 433 } 434 }; 435 } 436 437 private static ListenableFuture<Void> clearStorage(Context context, 438 boolean shouldCleanSetupParametersAndDebugSetups) { 439 if (shouldCleanSetupParametersAndDebugSetups) { 440 DeviceCheckInClientDebug.clear(context); 441 DeviceLockControllerSchedulerImpl.clear(context); 442 } 443 UserParameters.clear(context); 444 return Futures.whenAllSucceed( 445 shouldCleanSetupParametersAndDebugSetups 446 ? SetupParametersClient.getInstance().clear() 447 : Futures.immediateVoidFuture(), 448 GlobalParametersClient.getInstance().clear()) 449 .call(() -> { 450 ((PolicyObjectsProvider) context.getApplicationContext()).destroyObjects(); 451 return null; 452 }, context.getMainExecutor()); 453 } 454 } 455