ForceStopRunnable.java revision d554aebb8aa2aa48222b7b003273b06369d55a89
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.annotation.TargetApi;
24import android.app.AlarmManager;
25import android.app.PendingIntent;
26import android.app.job.JobScheduler;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.os.Build;
31import android.support.annotation.NonNull;
32import android.support.annotation.RestrictTo;
33import android.support.annotation.VisibleForTesting;
34import android.util.Log;
35
36import androidx.work.impl.WorkManagerImpl;
37import androidx.work.impl.background.systemjob.SystemJobScheduler;
38
39import java.util.concurrent.TimeUnit;
40
41/**
42 * WorkManager is restarted after an app was force stopped.
43 * Alarms and Jobs get cancelled when an application is force-stopped. To reschedule, we
44 * create a pending alarm that will not survive force stops.
45 *
46 * @hide
47 */
48@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
49public class ForceStopRunnable implements Runnable {
50
51    private static final String TAG = "ForceStopRunnable";
52
53    @VisibleForTesting
54    static final String ACTION_FORCE_STOP_RESCHEDULE = "ACTION_FORCE_STOP_RESCHEDULE";
55
56    // All our alarms are use request codes which are > 0.
57    private static final int ALARM_ID = -1;
58    private static final long TEN_YEARS = TimeUnit.DAYS.toMillis(10 * 365);
59
60    private final Context mContext;
61    private final WorkManagerImpl mWorkManager;
62
63    public ForceStopRunnable(@NonNull Context context, @NonNull WorkManagerImpl workManager) {
64        mContext = context.getApplicationContext();
65        mWorkManager = workManager;
66    }
67
68    @Override
69    public void run() {
70        if (shouldCancelPersistedJobs()) {
71            cancelAllInJobScheduler();
72            Log.d(TAG, "Migrating persisted jobs.");
73            mWorkManager.rescheduleEligibleWork();
74            // Mark the jobs as migrated.
75            mWorkManager.getPreferences().setMigratedPersistedJobs();
76        } else if (isForceStopped()) {
77            Log.d(TAG, "Application was force-stopped, rescheduling.");
78            mWorkManager.rescheduleEligibleWork();
79        }
80    }
81
82    /**
83     * @return {@code true} If the application was force stopped.
84     */
85    @VisibleForTesting
86    public boolean isForceStopped() {
87        // Alarms get cancelled when an app is force-stopped starting at Eclair MR1.
88        // Cancelling of Jobs on force-stop was introduced in N-MR1 (SDK 25).
89        // Even though API 23, 24 are probably safe, OEMs may choose to do
90        // something different.
91        PendingIntent pendingIntent = getPendingIntent(ALARM_ID, FLAG_NO_CREATE);
92        if (pendingIntent == null) {
93            setAlarm(ALARM_ID);
94            return true;
95        } else {
96            return false;
97        }
98    }
99
100    /**
101     * @return {@code true} If persisted jobs in JobScheduler need to be cancelled.
102     */
103    @VisibleForTesting
104    public boolean shouldCancelPersistedJobs() {
105        return Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
106                && mWorkManager.getPreferences().shouldMigratePersistedJobs();
107    }
108
109    /**
110     * @param alarmId The stable alarm id to be used.
111     * @param flags   The {@link PendingIntent} flags.
112     * @return an instance of the {@link PendingIntent}.
113     */
114    @VisibleForTesting
115    public PendingIntent getPendingIntent(int alarmId, int flags) {
116        Intent intent = getIntent();
117        return PendingIntent.getBroadcast(mContext, alarmId, intent, flags);
118    }
119
120    /**
121     * @return The instance of {@link Intent} used to keep track of force stops.
122     */
123    @VisibleForTesting
124    public Intent getIntent() {
125        Intent intent = new Intent();
126        intent.setComponent(new ComponentName(mContext, ForceStopRunnable.BroadcastReceiver.class));
127        intent.setAction(ACTION_FORCE_STOP_RESCHEDULE);
128        return intent;
129    }
130
131    /**
132     * Cancels all the persisted jobs in {@link JobScheduler}.
133     */
134    @VisibleForTesting
135    @TargetApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
136    public void cancelAllInJobScheduler() {
137        SystemJobScheduler.jobSchedulerCancelAll(mContext);
138    }
139
140    private void setAlarm(int alarmId) {
141        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
142        // Using FLAG_UPDATE_CURRENT, because we only ever want once instance of this alarm.
143        PendingIntent pendingIntent = getPendingIntent(alarmId, FLAG_UPDATE_CURRENT);
144        long triggerAt = System.currentTimeMillis() + TEN_YEARS;
145        if (alarmManager != null) {
146            if (Build.VERSION.SDK_INT >= 19) {
147                alarmManager.setExact(RTC_WAKEUP, triggerAt, pendingIntent);
148            } else {
149                alarmManager.set(RTC_WAKEUP, triggerAt, pendingIntent);
150            }
151        }
152    }
153
154    /**
155     * A {@link android.content.BroadcastReceiver} which takes care of recreating the
156     * long lived alarm which helps track force stops for an application.
157     *
158     * @hide
159     */
160    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
161    public static class BroadcastReceiver extends android.content.BroadcastReceiver {
162        private static final String TAG = "ForceStopRunnable$Rcvr";
163
164        @Override
165        public void onReceive(Context context, Intent intent) {
166            if (intent != null) {
167                String action = intent.getAction();
168                if (ACTION_FORCE_STOP_RESCHEDULE.equals(action)) {
169                    Log.v(TAG, "Rescheduling alarm that keeps track of force-stops.");
170                    WorkManagerImpl workManager = WorkManagerImpl.getInstance();
171                    ForceStopRunnable runnable = new ForceStopRunnable(context, workManager);
172                    runnable.setAlarm(ForceStopRunnable.ALARM_ID);
173                }
174            }
175        }
176    }
177}
178