ForceStopRunnable.java revision b5728f4e1a4b3f4f1fabf033b1363ca6b1cffdef
1/* 2 * Copyright 2018 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 17package androidx.work.impl.utils; 18 19import static android.app.AlarmManager.RTC_WAKEUP; 20 21import android.app.AlarmManager; 22import android.app.PendingIntent; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.Intent; 26import android.os.Build; 27import android.support.annotation.NonNull; 28import android.support.annotation.RestrictTo; 29import android.support.annotation.VisibleForTesting; 30 31import androidx.work.impl.WorkManagerImpl; 32import androidx.work.impl.background.systemalarm.SystemAlarmService; 33import androidx.work.impl.background.systemjob.SystemJobService; 34import androidx.work.impl.logger.Logger; 35 36import java.util.concurrent.TimeUnit; 37 38/** 39 * WorkManager is restarted after an app was force stopped. 40 * Alarms and Jobs get cancelled when an application is force-stopped. To reschedule, we 41 * create a pending alarm that will not survive force stops. 42 * 43 * @hide 44 */ 45@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 46public class ForceStopRunnable implements Runnable { 47 48 private static final String TAG = "ForceStopRunnable"; 49 50 // All our alarms are use request codes which are > 0. 51 private static final int ALARM_ID = -1; 52 private static final long TEN_YEARS = TimeUnit.DAYS.toMillis(10 * 365); 53 54 private final Context mContext; 55 private final WorkManagerImpl mWorkManager; 56 57 public ForceStopRunnable(@NonNull Context context, @NonNull WorkManagerImpl workManager) { 58 mContext = context.getApplicationContext(); 59 mWorkManager = workManager; 60 } 61 62 @Override 63 public void run() { 64 if (isForceStopped()) { 65 Logger.debug(TAG, "Application was force-stopped, rescheduling."); 66 mWorkManager.rescheduleEligibleWork(); 67 } 68 } 69 70 /** 71 * @return {@code true} If the application was force stopped. 72 */ 73 @VisibleForTesting 74 public boolean isForceStopped() { 75 // Alarms get cancelled when an app is force-stopped starting at Eclair MR1. 76 // Cancelling of Jobs on force-stop was introduced in N-MR1 (SDK 25). 77 // Even though API 23, 24 are probably safe, OEMs may choose to do 78 // something different. 79 PendingIntent pendingIntent = getPendingIntent( 80 ALARM_ID, 81 PendingIntent.FLAG_NO_CREATE); 82 83 if (pendingIntent == null) { 84 setAlarm(ALARM_ID); 85 return true; 86 } else { 87 return false; 88 } 89 } 90 91 /** 92 * @param alarmId The stable alarm id to be used. 93 * @param flags The {@link PendingIntent} flags. 94 * @return an instance of the {@link PendingIntent}. 95 */ 96 @VisibleForTesting 97 public PendingIntent getPendingIntent(int alarmId, int flags) { 98 Intent intent = getIntent(); 99 return PendingIntent.getService(mContext, alarmId, intent, flags); 100 } 101 102 /** 103 * @return The instance of {@link Intent}. 104 * 105 * Uses {@link SystemJobService} on API_LEVEL >= 106 * {@link WorkManagerImpl#MIN_JOB_SCHEDULER_API_LEVEL}. 107 * 108 * Uses {@link SystemAlarmService} otherwise. 109 */ 110 @VisibleForTesting 111 public Intent getIntent() { 112 Intent intent = new Intent(); 113 if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) { 114 intent.setComponent(new ComponentName(mContext, SystemJobService.class)); 115 } else { 116 intent.setComponent(new ComponentName(mContext, SystemAlarmService.class)); 117 } 118 intent.setAction(TAG); 119 return intent; 120 } 121 122 private void setAlarm(int alarmId) { 123 AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 124 PendingIntent pendingIntent = getPendingIntent(alarmId, PendingIntent.FLAG_ONE_SHOT); 125 long triggerAt = System.currentTimeMillis() + TEN_YEARS; 126 if (alarmManager != null) { 127 if (Build.VERSION.SDK_INT >= 19) { 128 alarmManager.setExact(RTC_WAKEUP, triggerAt, pendingIntent); 129 } else { 130 alarmManager.set(RTC_WAKEUP, triggerAt, pendingIntent); 131 } 132 } 133 } 134} 135