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);
+    }
+}