Merge "Add ability to query when cancelAll was last called." into pi-preview1-androidx-dev
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index c1daf54..7168600 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -36,6 +36,7 @@
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.emptyCollectionOf;
+import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.isIn;
 import static org.hamcrest.Matchers.isOneOf;
@@ -57,6 +58,7 @@
 import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -80,6 +82,8 @@
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.model.WorkTag;
 import androidx.work.impl.model.WorkTagDao;
+import androidx.work.impl.utils.CancelWorkRunnable;
+import androidx.work.impl.utils.Preferences;
 import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule;
 import androidx.work.impl.workers.ConstraintTrackingWorker;
 import androidx.work.worker.InfiniteTestWorker;
@@ -1285,6 +1289,49 @@
     }
 
     @Test
+    @MediumTest
+    public void testCancelAllWork_updatesLastCancelAllTime() {
+        Preferences preferences = new Preferences(InstrumentationRegistry.getTargetContext());
+        preferences.setLastCancelAllTimeMillis(0L);
+
+        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        insertWorkSpecAndTags(work);
+
+        CancelWorkRunnable.forAll(mWorkManagerImpl).run();
+
+        assertThat(preferences.getLastCancelAllTimeMillis(), is(greaterThan(0L)));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("unchecked")
+    public void testCancelAllWork_updatesLastCancelAllTimeLiveData() throws InterruptedException {
+        Preferences preferences = new Preferences(InstrumentationRegistry.getTargetContext());
+        preferences.setLastCancelAllTimeMillis(0L);
+
+        TestLifecycleOwner testLifecycleOwner = new TestLifecycleOwner();
+        LiveData<Long> cancelAllTimeLiveData = mWorkManagerImpl.getLastCancelAllTimeMillis();
+        Observer<Long> mockObserver = mock(Observer.class);
+        cancelAllTimeLiveData.observe(testLifecycleOwner, mockObserver);
+
+        ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+        verify(mockObserver).onChanged(captor.capture());
+        assertThat(captor.getValue(), is(0L));
+
+        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        insertWorkSpecAndTags(work);
+
+        clearInvocations(mockObserver);
+        CancelWorkRunnable.forAll(mWorkManagerImpl).run();
+
+        Thread.sleep(1000L);
+        verify(mockObserver).onChanged(captor.capture());
+        assertThat(captor.getValue(), is(greaterThan(0L)));
+
+        cancelAllTimeLiveData.removeObservers(testLifecycleOwner);
+    }
+
+    @Test
     @SmallTest
     public void testSynchronousCancelAndGetStatus() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
index 3359905..f01e363 100644
--- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
@@ -118,6 +118,17 @@
     void cancelAllWorkSync();
 
     /**
+     * Gets the timestamp of the last time all work was cancelled in a synchronous fashion.  This
+     * method is intended for use by library and module developers who have dependent data in their
+     * own repository that must be updated or deleted in case someone cancels their work without
+     * their prior knowledge.
+     *
+     * @return The timestamp in milliseconds when a method that cancelled all work was last invoked
+     */
+    @WorkerThread
+    long getLastCancelAllTimeMillisSync();
+
+    /**
      * Gets the {@link WorkStatus} of a given work id in a synchronous fashion.  This method is
      * expected to be called from a background thread.
      *
diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java
index 00a41d3..83891dc 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java
@@ -288,6 +288,16 @@
     public abstract void cancelAllWork();
 
     /**
+     * Gets a {@link LiveData} of the last time all work was cancelled.  This method is intended for
+     * use by library and module developers who have dependent data in their own repository that
+     * must be updated or deleted in case someone cancels their work without their prior knowledge.
+     *
+     * @return A {@link LiveData} of the timestamp in milliseconds when method that cancelled all
+     *         work was last invoked
+     */
+    public abstract LiveData<Long> getLastCancelAllTimeMillis();
+
+    /**
      * Gets a {@link LiveData} of the {@link WorkStatus} for a given work id.
      *
      * @param id The id of the work
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 3d61cae..5b659d1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -42,6 +42,7 @@
 import androidx.work.impl.utils.CancelWorkRunnable;
 import androidx.work.impl.utils.ForceStopRunnable;
 import androidx.work.impl.utils.LiveDataUtils;
+import androidx.work.impl.utils.Preferences;
 import androidx.work.impl.utils.StartWorkRunnable;
 import androidx.work.impl.utils.StopWorkRunnable;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -69,6 +70,7 @@
     private TaskExecutor mTaskExecutor;
     private List<Scheduler> mSchedulers;
     private Processor mProcessor;
+    private Preferences mPreferences;
 
     private static WorkManagerImpl sDelegatedInstance = null;
     private static WorkManagerImpl sDefaultInstance = null;
@@ -167,12 +169,22 @@
                 mWorkDatabase,
                 getSchedulers(),
                 configuration.getExecutor());
+        mPreferences = new Preferences(mContext);
 
         // Checks for app force stops.
         mTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
     }
 
     /**
+     * @return The application {@link Context} associated with this WorkManager.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public Context getApplicationContext() {
+        return mContext;
+    }
+
+    /**
      * @return The {@link WorkDatabase} instance associated with this WorkManager.
      * @hide
      */
@@ -345,6 +357,16 @@
     }
 
     @Override
+    public LiveData<Long> getLastCancelAllTimeMillis() {
+        return mPreferences.getLastCancelAllTimeMillisLiveData();
+    }
+
+    @Override
+    public long getLastCancelAllTimeMillisSync() {
+        return mPreferences.getLastCancelAllTimeMillis();
+    }
+
+    @Override
     public LiveData<WorkStatus> getStatusById(@NonNull UUID id) {
         WorkSpecDao dao = mWorkDatabase.workSpecDao();
         LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
index 46f1b45..e117c95 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
@@ -176,6 +176,9 @@
                         cancel(workManagerImpl, workSpecId);
                     }
                     workDatabase.setTransactionSuccessful();
+                    // Update the preferences
+                    new Preferences(workManagerImpl.getApplicationContext())
+                            .setLastCancelAllTimeMillis(System.currentTimeMillis());
                 } finally {
                     workDatabase.endTransaction();
                 }
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
new file mode 100644
index 0000000..762f6b0
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
@@ -0,0 +1,109 @@
+/*
+ * 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.utils;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Preferences for WorkManager.
+ *
+ * TODO: Migrate all preferences, including IdGenerator, to this file.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class Preferences {
+
+    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 SharedPreferences mSharedPreferences;
+
+    public Preferences(Context context) {
+        mSharedPreferences =
+                context.getSharedPreferences(PREFERENCES_FILE_NAME, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * @return The last time (in milliseconds) a {@code cancelAll} method was called
+     */
+    public long getLastCancelAllTimeMillis() {
+        return mSharedPreferences.getLong(KEY_LAST_CANCEL_ALL_TIME_MS, 0L);
+    }
+
+    /**
+     * @return A {@link LiveData} of the last time (in milliseconds) a {@code cancelAll} method was
+     *         called
+     */
+    public LiveData<Long> getLastCancelAllTimeMillisLiveData() {
+        return new LastCancelAllLiveData(mSharedPreferences);
+    }
+
+    /**
+     * Sets the last time a {@code cancelAll} method was called
+     *
+     * @param timeMillis The time a {@code cancelAll} method was called (in milliseconds)
+     */
+    public void setLastCancelAllTimeMillis(long timeMillis) {
+        mSharedPreferences.edit().putLong(KEY_LAST_CANCEL_ALL_TIME_MS, timeMillis).apply();
+    }
+
+    /**
+     * A {@link android.arch.lifecycle.LiveData} that responds to changes in
+     * {@link SharedPreferences} for the {@code lastCancelAllTime} value.
+     */
+    private static class LastCancelAllLiveData extends MutableLiveData<Long>
+            implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+        private SharedPreferences mSharedPreferences;
+        private long mLastCancelAllTimeMillis;
+
+        LastCancelAllLiveData(SharedPreferences sharedPreferences) {
+            mSharedPreferences = sharedPreferences;
+            mLastCancelAllTimeMillis = mSharedPreferences.getLong(KEY_LAST_CANCEL_ALL_TIME_MS, 0L);
+            postValue(mLastCancelAllTimeMillis);
+        }
+
+        @Override
+        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+            if (KEY_LAST_CANCEL_ALL_TIME_MS.equals(key)) {
+                long lastCancelAllTimeMillis = sharedPreferences.getLong(key, 0L);
+                if (mLastCancelAllTimeMillis != lastCancelAllTimeMillis) {
+                    mLastCancelAllTimeMillis = lastCancelAllTimeMillis;
+                    setValue(mLastCancelAllTimeMillis);
+                }
+            }
+        }
+
+        @Override
+        protected void onActive() {
+            super.onActive();
+            mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
+        }
+
+        @Override
+        protected void onInactive() {
+            super.onInactive();
+            mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+        }
+    }
+}