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.schedule; 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.WorkManagerExceptionHandler.AlarmReason; 25 import static com.android.devicelockcontroller.common.DeviceLockConstants.MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE; 26 import static com.android.devicelockcontroller.common.DeviceLockConstants.NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE; 27 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_FAILED; 28 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_PAUSED; 29 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED; 30 import static com.android.devicelockcontroller.provision.worker.AbstractCheckInWorker.BACKOFF_DELAY; 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.content.SharedPreferences; 38 import android.net.NetworkRequest; 39 import android.os.Build; 40 import android.os.SystemClock; 41 42 import androidx.annotation.VisibleForTesting; 43 import androidx.work.BackoffPolicy; 44 import androidx.work.Constraints; 45 import androidx.work.ExistingWorkPolicy; 46 import androidx.work.NetworkType; 47 import androidx.work.OneTimeWorkRequest; 48 import androidx.work.Operation; 49 import androidx.work.OutOfQuotaPolicy; 50 import androidx.work.WorkManager; 51 52 import com.android.devicelockcontroller.DeviceLockControllerApplication; 53 import com.android.devicelockcontroller.WorkManagerExceptionHandler; 54 import com.android.devicelockcontroller.activities.DeviceLockNotificationManager; 55 import com.android.devicelockcontroller.policy.ProvisionStateController; 56 import com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState; 57 import com.android.devicelockcontroller.provision.worker.DeviceCheckInWorker; 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.storage.GlobalParametersClient; 62 import com.android.devicelockcontroller.storage.UserParameters; 63 import com.android.devicelockcontroller.util.LogUtil; 64 import com.android.devicelockcontroller.util.ThreadUtils; 65 66 import com.google.common.base.Function; 67 import com.google.common.util.concurrent.FluentFuture; 68 import com.google.common.util.concurrent.FutureCallback; 69 import com.google.common.util.concurrent.Futures; 70 import com.google.common.util.concurrent.ListenableFuture; 71 import com.google.common.util.concurrent.MoreExecutors; 72 73 import java.time.Clock; 74 import java.time.Duration; 75 import java.time.Instant; 76 import java.util.Objects; 77 import java.util.concurrent.Executor; 78 import java.util.concurrent.TimeUnit; 79 80 /** 81 * Implementation of {@link DeviceLockControllerScheduler}. 82 * WARNING: Do not create an instance directly, instead you should retrieve it using the 83 * {@link DeviceLockControllerApplication#getDeviceLockControllerScheduler()} API. 84 */ 85 public final class DeviceLockControllerSchedulerImpl implements DeviceLockControllerScheduler { 86 private static final String TAG = "DeviceLockControllerSchedulerImpl"; 87 private static final String FILENAME = "device-lock-controller-scheduler-preferences"; 88 public static final String DEVICE_CHECK_IN_WORK_NAME = "device-check-in"; 89 private static final String DEBUG_DEVICELOCK_PAUSED_MINUTES = "debug.devicelock.paused-minutes"; 90 private static final String DEBUG_DEVICELOCK_REPORT_INTERVAL_MINUTES = 91 "debug.devicelock.report-interval-minutes"; 92 private static final String DEBUG_DEVICELOCK_RESET_DEVICE_MINUTES = 93 "debug.devicelock.reset-device-minutes"; 94 private static final String DEBUG_DEVICELOCK_MANDATORY_RESET_DEVICE_MINUTES = 95 "debug.devicelock.mandatory-reset-device-minutes"; 96 97 // The default minute value of the duration that provision UI can be paused. 98 public static final int PROVISION_PAUSED_MINUTES_DEFAULT = 60; 99 // The default minute value of the interval between steps of provision failed flow. 100 public static final long PROVISION_STATE_REPORT_INTERVAL_DEFAULT_MINUTES = 101 TimeUnit.DAYS.toMinutes(1); 102 private final Context mContext; 103 private final Clock mClock; 104 private final Executor mSequentialExecutor; 105 private final ProvisionStateController mProvisionStateController; 106 107 private static volatile SharedPreferences sSharedPreferences; 108 getSharedPreferences( Context context)109 private static synchronized SharedPreferences getSharedPreferences( 110 Context context) { 111 if (sSharedPreferences == null) { 112 sSharedPreferences = context.createDeviceProtectedStorageContext().getSharedPreferences( 113 FILENAME, 114 Context.MODE_PRIVATE); 115 } 116 return sSharedPreferences; 117 } 118 119 /** 120 * Set how long provision should be paused after user hit the "Do it in 1 hour" button, in 121 * minutes. 122 */ setDebugProvisionPausedMinutes(Context context, int minutes)123 public static void setDebugProvisionPausedMinutes(Context context, int minutes) { 124 getSharedPreferences(context).edit().putInt(DEBUG_DEVICELOCK_PAUSED_MINUTES, 125 minutes).apply(); 126 } 127 128 /** 129 * Set the length of the interval of provisioning failure reporting for debugging purpose. 130 */ setDebugReportIntervalMinutes(Context context, long minutes)131 public static void setDebugReportIntervalMinutes(Context context, long minutes) { 132 getSharedPreferences(context).edit().putLong(DEBUG_DEVICELOCK_REPORT_INTERVAL_MINUTES, 133 minutes).apply(); 134 } 135 136 /** 137 * Set the length of the countdown minutes when device is about to factory reset in 138 * non-mandatory provisioning case for debugging purpose. 139 */ setDebugResetDeviceMinutes(Context context, int minutes)140 public static void setDebugResetDeviceMinutes(Context context, int minutes) { 141 getSharedPreferences(context).edit().putInt(DEBUG_DEVICELOCK_RESET_DEVICE_MINUTES, 142 minutes).apply(); 143 } 144 145 /** 146 * Set the length of the countdown minutes when device is about to factory reset in mandatory 147 * provisioning case for debugging purpose. 148 */ setDebugMandatoryResetDeviceMinutes(Context context, int minutes)149 public static void setDebugMandatoryResetDeviceMinutes(Context context, int minutes) { 150 getSharedPreferences(context).edit().putInt(DEBUG_DEVICELOCK_MANDATORY_RESET_DEVICE_MINUTES, 151 minutes).apply(); 152 } 153 154 /** 155 * Dump current debugging setup to logcat. 156 */ dumpDebugScheduler(Context context)157 public static void dumpDebugScheduler(Context context) { 158 LogUtil.d(TAG, 159 "Current Debug Scheduler setups:\n" + getSharedPreferences(context).getAll()); 160 } 161 162 /** 163 * Clear current debugging setup. 164 */ clear(Context context)165 public static void clear(Context context) { 166 getSharedPreferences(context).edit().clear().apply(); 167 } 168 DeviceLockControllerSchedulerImpl(Context context, ProvisionStateController provisionStateController)169 public DeviceLockControllerSchedulerImpl(Context context, 170 ProvisionStateController provisionStateController) { 171 this(context, Clock.systemUTC(), provisionStateController); 172 } 173 174 @VisibleForTesting DeviceLockControllerSchedulerImpl(Context context, Clock clock, ProvisionStateController provisionStateController)175 DeviceLockControllerSchedulerImpl(Context context, Clock clock, 176 ProvisionStateController provisionStateController) { 177 mContext = context; 178 mProvisionStateController = provisionStateController; 179 mClock = clock; 180 mSequentialExecutor = ThreadUtils.getSequentialSchedulerExecutor(); 181 } 182 183 @Override notifyTimeChanged()184 public void notifyTimeChanged() { 185 Futures.addCallback(mProvisionStateController.getState(), 186 new FutureCallback<>() { 187 @Override 188 public void onSuccess(@ProvisionState Integer currentState) { 189 correctStoredTime(currentState); 190 } 191 192 @Override 193 public void onFailure(Throwable t) { 194 throw new RuntimeException(t); 195 } 196 }, mSequentialExecutor); 197 } 198 199 /** 200 * Correct the stored time for when a scheduled work/alarm should execute based on the 201 * difference between current time and stored time. 202 * 203 * @param currentState The current {@link ProvisionState} used to determine which work/alarm may 204 * be possibly scheduled. 205 */ 206 @VisibleForTesting correctStoredTime(@rovisionState Integer currentState)207 void correctStoredTime(@ProvisionState Integer currentState) { 208 long bootTimestamp = UserParameters.getBootTimeMillis(mContext); 209 long delta = 210 mClock.millis() - (bootTimestamp + SystemClock.elapsedRealtime()); 211 UserParameters.setBootTimeMillis(mContext, 212 UserParameters.getBootTimeMillis(mContext) + delta); 213 if (currentState == UNPROVISIONED) { 214 long before = UserParameters.getNextCheckInTimeMillis(mContext); 215 if (before > 0) { 216 UserParameters.setNextCheckInTimeMillis(mContext, 217 before + delta); 218 } 219 // We have to reschedule (update) the check-in work, because, otherwise, if device 220 // reboots, WorkManager will reschedule the work based on the changed system clock, 221 // which will result in inaccurate schedule. (see b/285221785) 222 rescheduleRetryCheckInWork(); 223 } else if (currentState == PROVISION_PAUSED) { 224 long before = UserParameters.getResumeProvisionTimeMillis(mContext); 225 if (before > 0) { 226 UserParameters.setResumeProvisionTimeMillis(mContext, 227 before + delta); 228 } 229 } else if (currentState == PROVISION_FAILED) { 230 long before = UserParameters.getNextProvisionFailedStepTimeMills( 231 mContext); 232 if (before > 0) { 233 UserParameters.setNextProvisionFailedStepTimeMills(mContext, 234 before + delta); 235 } 236 before = UserParameters.getResetDeviceTimeMillis(mContext); 237 if (before > 0) { 238 UserParameters.setResetDeviceTimeMillis(mContext, 239 before + delta); 240 } 241 } 242 } 243 244 @Override scheduleResumeProvisionAlarm()245 public void scheduleResumeProvisionAlarm() { 246 Duration delay = Duration.ofMinutes(PROVISION_PAUSED_MINUTES_DEFAULT); 247 if (Build.isDebuggable()) { 248 delay = Duration.ofMinutes( 249 getSharedPreferences(mContext).getInt(DEBUG_DEVICELOCK_PAUSED_MINUTES, 250 PROVISION_PAUSED_MINUTES_DEFAULT)); 251 } 252 LogUtil.i(TAG, "Scheduling resume provision work with delay: " + delay); 253 scheduleResumeProvisionAlarm(delay); 254 Instant whenExpectedToRun = Instant.now(mClock).plus(delay); 255 UserParameters.setResumeProvisionTimeMillis(mContext, 256 whenExpectedToRun.toEpochMilli()); 257 } 258 259 @Override notifyRebootWhenProvisionPaused()260 public void notifyRebootWhenProvisionPaused() { 261 dispatchFuture(this::rescheduleResumeProvisionAlarmIfNeeded, 262 "notifyRebootWhenProvisionPaused"); 263 } 264 265 @Override scheduleInitialCheckInWork()266 public ListenableFuture<Void> scheduleInitialCheckInWork() { 267 LogUtil.i(TAG, "Scheduling initial check-in work"); 268 final Operation operation = 269 enqueueCheckInWorkRequest(/* isExpedited= */ true, Duration.ZERO); 270 final ListenableFuture<Operation.State.SUCCESS> result = operation.getResult(); 271 272 return FluentFuture.from(result) 273 .transform((Function<Operation.State.SUCCESS, Void>) ignored -> { 274 UserParameters.initialCheckInScheduled(mContext); 275 return null; 276 }, mSequentialExecutor) 277 .catching(Throwable.class, (e) -> { 278 LogUtil.e(TAG, "Failed to enqueue initial check in work", e); 279 WorkManagerExceptionHandler.scheduleAlarm(mContext, 280 AlarmReason.INITIAL_CHECK_IN); 281 throw new RuntimeException(e); 282 }, mSequentialExecutor); 283 } 284 285 @Override scheduleRetryCheckInWork(Duration delay)286 public ListenableFuture<Void> scheduleRetryCheckInWork(Duration delay) { 287 LogUtil.i(TAG, "Scheduling retry check-in work with delay: " + delay); 288 final Operation operation = 289 enqueueCheckInWorkRequest(/* isExpedited= */ false, delay); 290 final ListenableFuture<Operation.State.SUCCESS> result = operation.getResult(); 291 292 return FluentFuture.from(result) 293 .transform((Function<Operation.State.SUCCESS, Void>) ignored -> { 294 Instant whenExpectedToRun = Instant.now(mClock).plus(delay); 295 UserParameters.setNextCheckInTimeMillis(mContext, 296 whenExpectedToRun.toEpochMilli()); 297 return null; 298 }, mSequentialExecutor) 299 .catching(Throwable.class, (e) -> { 300 LogUtil.e(TAG, "Failed to enqueue retry check in work", e); 301 WorkManagerExceptionHandler.scheduleAlarm(mContext, 302 AlarmReason.RETRY_CHECK_IN); 303 throw new RuntimeException(e); 304 }, mSequentialExecutor); 305 } 306 307 @Override 308 public ListenableFuture<Void> notifyNeedRescheduleCheckIn() { 309 final ListenableFuture<Void> result = 310 Futures.submit(this::rescheduleRetryCheckInWork, mSequentialExecutor); 311 Futures.addCallback(result, 312 new FutureCallback<>() { 313 @Override 314 public void onSuccess(Void unused) { 315 LogUtil.i(TAG, "Successfully called notifyNeedRescheduleCheckIn"); 316 } 317 318 @Override 319 public void onFailure(Throwable t) { 320 throw new RuntimeException("failed to call notifyNeedRescheduleCheckIn", t); 321 } 322 }, MoreExecutors.directExecutor()); 323 return result; 324 } 325 326 @VisibleForTesting 327 void rescheduleRetryCheckInWork() { 328 long nextCheckInTimeMillis = UserParameters.getNextCheckInTimeMillis(mContext); 329 if (nextCheckInTimeMillis > 0) { 330 Duration delay = Duration.between( 331 Instant.now(mClock), 332 Instant.ofEpochMilli(nextCheckInTimeMillis)); 333 LogUtil.i(TAG, "Rescheduling retry check-in work with delay: " + delay); 334 final Operation operation = 335 enqueueCheckInWorkRequest(/* isExpedited= */ false, delay); 336 Futures.addCallback(operation.getResult(), new FutureCallback<>() { 337 @Override 338 public void onSuccess(Operation.State.SUCCESS result) { 339 // No-op 340 } 341 342 @Override 343 public void onFailure(Throwable t) { 344 LogUtil.e(TAG, "Failed to reschedule retry check in work", t); 345 WorkManagerExceptionHandler.scheduleAlarm(mContext, 346 AlarmReason.RESCHEDULE_CHECK_IN); 347 } 348 }, mSequentialExecutor); 349 } 350 } 351 352 @Override 353 public ListenableFuture<Void> maybeScheduleInitialCheckIn() { 354 return FluentFuture.from(Futures.submit(() -> UserParameters.needInitialCheckIn(mContext), 355 mSequentialExecutor)) 356 .transformAsync(needCheckIn -> { 357 if (needCheckIn) { 358 return Futures.transform(scheduleInitialCheckInWork(), 359 input -> false /* reschedule */, mSequentialExecutor); 360 } else { 361 return Futures.transform( 362 GlobalParametersClient.getInstance().isProvisionReady(), 363 ready -> !ready, mSequentialExecutor); 364 } 365 }, mSequentialExecutor) 366 .transformAsync(reschedule -> { 367 if (reschedule) { 368 return notifyNeedRescheduleCheckIn(); 369 } 370 return Futures.immediateVoidFuture(); 371 }, mSequentialExecutor); 372 } 373 374 @Override 375 public void scheduleNextProvisionFailedStepAlarm() { 376 LogUtil.d(TAG, 377 "Scheduling next provision failed step alarm"); 378 long lastTimestamp = UserParameters.getNextProvisionFailedStepTimeMills(mContext); 379 long nextTimestamp; 380 if (lastTimestamp == 0) { 381 lastTimestamp = Instant.now(mClock).toEpochMilli(); 382 } 383 long minutes = Build.isDebuggable() ? getSharedPreferences(mContext).getLong( 384 DEBUG_DEVICELOCK_REPORT_INTERVAL_MINUTES, 385 PROVISION_STATE_REPORT_INTERVAL_DEFAULT_MINUTES) 386 : PROVISION_STATE_REPORT_INTERVAL_DEFAULT_MINUTES; 387 Duration delay = Duration.ofMinutes(minutes); 388 nextTimestamp = lastTimestamp + delay.toMillis(); 389 scheduleNextProvisionFailedStepAlarm( 390 Duration.between(Instant.now(mClock), Instant.ofEpochMilli(nextTimestamp))); 391 UserParameters.setNextProvisionFailedStepTimeMills(mContext, nextTimestamp); 392 } 393 394 @Override 395 public void notifyRebootWhenProvisionFailed() { 396 dispatchFuture(() -> { 397 rescheduleNextProvisionFailedStepAlarmIfNeeded(); 398 rescheduleResetDeviceAlarmIfNeeded(); 399 }, "notifyRebootWhenProvisionFailed"); 400 } 401 402 403 @Override 404 public void scheduleResetDeviceAlarm() { 405 Duration delay = Duration.ofMinutes(NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE); 406 if (Build.isDebuggable()) { 407 delay = Duration.ofMinutes( 408 getSharedPreferences(mContext) 409 .getInt(DEBUG_DEVICELOCK_RESET_DEVICE_MINUTES, 410 NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE)); 411 } 412 scheduleResetDeviceAlarm(delay); 413 } 414 415 @Override 416 public void scheduleMandatoryResetDeviceAlarm() { 417 Duration delay = Duration.ofMinutes(MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE); 418 if (Build.isDebuggable()) { 419 delay = Duration.ofMinutes( 420 getSharedPreferences(mContext) 421 .getInt(DEBUG_DEVICELOCK_MANDATORY_RESET_DEVICE_MINUTES, 422 MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE)); 423 } 424 scheduleResetDeviceAlarm(delay); 425 } 426 427 private void scheduleResetDeviceAlarm(Duration delay) { 428 scheduleResetDeviceAlarmInternal(delay); 429 Instant whenExpectedToRun = Instant.now(mClock).plus(delay); 430 DeviceLockNotificationManager.getInstance().sendDeviceResetTimerNotification(mContext, 431 SystemClock.elapsedRealtime() + delay.toMillis()); 432 UserParameters.setResetDeviceTimeMillis(mContext, whenExpectedToRun.toEpochMilli()); 433 } 434 435 @VisibleForTesting 436 void rescheduleNextProvisionFailedStepAlarmIfNeeded() { 437 long timestamp = UserParameters.getNextProvisionFailedStepTimeMills(mContext); 438 if (timestamp > 0) { 439 Duration delay = Duration.between( 440 Instant.now(mClock), 441 Instant.ofEpochMilli(timestamp)); 442 scheduleNextProvisionFailedStepAlarm(delay); 443 } 444 } 445 446 @VisibleForTesting 447 void rescheduleResetDeviceAlarmIfNeeded() { 448 long timestamp = UserParameters.getResetDeviceTimeMillis(mContext); 449 if (timestamp > 0) { 450 Duration delay = Duration.between( 451 Instant.now(mClock), 452 Instant.ofEpochMilli(timestamp)); 453 scheduleResetDeviceAlarmInternal(delay); 454 } 455 } 456 457 @VisibleForTesting 458 void rescheduleResumeProvisionAlarmIfNeeded() { 459 long resumeProvisionTimeMillis = UserParameters.getResumeProvisionTimeMillis(mContext); 460 if (resumeProvisionTimeMillis > 0) { 461 Duration delay = Duration.between( 462 Instant.now(mClock), 463 Instant.ofEpochMilli(resumeProvisionTimeMillis)); 464 scheduleResumeProvisionAlarm(delay); 465 } 466 } 467 468 /** 469 * Run the input runnable in order on the scheduler's sequential executor 470 * 471 * @param runnable The runnable to run on worker thread. 472 * @param methodName The name of the method that requested to run runnable. 473 */ 474 private void dispatchFuture(Runnable runnable, String methodName) { 475 Futures.addCallback(Futures.submit(runnable, mSequentialExecutor), 476 new FutureCallback<>() { 477 @Override 478 public void onSuccess(Void unused) { 479 LogUtil.i(TAG, "Successfully called " + methodName); 480 } 481 482 @Override 483 public void onFailure(Throwable t) { 484 throw new RuntimeException("failed to call " + methodName, t); 485 } 486 }, MoreExecutors.directExecutor()); 487 } 488 489 private Operation enqueueCheckInWorkRequest(boolean isExpedited, Duration delay) { 490 NetworkRequest request = new NetworkRequest.Builder() 491 .addCapability(NET_CAPABILITY_NOT_RESTRICTED) 492 .addCapability(NET_CAPABILITY_TRUSTED) 493 .addCapability(NET_CAPABILITY_INTERNET) 494 .addCapability(NET_CAPABILITY_NOT_VPN) 495 .build(); 496 OneTimeWorkRequest.Builder builder = 497 new OneTimeWorkRequest.Builder(DeviceCheckInWorker.class) 498 .setConstraints( 499 new Constraints.Builder().setRequiredNetworkRequest(request, 500 NetworkType.CONNECTED).build()) 501 .setInitialDelay(delay) 502 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY); 503 if (isExpedited) builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST); 504 505 return WorkManager.getInstance(mContext).enqueueUniqueWork(DEVICE_CHECK_IN_WORK_NAME, 506 ExistingWorkPolicy.REPLACE, builder.build()); 507 } 508 509 private void scheduleResumeProvisionAlarm(Duration delay) { 510 scheduleAlarmWithPendingIntentAndDelay(ResumeProvisionReceiver.class, delay); 511 } 512 513 private void scheduleNextProvisionFailedStepAlarm(Duration delay) { 514 scheduleAlarmWithPendingIntentAndDelay(NextProvisionFailedStepReceiver.class, delay); 515 } 516 517 private void scheduleResetDeviceAlarmInternal(Duration delay) { 518 scheduleAlarmWithPendingIntentAndDelay(ResetDeviceReceiver.class, delay); 519 } 520 521 private void scheduleAlarmWithPendingIntentAndDelay( 522 Class<? extends BroadcastReceiver> receiverClass, Duration delay) { 523 long countDownBase = SystemClock.elapsedRealtime() + delay.toMillis(); 524 AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); 525 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, /* ignored */ 0, 526 new Intent(mContext, receiverClass), 527 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); 528 Objects.requireNonNull(alarmManager).setExactAndAllowWhileIdle( 529 AlarmManager.ELAPSED_REALTIME_WAKEUP, 530 countDownBase, 531 pendingIntent); 532 } 533 } 534