Snap for 4829593 from 4e496f7ea9bc3d2535219ce0861b4b2fc4397545 to pi-release
Change-Id: I1abd50e0ebb6db38bd732947c01dc4964c97f8dd
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
index c633c46..323757a 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
@@ -18,11 +18,11 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -48,6 +48,7 @@
private WorkManagerImpl mWorkManager;
private WorkDatabase mWorkDatabase;
private WorkSpecDao mWorkSpecDao;
+ private Preferences mPreferences;
private ForceStopRunnable mRunnable;
@Before
@@ -56,8 +57,10 @@
mWorkManager = mock(WorkManagerImpl.class);
mWorkDatabase = mock(WorkDatabase.class);
mWorkSpecDao = mock(WorkSpecDao.class);
+ mPreferences = mock(Preferences.class);
when(mWorkManager.getWorkDatabase()).thenReturn(mWorkDatabase);
when(mWorkDatabase.workSpecDao()).thenReturn(mWorkSpecDao);
+ when(mWorkManager.getPreferences()).thenReturn(mPreferences);
mRunnable = new ForceStopRunnable(mContext, mWorkManager);
}
@@ -73,6 +76,7 @@
@Test
public void testReschedulesOnForceStop() {
ForceStopRunnable runnable = spy(mRunnable);
+ when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
when(runnable.isForceStopped()).thenReturn(true);
runnable.run();
verify(mWorkManager, times(1)).rescheduleEligibleWork();
@@ -81,8 +85,28 @@
@Test
public void test_doNothingWhenNotForceStopped() {
ForceStopRunnable runnable = spy(mRunnable);
+ when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
when(runnable.isForceStopped()).thenReturn(false);
runnable.run();
- verifyNoMoreInteractions(mWorkManager);
+ verify(mWorkManager, times(0)).rescheduleEligibleWork();
+ }
+
+ @Test
+ public void test_cancelAllJobSchedulerJobs() {
+ ForceStopRunnable runnable = spy(mRunnable);
+ doNothing().when(runnable).cancelAllInJobScheduler();
+ when(runnable.shouldCancelPersistedJobs()).thenReturn(true);
+ runnable.run();
+ verify(runnable, times(1)).cancelAllInJobScheduler();
+ verify(mPreferences, times(1)).setMigratedPersistedJobs();
+ }
+
+ @Test
+ public void test_doNothingWhenThereIsNothingToCancel() {
+ ForceStopRunnable runnable = spy(mRunnable);
+ doNothing().when(runnable).cancelAllInJobScheduler();
+ when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
+ runnable.run();
+ verify(runnable, times(0)).cancelAllInJobScheduler();
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java b/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
index 333335f..57849be 100644
--- a/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
@@ -62,7 +62,6 @@
return workList;
}
-
OneTimeWorkRequest(Builder builder) {
super(builder.mId, builder.mWorkSpec, builder.mTags);
}
diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
index 2232444..c1eae64 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
@@ -53,6 +53,10 @@
private @NonNull WorkSpec mWorkSpec;
private @NonNull Set<String> mTags;
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected WorkRequest(@NonNull UUID id, @NonNull WorkSpec workSpec, @NonNull Set<String> tags) {
mId = id;
mWorkSpec = workSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/Worker.java b/work/workmanager/src/main/java/androidx/work/Worker.java
index 58a5c22..87e4ca4 100644
--- a/work/workmanager/src/main/java/androidx/work/Worker.java
+++ b/work/workmanager/src/main/java/androidx/work/Worker.java
@@ -168,30 +168,30 @@
public abstract @NonNull Result doWork();
/**
- * Call this method to pass an {@link Data} object to {@link Worker} that is
- * dependent on this one.
+ * Call this method to pass a {@link Data} object as the output of this {@link Worker}. This
+ * result can be observed and passed to Workers that are dependent on this one.
*
- * Note that if there are multiple {@link Worker}s that contribute to the target, the
- * Data will be merged together, so it is up to the developer to make sure that keys are
- * unique. New values and types will clobber old values and types, and if there are multiple
- * parent Workers of a child Worker, the order of clobbering may not be deterministic.
- *
- * This method is invoked after {@link #doWork()} returns {@link Result#SUCCESS}
- * and there are chained jobs available.
- *
+ * In cases like where two or more {@link OneTimeWorkRequest}s share a dependent WorkRequest,
+ * their Data will be merged together using an {@link InputMerger}. The default InputMerger is
+ * {@link OverwritingInputMerger}, unless otherwise specified using the
+ * {@link OneTimeWorkRequest.Builder#setInputMerger(Class)} method.
+ * <p>
+ * This method is invoked after {@link #doWork()} returns {@link Result#SUCCESS} or
+ * {@link Result#FAILURE}.
+ * <p>
* For example, if you had this structure:
- *
+ * <pre>
* {@code WorkManager.getInstance(context)
- * .enqueueWithDefaults(WorkerA.class, WorkerB.class)
- * .then(WorkerC.class)
- * .enqueue()}
+ * .beginWith(workRequestA, workRequestB)
+ * .then(workRequestC)
+ * .enqueue()}</pre>
*
- * This method would be called for both WorkerA and WorkerB after their successful completion,
- * modifying the input Data for WorkerC.
+ * This method would be called for both {@code workRequestA} and {@code workRequestB} after
+ * their completion, modifying the input Data for {@code workRequestC}.
*
* @param outputData An {@link Data} object that will be merged into the input Data of any
- * OneTimeWorkRequest that is dependent on this one, or {@code null} if there
- * is nothing to contribute
+ * OneTimeWorkRequest that is dependent on this one, or {@link Data#EMPTY} if
+ * there is nothing to contribute
*/
public final void setOutputData(@NonNull Data outputData) {
mOutputData = outputData;
@@ -220,7 +220,7 @@
* <p>
* Note that it is almost never sufficient to check only this method; its value is only
* meaningful when {@link #isStopped()} returns {@code true}.
- * <p>
+ *
* @return {@code true} if this work operation has been cancelled
*/
public final boolean isCancelled() {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 5b659d1..119325c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -237,6 +237,15 @@
return mTaskExecutor;
}
+ /**
+ * @return the {@link Preferences} used by the instance of {@link WorkManager}.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public @NonNull Preferences getPreferences() {
+ return mPreferences;
+ }
+
@Override
public void enqueue(@NonNull List<? extends WorkRequest> workRequests) {
new WorkContinuationImpl(this, workRequests).enqueue();
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
index 5915d66..22c5559 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
@@ -25,6 +25,7 @@
import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
@@ -37,7 +38,10 @@
/**
* Converts a {@link WorkSpec} into a JobInfo.
+ *
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(api = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
class SystemJobInfoConverter {
private static final String TAG = "SystemJobInfoConverter";
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index 98aff4e..933e0d8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -20,6 +20,7 @@
import android.app.job.JobScheduler;
import android.content.Context;
import android.os.Build;
+import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
@@ -151,4 +152,26 @@
}
}
}
+
+ /**
+ * Cancels all the jobs owned by {@link androidx.work.WorkManager} in {@link JobScheduler}.
+ */
+ public static void jobSchedulerCancelAll(@NonNull Context context) {
+ JobScheduler jobScheduler = (JobScheduler)
+ context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+ if (jobScheduler != null) {
+ List<JobInfo> jobInfos = jobScheduler.getAllPendingJobs();
+ // Apparently this can be null on API 23?
+ if (jobInfos != null) {
+ for (JobInfo jobInfo : jobInfos) {
+ PersistableBundle extras = jobInfo.getExtras();
+ // This is a job scheduled by WorkManager.
+ if (extras.containsKey(SystemJobInfoConverter.EXTRA_WORK_SPEC_ID)) {
+ jobScheduler.cancel(jobInfo.getId());
+ }
+ }
+ }
+ }
+ }
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index e9712b3..87de105 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
@@ -20,8 +20,10 @@
import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -32,6 +34,7 @@
import android.util.Log;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.background.systemjob.SystemJobScheduler;
import java.util.concurrent.TimeUnit;
@@ -64,7 +67,13 @@
@Override
public void run() {
- if (isForceStopped()) {
+ if (shouldCancelPersistedJobs()) {
+ cancelAllInJobScheduler();
+ Log.d(TAG, "Migrating persisted jobs.");
+ mWorkManager.rescheduleEligibleWork();
+ // Mark the jobs as migrated.
+ mWorkManager.getPreferences().setMigratedPersistedJobs();
+ } else if (isForceStopped()) {
Log.d(TAG, "Application was force-stopped, rescheduling.");
mWorkManager.rescheduleEligibleWork();
}
@@ -89,6 +98,15 @@
}
/**
+ * @return {@code true} If persisted jobs in JobScheduler need to be cancelled.
+ */
+ @VisibleForTesting
+ public boolean shouldCancelPersistedJobs() {
+ return Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
+ && mWorkManager.getPreferences().shouldMigratePersistedJobs();
+ }
+
+ /**
* @param alarmId The stable alarm id to be used.
* @param flags The {@link PendingIntent} flags.
* @return an instance of the {@link PendingIntent}.
@@ -110,6 +128,15 @@
return intent;
}
+ /**
+ * Cancels all the persisted jobs in {@link JobScheduler}.
+ */
+ @VisibleForTesting
+ @TargetApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
+ public void cancelAllInJobScheduler() {
+ SystemJobScheduler.jobSchedulerCancelAll(mContext);
+ }
+
private void setAlarm(int alarmId) {
AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
// Using FLAG_UPDATE_CURRENT, because we only ever want once instance of this alarm.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
index 762f6b0..49cd262 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
@@ -35,6 +35,7 @@
private static final String PREFERENCES_FILE_NAME = "androidx.work.util.preferences";
private static final String KEY_LAST_CANCEL_ALL_TIME_MS = "last_cancel_all_time_ms";
+ private static final String KEY_MIGRATE_PERSISTED_JOBS = "migrate_persisted_jobs";
private SharedPreferences mSharedPreferences;
@@ -68,6 +69,23 @@
}
/**
+ * @return {@code true} When we should migrate from persisted jobs to non-persisted jobs in
+ * {@link android.app.job.JobScheduler}
+ */
+ public boolean shouldMigratePersistedJobs() {
+ // TODO Remove this before WorkManager 1.0 beta.
+ return mSharedPreferences.getBoolean(KEY_MIGRATE_PERSISTED_JOBS, true);
+ }
+
+ /**
+ * Updates the key which indicates that we have migrated all our persisted jobs in
+ * {@link android.app.job.JobScheduler}.
+ */
+ public void setMigratedPersistedJobs() {
+ mSharedPreferences.edit().putBoolean(KEY_MIGRATE_PERSISTED_JOBS, true).apply();
+ }
+
+ /**
* A {@link android.arch.lifecycle.LiveData} that responds to changes in
* {@link SharedPreferences} for the {@code lastCancelAllTime} value.
*/