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