ForceStopRunnable.java revision 850226438f7ff51724f86cc7cc1c5c03afe91399
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; 20import static android.app.PendingIntent.FLAG_NO_CREATE; 21import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 22 23import android.app.AlarmManager; 24import android.app.PendingIntent; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.os.Build; 29import android.support.annotation.NonNull; 30import android.support.annotation.RestrictTo; 31import android.support.annotation.VisibleForTesting; 32import android.util.Log; 33 34import androidx.work.impl.WorkManagerImpl; 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 @VisibleForTesting 51 static final String ACTION_FORCE_STOP_RESCHEDULE = "ACTION_FORCE_STOP_RESCHEDULE"; 52 53 // All our alarms are use request codes which are > 0. 54 private static final int ALARM_ID = -1; 55 private static final long TEN_YEARS = TimeUnit.DAYS.toMillis(10 * 365); 56 57 private final Context mContext; 58 private final WorkManagerImpl mWorkManager; 59 60 public ForceStopRunnable(@NonNull Context context, @NonNull WorkManagerImpl workManager) { 61 mContext = context.getApplicationContext(); 62 mWorkManager = workManager; 63 } 64 65 @Override 66 public void run() { 67 if (isForceStopped()) { 68 Log.d(TAG, "Application was force-stopped, rescheduling."); 69 mWorkManager.rescheduleEligibleWork(); 70 } 71 } 72 73 /** 74 * @return {@code true} If the application was force stopped. 75 */ 76 @VisibleForTesting 77 public boolean isForceStopped() { 78 // Alarms get cancelled when an app is force-stopped starting at Eclair MR1. 79 // Cancelling of Jobs on force-stop was introduced in N-MR1 (SDK 25). 80 // Even though API 23, 24 are probably safe, OEMs may choose to do 81 // something different. 82 PendingIntent pendingIntent = getPendingIntent(ALARM_ID, FLAG_NO_CREATE); 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.getBroadcast(mContext, alarmId, intent, flags); 100 } 101 102 /** 103 * @return The instance of {@link Intent} used to keep track of force stops. 104 */ 105 @VisibleForTesting 106 public Intent getIntent() { 107 Intent intent = new Intent(); 108 intent.setComponent(new ComponentName(mContext, ForceStopRunnable.BroadcastReceiver.class)); 109 intent.setAction(ACTION_FORCE_STOP_RESCHEDULE); 110 return intent; 111 } 112 113 private void setAlarm(int alarmId) { 114 AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 115 // Using FLAG_UPDATE_CURRENT, because we only ever want once instance of this alarm. 116 PendingIntent pendingIntent = getPendingIntent(alarmId, FLAG_UPDATE_CURRENT); 117 long triggerAt = System.currentTimeMillis() + TEN_YEARS; 118 if (alarmManager != null) { 119 if (Build.VERSION.SDK_INT >= 19) { 120 alarmManager.setExact(RTC_WAKEUP, triggerAt, pendingIntent); 121 } else { 122 alarmManager.set(RTC_WAKEUP, triggerAt, pendingIntent); 123 } 124 } 125 } 126 127 /** 128 * A {@link android.content.BroadcastReceiver} which takes care of recreating the 129 * long lived alarm which helps track force stops for an application. 130 * 131 * @hide 132 */ 133 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 134 public static class BroadcastReceiver extends android.content.BroadcastReceiver { 135 private static final String TAG = "ForceStopRunnable$Rcvr"; 136 137 @Override 138 public void onReceive(Context context, Intent intent) { 139 if (intent != null) { 140 String action = intent.getAction(); 141 if (ACTION_FORCE_STOP_RESCHEDULE.equals(action)) { 142 Log.v(TAG, "Rescheduling alarm that keeps track of force-stops."); 143 WorkManagerImpl workManager = WorkManagerImpl.getInstance(); 144 ForceStopRunnable runnable = new ForceStopRunnable(context, workManager); 145 runnable.setAlarm(ForceStopRunnable.ALARM_ID); 146 } 147 } 148 } 149 } 150} 151