ForceStopRunnable.java revision 697d6a4a3797bc71d0dd8685937a318e9934066b
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;
30import android.util.Log;
31
32import androidx.work.impl.WorkManagerImpl;
33import androidx.work.impl.background.systemalarm.SystemAlarmService;
34import androidx.work.impl.background.systemjob.SystemJobService;
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            Log.d(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