Merge "Reschedules alarms when BOOT_COMPLETED, TIMEZONE_CHANGED, and TIME_SET. Test: Updated unit tests. Fixes: b/73313567" into flatfoot-background
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 729ed40..e026e9d 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -37,12 +37,15 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -337,7 +340,6 @@
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
WorkSpec workSpec = workSpecDao.getWorkSpec(work.getId());
-
assertThat(mLatch.getCount(), is(0L));
// Assert order of events
assertThat(intentActions,
@@ -348,6 +350,62 @@
assertThat(workSpec.getState(), is(State.SUCCEEDED));
}
+ @Test
+ public void testReschedule() throws InterruptedException {
+ // Use a mocked scheduler in this test.
+ Scheduler scheduler = mock(Scheduler.class);
+ when(mWorkManager.getSchedulers()).thenReturn(Collections.singletonList(scheduler));
+
+ Work failed = new Work.Builder(TestWorker.class)
+ .withPeriodStartTime(System.currentTimeMillis())
+ .withInitialState(State.FAILED)
+ .build();
+
+ Work succeeded = new Work.Builder(TestWorker.class)
+ .withPeriodStartTime(System.currentTimeMillis())
+ .withInitialState(State.SUCCEEDED)
+ .build();
+
+ Work noConstraints = new Work.Builder(TestWorker.class)
+ .withPeriodStartTime(System.currentTimeMillis())
+ .build();
+
+ Work workWithConstraints = new Work.Builder(TestWorker.class)
+ .withPeriodStartTime(System.currentTimeMillis())
+ .withConstraints(new Constraints.Builder()
+ .setRequiresCharging(true)
+ .build())
+ .build();
+
+ insertWork(failed);
+ insertWork(succeeded);
+ insertWork(noConstraints);
+ insertWork(workWithConstraints);
+
+ Intent reschedule = CommandHandler.createRescheduleIntent(mContext);
+ mSpyDispatcher.postOnMainThread(
+ new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, reschedule, START_ID));
+
+ mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
+ assertThat(mLatch.getCount(), is(0L));
+
+ ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
+ verify(scheduler, times(1))
+ .schedule(captor.capture());
+
+ Set<String> capturedIds = new HashSet<>();
+ List<WorkSpec> workSpecs = captor.getAllValues();
+ for (WorkSpec workSpec : workSpecs) {
+ capturedIds.add(workSpec.getId());
+ }
+
+ assertThat(capturedIds.size(), is(2));
+ assertThat(capturedIds.contains(noConstraints.getId()), is(true));
+ assertThat(capturedIds.contains(workWithConstraints.getId()), is(true));
+ assertThat(capturedIds.contains(failed.getId()), is(false));
+ assertThat(capturedIds.contains(succeeded.getId()), is(false));
+ }
+
private static List<String> intentActionsFor(@NonNull List<Intent> intents) {
List<String> intentActions = new ArrayList<>(intents.size());
for (Intent intent : intents) {
diff --git a/work/workmanager/src/main/AndroidManifest.xml b/work/workmanager/src/main/AndroidManifest.xml
index 14aba26..bdb650a 100644
--- a/work/workmanager/src/main/AndroidManifest.xml
+++ b/work/workmanager/src/main/AndroidManifest.xml
@@ -67,5 +67,14 @@
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
+ <receiver
+ android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
+ android:enabled="@bool/enable_system_alarm_service_default">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ <action android:name="android.intent.action.TIME_SET" />
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
index cd8e043..c7668d8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
@@ -31,10 +31,12 @@
import android.support.annotation.WorkerThread;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Processor;
+import androidx.work.impl.Scheduler;
import androidx.work.impl.logger.Logger;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.utils.IdGenerator;
@@ -54,6 +56,7 @@
static final String ACTION_DELAY_MET = "ACTION_DELAY_MET";
static final String ACTION_STOP_WORK = "ACTION_STOP_WORK";
static final String ACTION_CONSTRAINTS_CHANGED = "ACTION_CONSTRAINTS_CHANGED";
+ static final String ACTION_RESCHEDULE = "ACTION_RESCHEDULE";
// keys
private static final String KEY_WORKSPEC_ID = "KEY_WORKSPEC_ID";
@@ -89,6 +92,12 @@
return intent;
}
+ static Intent createRescheduleIntent(@NonNull Context context) {
+ Intent intent = new Intent(context, SystemAlarmService.class);
+ intent.setAction(ACTION_RESCHEDULE);
+ return intent;
+ }
+
// members
private final Context mContext;
private final IdGenerator mIdGenerator;
@@ -148,6 +157,8 @@
if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
handleConstraintsChanged(intent, startId, dispatcher);
+ } else if (ACTION_RESCHEDULE.equals(action)) {
+ handleReschedule(intent, startId, dispatcher);
} else {
Bundle extras = intent.getExtras();
if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
@@ -256,6 +267,24 @@
changedCommandHandler.handleConstraintsChanged();
}
+ private void handleReschedule(
+ @NonNull Intent intent,
+ int startId,
+ @NonNull SystemAlarmDispatcher dispatcher) {
+
+ Logger.debug(TAG, "Handling reschedule %s, %s", intent, startId);
+ // Get workspec's that are eligible irrespective of their start time.
+ List<WorkSpec> eligibleWorkSpecs = dispatcher.getWorkManager().getWorkDatabase()
+ .workSpecDao()
+ .getSystemAlarmEligibleWorkSpecs(Long.MAX_VALUE);
+
+ // TODO (rahulrav@) Cancel alarms when applicable
+ // Delegate to the WorkManager's schedulers.
+ for (Scheduler scheduler: dispatcher.getWorkManager().getSchedulers()) {
+ scheduler.schedule(eligibleWorkSpecs.toArray(new WorkSpec[0]));
+ }
+ }
+
private void setExactAlarm(@NonNull Intent intent, long triggerAtMillis) {
int alarmId = mIdGenerator.nextAlarmManagerId();
PendingIntent pendingIntent = PendingIntent.getService(
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java
new file mode 100644
index 0000000..27eb6af
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.impl.background.systemalarm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Reschedules alarms on BOOT_COMPLETED and other similar scenarios.
+ */
+public class RescheduleReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Intent reschedule = CommandHandler.createRescheduleIntent(context);
+ context.startService(reschedule);
+ }
+}