From 4e34c1f8146c680986965fa1695cbd530b684121 Mon Sep 17 00:00:00 2001 From: Sumir Kataria Date: Thu, 7 Jun 2018 16:40:01 -0700 Subject: Add pruneWork(Sync) methods. This method will prune work matching the following criteria: - Finished (succeeded, failed, cancelled) - Zero unfinished dependents This, together with cancelAll, gives the ability for people to clear jobs and solves the "observation of a growing list of jobs" problem. Change-Id: Ied5571f3d143659f5d52cc4e1983e96329e91390 Fixes: 79950952 Test: Added and ran tests. --- .../androidx/work/impl/WorkManagerImplTest.java | 32 +++++++++++++++ .../java/androidx/work/SynchronousWorkManager.java | 15 +++++++ .../src/main/java/androidx/work/WorkManager.java | 14 +++++++ .../java/androidx/work/impl/WorkManagerImpl.java | 15 +++++++ .../java/androidx/work/impl/model/WorkSpecDao.java | 13 ++++++ .../work/impl/utils/PruneWorkRunnable.java | 47 ++++++++++++++++++++++ 6 files changed, 136 insertions(+) create mode 100644 work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java 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 7168600890..a96c677ece 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java @@ -1331,6 +1331,38 @@ public class WorkManagerImplTest { cancelAllTimeLiveData.removeObservers(testLifecycleOwner); } + @Test + @SmallTest + public void pruneFinishedWork() { + OneTimeWorkRequest enqueuedWork = new OneTimeWorkRequest.Builder(TestWorker.class).build(); + OneTimeWorkRequest finishedWork = + new OneTimeWorkRequest.Builder(TestWorker.class).setInitialState(SUCCEEDED).build(); + OneTimeWorkRequest finishedWorkWithUnfinishedDependent = + new OneTimeWorkRequest.Builder(TestWorker.class).setInitialState(SUCCEEDED).build(); + OneTimeWorkRequest finishedWorkWithLongKeepForAtLeast = + new OneTimeWorkRequest.Builder(TestWorker.class) + .setInitialState(SUCCEEDED) + .keepResultsForAtLeast(999, TimeUnit.DAYS) + .build(); + + insertWorkSpecAndTags(enqueuedWork); + insertWorkSpecAndTags(finishedWork); + insertWorkSpecAndTags(finishedWorkWithUnfinishedDependent); + insertWorkSpecAndTags(finishedWorkWithLongKeepForAtLeast); + + insertDependency(enqueuedWork, finishedWorkWithUnfinishedDependent); + + mWorkManagerImpl.synchronous().pruneWorkSync(); + + WorkSpecDao workSpecDao = mDatabase.workSpecDao(); + assertThat(workSpecDao.getWorkSpec(enqueuedWork.getStringId()), is(notNullValue())); + assertThat(workSpecDao.getWorkSpec(finishedWork.getStringId()), is(nullValue())); + assertThat(workSpecDao.getWorkSpec(finishedWorkWithUnfinishedDependent.getStringId()), + is(notNullValue())); + assertThat(workSpecDao.getWorkSpec(finishedWorkWithLongKeepForAtLeast.getStringId()), + is(nullValue())); + } + @Test @SmallTest public void testSynchronousCancelAndGetStatus() { diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java index f01e363152..9f633d8e3e 100644 --- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java +++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java @@ -21,6 +21,7 @@ import android.support.annotation.WorkerThread; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * Blocking methods for {@link WorkManager} operations. These methods are expected to be called @@ -128,6 +129,20 @@ public interface SynchronousWorkManager { @WorkerThread long getLastCancelAllTimeMillisSync(); + /** + * Prunes all eligible finished work from the internal database in a synchronous fashion. + * Eligible work must be finished ({@link State#SUCCEEDED}, {@link State#FAILED}, or + * {@link State#CANCELLED}), with zero unfinished dependents. + *

+ * Use this method with caution; by invoking it, you (and any modules and libraries in + * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work. + * You do not normally need to call this method - WorkManager takes care to auto-prune its work + * after a sane period of time. This method also ignores the + * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy. + */ + @WorkerThread + void pruneWorkSync(); + /** * 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 83891dccef..5960e50fca 100644 --- a/work/workmanager/src/main/java/androidx/work/WorkManager.java +++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java @@ -26,6 +26,7 @@ import androidx.work.impl.WorkManagerImpl; import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * WorkManager is a library used to enqueue work that is guaranteed to execute after its constraints @@ -287,6 +288,19 @@ public abstract class WorkManager { */ public abstract void cancelAllWork(); + /** + * Prunes all eligible finished work from the internal database. Eligible work must be finished + * ({@link State#SUCCEEDED}, {@link State#FAILED}, or {@link State#CANCELLED}), with zero + * unfinished dependents. + *

+ * Use this method with caution; by invoking it, you (and any modules and libraries in + * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work. + * You do not normally need to call this method - WorkManager takes care to auto-prune its work + * after a sane period of time. This method also ignores the + * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy. + */ + public abstract void pruneWork(); + /** * 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 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 5b659d1c67..13ba38f352 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java +++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java @@ -43,6 +43,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.PruneWorkRunnable; import androidx.work.impl.utils.StartWorkRunnable; import androidx.work.impl.utils.StopWorkRunnable; import androidx.work.impl.utils.taskexecutor.TaskExecutor; @@ -340,6 +341,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @Override + @WorkerThread public void cancelUniqueWorkSync(@NonNull String uniqueWorkName) { assertBackgroundThread("Cannot cancelAllWorkByNameBlocking on main thread!"); CancelWorkRunnable.forName(uniqueWorkName, this).run(); @@ -351,6 +353,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @Override + @WorkerThread public void cancelAllWorkSync() { assertBackgroundThread("Cannot cancelAllWorkSync on main thread!"); CancelWorkRunnable.forAll(this).run(); @@ -366,6 +369,18 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag return mPreferences.getLastCancelAllTimeMillis(); } + @Override + public void pruneWork() { + mTaskExecutor.executeOnBackgroundThread(new PruneWorkRunnable(this)); + } + + @Override + @WorkerThread + public void pruneWorkSync() { + assertBackgroundThread("Cannot pruneWork on main thread!"); + new PruneWorkRunnable(this).run(); + } + @Override public LiveData getStatusById(@NonNull UUID id) { WorkSpecDao dao = mWorkDatabase.workSpecDao(); diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java index 7d9775d380..f08658fb08 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java +++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java @@ -291,4 +291,17 @@ public interface WorkSpecDao { + ")" ) List getEligibleWorkForScheduling(); + + /** + * Immediately prunes eligible work from the database meeting the following criteria: + * - Is finished (succeeded, failed, or cancelled) + * - Has zero unfinished dependents + */ + @Query("DELETE FROM workspec WHERE " + + "state IN " + COMPLETED_STATES + + " AND (SELECT COUNT(*)=0 FROM dependency WHERE " + + " prerequisite_id=id AND " + + " work_spec_id NOT IN " + + " (SELECT id FROM workspec WHERE state IN " + COMPLETED_STATES + "))") + void pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast(); } diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java new file mode 100644 index 0000000000..bf93f4602a --- /dev/null +++ b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java @@ -0,0 +1,47 @@ +/* + * 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.support.annotation.RestrictTo; + +import androidx.work.impl.WorkDatabase; +import androidx.work.impl.WorkManagerImpl; +import androidx.work.impl.model.WorkSpecDao; + +/** + * A Runnable that prunes work in the background. Pruned work meets the following criteria: + * - Is finished (succeeded, failed, or cancelled) + * - Has zero unfinished dependents + * + * @hide + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public class PruneWorkRunnable implements Runnable { + + private WorkManagerImpl mWorkManagerImpl; + + public PruneWorkRunnable(WorkManagerImpl workManagerImpl) { + mWorkManagerImpl = workManagerImpl; + } + + @Override + public void run() { + WorkDatabase workDatabase = mWorkManagerImpl.getWorkDatabase(); + WorkSpecDao workSpecDao = workDatabase.workSpecDao(); + workSpecDao.pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast(); + } +} -- cgit v1.2.3 From e326592ec414dfe4c002e2840d9fc4aef0ee8747 Mon Sep 17 00:00:00 2001 From: Rahul Ravikumar Date: Thu, 7 Jun 2018 16:35:15 -0700 Subject: Make the scheduler limit configurable. - on API 23, use half the scheduler limit to take into account of double scheduling. Test: Unit tests pass on both scheduler implementations. Change-Id: Ie7ba6e056d9e8911261599ed1fbb1dddf6af99fb --- .../firebase/FirebaseDelayedJobAlarmReceiver.java | 4 +- .../java/androidx/work/WorkSpecDaoTest.java | 14 ++-- .../java/androidx/work/impl/ProcessorTest.java | 3 + .../work/impl/WorkContinuationImplTest.java | 14 ++-- .../impl/WorkManagerImplLargeExecutorTest.java | 82 ++++++++++++++++++--- .../java/androidx/work/impl/WorkerWrapperTest.java | 84 ++++++++++++---------- .../systemalarm/SystemAlarmDispatcherTest.java | 5 ++ .../impl/workers/ConstraintTrackingWorkerTest.java | 17 +++-- .../androidx/work/worker/CheckLimitsWorker.java | 56 +++++++++++++++ .../src/main/java/androidx/work/Configuration.java | 56 +++++++++++++++ .../main/java/androidx/work/impl/Processor.java | 17 +++-- .../main/java/androidx/work/impl/Schedulers.java | 6 +- .../java/androidx/work/impl/WorkManagerImpl.java | 3 +- .../java/androidx/work/impl/WorkerWrapper.java | 10 ++- .../systemalarm/ConstraintsCommandHandler.java | 7 +- .../java/androidx/work/impl/model/WorkSpecDao.java | 5 +- .../work/impl/utils/CancelWorkRunnable.java | 5 +- .../androidx/work/impl/utils/EnqueueRunnable.java | 5 +- 18 files changed, 315 insertions(+), 78 deletions(-) create mode 100644 work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java diff --git a/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java b/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java index f17fde4dbb..5e549d2509 100644 --- a/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java +++ b/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.support.annotation.RestrictTo; import android.util.Log; +import androidx.work.Configuration; import androidx.work.impl.Schedulers; import androidx.work.impl.WorkDatabase; import androidx.work.impl.WorkManagerImpl; @@ -41,6 +42,7 @@ public class FirebaseDelayedJobAlarmReceiver extends BroadcastReceiver { final PendingResult pendingResult = goAsync(); final String workSpecId = intent.getStringExtra(WORKSPEC_ID_KEY); final WorkManagerImpl workManagerImpl = WorkManagerImpl.getInstance(); + final Configuration configuration = workManagerImpl.getConfiguration(); final WorkDatabase database = workManagerImpl.getWorkDatabase(); // TODO (rahulrav@) Use WorkManager's task executor here instead. new Thread(new Runnable() { @@ -48,7 +50,7 @@ public class FirebaseDelayedJobAlarmReceiver extends BroadcastReceiver { public void run() { WorkSpec workSpec = database.workSpecDao().getWorkSpec(workSpecId); if (workSpec != null) { - Schedulers.schedule(database, workManagerImpl.getSchedulers()); + Schedulers.schedule(configuration, database, workManagerImpl.getSchedulers()); } else { Log.e(TAG, "WorkSpec not found! Cannot schedule!"); } diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java index da5a8e019e..6dd23cbfa8 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java @@ -19,6 +19,7 @@ package androidx.work; import static androidx.work.State.BLOCKED; import static androidx.work.State.FAILED; import static androidx.work.State.SUCCEEDED; +import static androidx.work.impl.Scheduler.MAX_SCHEDULER_LIMIT; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -70,7 +71,9 @@ public class WorkSpecDaoTest extends DatabaseTest { WorkSpecDao workSpecDao = mDatabase.workSpecDao(); // Treat the scheduled request as previously scheduled workSpecDao.markWorkSpecScheduled(scheduled.getStringId(), System.currentTimeMillis()); - List eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List eligibleWorkSpecs = + workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT); + assertThat(eligibleWorkSpecs.size(), equalTo(2)); assertThat(eligibleWorkSpecs, containsInAnyOrder(work.getWorkSpec(), enqueued.getWorkSpec())); @@ -100,7 +103,8 @@ public class WorkSpecDaoTest extends DatabaseTest { insertWork(succeeded); insertWork(failed); - List eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List eligibleWorkSpecs = + workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT); assertThat(eligibleWorkSpecs, notNullValue()); assertThat(eligibleWorkSpecs.size(), is(1)); assertThat(eligibleWorkSpecs, containsInAnyOrder(enqueued.getWorkSpec())); @@ -131,7 +135,8 @@ public class WorkSpecDaoTest extends DatabaseTest { insertWork(succeeded); insertWork(failed); - List eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List eligibleWorkSpecs = + workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT); assertThat(eligibleWorkSpecs, notNullValue()); assertThat(eligibleWorkSpecs.size(), is(0)); } @@ -171,7 +176,8 @@ public class WorkSpecDaoTest extends DatabaseTest { workSpecDao.resetScheduledState(); - List eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List eligibleWorkSpecs = + workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT); assertThat(eligibleWorkSpecs.size(), is(1)); // Not using contains in any order as the scheduleRequestedAt changes post reset. assertThat(eligibleWorkSpecs.get(0).id, is(enqueued.getStringId())); diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java index cfaa1f3857..1e178b1d47 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java @@ -25,6 +25,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import androidx.work.Configuration; import androidx.work.DatabaseTest; import androidx.work.OneTimeWorkRequest; import androidx.work.worker.InfiniteTestWorker; @@ -43,8 +44,10 @@ public class ProcessorTest extends DatabaseTest { @Before public void setUp() { Context appContext = InstrumentationRegistry.getTargetContext().getApplicationContext(); + Configuration configuration = new Configuration.Builder().build(); mProcessor = new Processor( appContext, + configuration, mDatabase, Collections.singletonList(mock(Scheduler.class)), Executors.newSingleThreadScheduledExecutor()) { diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java index 778320853f..f4a3a7b09b 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java @@ -72,6 +72,7 @@ import java.util.concurrent.Executors; @SmallTest public class WorkContinuationImplTest extends WorkManagerTest { + private Configuration mConfiguration; private WorkDatabase mDatabase; private WorkManagerImpl mWorkManagerImpl; private Scheduler mScheduler; @@ -103,11 +104,11 @@ public class WorkContinuationImplTest extends WorkManagerTest { mScheduler = mock(Scheduler.class); Context context = InstrumentationRegistry.getTargetContext(); - Configuration configuration = new Configuration.Builder() + mConfiguration = new Configuration.Builder() .setExecutor(Executors.newSingleThreadExecutor()) .build(); - mWorkManagerImpl = spy(new WorkManagerImpl(context, configuration)); + mWorkManagerImpl = spy(new WorkManagerImpl(context, mConfiguration)); when(mWorkManagerImpl.getSchedulers()).thenReturn(Collections.singletonList(mScheduler)); WorkManagerImpl.setDelegate(mWorkManagerImpl); mDatabase = mWorkManagerImpl.getWorkDatabase(); @@ -306,7 +307,8 @@ public class WorkContinuationImplTest extends WorkManagerTest { // TODO(sumir): I can't seem to get this kicked off automatically, so I'm running it myself. // Figure out what's going on here. - new WorkerWrapper.Builder(InstrumentationRegistry.getTargetContext(), mDatabase, joinId) + Context context = InstrumentationRegistry.getTargetContext(); + new WorkerWrapper.Builder(context, mConfiguration, mDatabase, joinId) .build() .run(); @@ -514,6 +516,7 @@ public class WorkContinuationImplTest extends WorkManagerTest { } private static void verifyScheduled(Scheduler scheduler, WorkContinuationImpl continuation) { + Configuration configuration = continuation.getWorkManagerImpl().getConfiguration(); ArgumentCaptor captor = ArgumentCaptor.forClass(WorkSpec.class); verify(scheduler, times(1)).schedule(captor.capture()); List workSpecs = captor.getAllValues(); @@ -521,7 +524,10 @@ public class WorkContinuationImplTest extends WorkManagerTest { WorkDatabase workDatabase = continuation.getWorkManagerImpl().getWorkDatabase(); List eligibleWorkSpecs = - workDatabase.workSpecDao().getEligibleWorkForScheduling(); + workDatabase + .workSpecDao() + .getEligibleWorkForScheduling( + configuration.getMaxSchedulerLimit()); Set capturedIds = new HashSet<>(); for (WorkSpec workSpec : workSpecs) { diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java index 51e98e5728..72ebe944b0 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java @@ -16,25 +16,33 @@ package androidx.work.impl; +import static androidx.work.worker.CheckLimitsWorker.KEY_EXCEEDS_SCHEDULER_LIMIT; +import static androidx.work.worker.CheckLimitsWorker.KEY_LIMIT_TO_ENFORCE; + +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.lessThanOrEqualTo; import static java.util.concurrent.TimeUnit.SECONDS; import android.arch.core.executor.ArchTaskExecutor; import android.arch.core.executor.TaskExecutor; +import android.arch.lifecycle.Observer; import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.filters.SdkSuppress; import android.support.test.runner.AndroidJUnit4; import androidx.work.Configuration; +import androidx.work.Data; import androidx.work.OneTimeWorkRequest; -import androidx.work.impl.model.WorkSpec; +import androidx.work.TestLifecycleOwner; +import androidx.work.WorkContinuation; +import androidx.work.WorkStatus; import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule; -import androidx.work.worker.TestWorker; +import androidx.work.worker.CheckLimitsWorker; import org.junit.After; import org.junit.Before; @@ -42,16 +50,23 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class WorkManagerImplLargeExecutorTest { private static final int NUM_WORKERS = 500; + private static final int TEST_TIMEOUT_SECONDS = 30; // ThreadPoolExecutor parameters. private static final int MIN_POOL_SIZE = 0; @@ -61,6 +76,7 @@ public class WorkManagerImplLargeExecutorTest { private static final long KEEP_ALIVE_TIME = 2L; private WorkManagerImpl mWorkManagerImpl; + private TestLifecycleOwner mLifecycleOwner; @Rule public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule(); @@ -90,8 +106,10 @@ public class WorkManagerImplLargeExecutorTest { MIN_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, SECONDS, queue); Configuration configuration = new Configuration.Builder() .setExecutor(executor) + .setMaxSchedulerLimit(50) .build(); - mWorkManagerImpl = new WorkManagerImpl(context, configuration); + mWorkManagerImpl = new WorkManagerImpl(context, configuration, true); + mLifecycleOwner = new TestLifecycleOwner(); WorkManagerImpl.setDelegate(mWorkManagerImpl); } @@ -104,15 +122,57 @@ public class WorkManagerImplLargeExecutorTest { @Test @LargeTest @SdkSuppress(maxSdkVersion = 22) - public void testSchedulerLimits() { + public void testSchedulerLimits() throws InterruptedException { + List workRequests = new ArrayList<>(NUM_WORKERS); + final Set completed = new HashSet<>(NUM_WORKERS); + final int schedulerLimit = mWorkManagerImpl + .getConfiguration() + .getMaxSchedulerLimit(); + + final Data input = new Data.Builder() + .putInt(KEY_LIMIT_TO_ENFORCE, schedulerLimit) + .build(); + for (int i = 0; i < NUM_WORKERS; i++) { - mWorkManagerImpl.enqueue(OneTimeWorkRequest.from(TestWorker.class)); - List eligibleWorkSpecs = mWorkManagerImpl.getWorkDatabase() - .workSpecDao() - .getEligibleWorkForScheduling(); + OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(CheckLimitsWorker.class) + .setInputData(input) + .build(); - int size = eligibleWorkSpecs != null ? eligibleWorkSpecs.size() : 0; - assertThat(size, lessThanOrEqualTo(Scheduler.MAX_SCHEDULER_LIMIT)); + workRequests.add(request); } + + + final CountDownLatch latch = new CountDownLatch(NUM_WORKERS); + WorkContinuation continuation = mWorkManagerImpl.beginWith(workRequests); + + continuation.getStatuses() + .observe(mLifecycleOwner, new Observer>() { + @Override + public void onChanged(@Nullable List workStatuses) { + if (workStatuses == null || workStatuses.isEmpty()) { + return; + } + + for (WorkStatus workStatus: workStatuses) { + if (workStatus.getState().isFinished()) { + + Data output = workStatus.getOutputData(); + + boolean exceededLimits = output.getBoolean( + KEY_EXCEEDS_SCHEDULER_LIMIT, true); + + assertThat(exceededLimits, is(false)); + if (!completed.contains(workStatus.getId())) { + completed.add(workStatus.getId()); + latch.countDown(); + } + } + } + } + }); + + continuation.enqueue(); + latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertThat(latch.getCount(), is(0L)); } } diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java index 4a50d10965..739fcf7b62 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java @@ -44,6 +44,7 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import androidx.work.ArrayCreatingInputMerger; +import androidx.work.Configuration; import androidx.work.Data; import androidx.work.DatabaseTest; import androidx.work.OneTimeWorkRequest; @@ -78,6 +79,7 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class WorkerWrapperTest extends DatabaseTest { + private Configuration mConfiguration; private WorkSpecDao mWorkSpecDao; private DependencyDao mDependencyDao; private Context mContext; @@ -90,6 +92,7 @@ public class WorkerWrapperTest extends DatabaseTest { @Before public void setUp() { mContext = InstrumentationRegistry.getTargetContext(); + mConfiguration = new Configuration.Builder().build(); mWorkSpecDao = spy(mDatabase.workSpecDao()); mDependencyDao = mDatabase.dependencyDao(); mMockListener = mock(ExecutionListener.class); @@ -101,7 +104,7 @@ public class WorkerWrapperTest extends DatabaseTest { public void testSuccess() { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withListener(mMockListener) .build() .run(); @@ -114,7 +117,7 @@ public class WorkerWrapperTest extends DatabaseTest { public void testRunAttemptCountIncremented_successfulExecution() { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build() @@ -128,7 +131,7 @@ public class WorkerWrapperTest extends DatabaseTest { public void testRunAttemptCountIncremented_failedExecution() { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build() @@ -141,7 +144,7 @@ public class WorkerWrapperTest extends DatabaseTest { @SmallTest public void testPermanentErrorWithInvalidWorkSpecId() { final String invalidWorkSpecId = "INVALID_ID"; - new WorkerWrapper.Builder(mContext, mDatabase, invalidWorkSpecId) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, invalidWorkSpecId) .withListener(mMockListener) .build() .run(); @@ -155,7 +158,7 @@ public class WorkerWrapperTest extends DatabaseTest { .setInitialState(RUNNING) .build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withListener(mMockListener) .build() .run(); @@ -169,7 +172,7 @@ public class WorkerWrapperTest extends DatabaseTest { .setInitialState(CANCELLED) .build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withListener(mMockListener) .build() .run(); @@ -183,7 +186,7 @@ public class WorkerWrapperTest extends DatabaseTest { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); getWorkSpec(work).workerClassName = "INVALID_CLASS_NAME"; insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withListener(mMockListener) .build() .run(); @@ -197,7 +200,7 @@ public class WorkerWrapperTest extends DatabaseTest { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); getWorkSpec(work).inputMergerClassName = "INVALID_CLASS_NAME"; insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build() @@ -211,7 +214,7 @@ public class WorkerWrapperTest extends DatabaseTest { public void testFailed() { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withListener(mMockListener) .build() .run(); @@ -224,7 +227,8 @@ public class WorkerWrapperTest extends DatabaseTest { public void testRunning() throws InterruptedException { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build(); insertWork(work); - WorkerWrapper wrapper = new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + WorkerWrapper wrapper = new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + work.getStringId()) .withListener(mMockListener) .build(); Executors.newSingleThreadExecutor().submit(wrapper); @@ -241,7 +245,7 @@ public class WorkerWrapperTest extends DatabaseTest { .setInitialState(RUNNING) .build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withListener(mMockListener) .build() .run(); @@ -271,7 +275,8 @@ public class WorkerWrapperTest extends DatabaseTest { assertThat(mWorkSpecDao.getState(work.getStringId()), is(BLOCKED)); assertThat(mDependencyDao.hasCompletedAllPrerequisites(work.getStringId()), is(false)); - new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + prerequisiteWork.getStringId()) .withListener(mMockListener) .withSchedulers(Collections.singletonList(mMockScheduler)) .build() @@ -306,7 +311,8 @@ public class WorkerWrapperTest extends DatabaseTest { mDatabase.endTransaction(); } - new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + prerequisiteWork.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .build().run(); @@ -349,17 +355,19 @@ public class WorkerWrapperTest extends DatabaseTest { } // Run the prerequisites. - new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork1.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + prerequisiteWork1.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .build().run(); - new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork2.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + prerequisiteWork2.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .build().run(); // Create and run the dependent work. WorkerWrapper workerWrapper = - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .build(); workerWrapper.run(); @@ -392,7 +400,8 @@ public class WorkerWrapperTest extends DatabaseTest { long beforeUnblockedTime = System.currentTimeMillis(); - new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + prerequisiteWork.getStringId()) .withListener(mMockListener) .withSchedulers(Collections.singletonList(mMockScheduler)) .build() @@ -429,7 +438,8 @@ public class WorkerWrapperTest extends DatabaseTest { mDatabase.endTransaction(); } - new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, + prerequisiteWork.getStringId()) .build() .run(); @@ -452,7 +462,7 @@ public class WorkerWrapperTest extends DatabaseTest { insertWork(periodicWork); - new WorkerWrapper.Builder(mContext, mDatabase, periodicWork.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWork.getStringId()) .withListener(mMockListener) .build() .run(); @@ -475,7 +485,7 @@ public class WorkerWrapperTest extends DatabaseTest { insertWork(periodicWork); - new WorkerWrapper.Builder(mContext, mDatabase, periodicWork.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWork.getStringId()) .withListener(mMockListener) .build() .run(); @@ -495,7 +505,7 @@ public class WorkerWrapperTest extends DatabaseTest { final String periodicWorkId = periodicWork.getStringId(); insertWork(periodicWork); - new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId) .withListener(mMockListener) .build() .run(); @@ -517,7 +527,7 @@ public class WorkerWrapperTest extends DatabaseTest { final String periodicWorkId = periodicWork.getStringId(); insertWork(periodicWork); - new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId) .withListener(mMockListener) .build() .run(); @@ -539,7 +549,7 @@ public class WorkerWrapperTest extends DatabaseTest { final String periodicWorkId = periodicWork.getStringId(); insertWork(periodicWork); - new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId) .withListener(mMockListener) .build() .run(); @@ -557,7 +567,7 @@ public class WorkerWrapperTest extends DatabaseTest { insertWork(work); Scheduler mockScheduler = mock(Scheduler.class); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mockScheduler)) .build() .run(); @@ -629,8 +639,8 @@ public class WorkerWrapperTest extends DatabaseTest { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); Extras.RuntimeExtras runtimeExtras = new Extras.RuntimeExtras(); - runtimeExtras.triggeredContentAuthorities = new String[] { "tca1", "tca2", "tca3" }; - runtimeExtras.triggeredContentUris = new Uri[] { Uri.parse("tcu1"), Uri.parse("tcu2") }; + runtimeExtras.triggeredContentAuthorities = new String[]{"tca1", "tca2", "tca3"}; + runtimeExtras.triggeredContentUris = new Uri[]{Uri.parse("tcu1"), Uri.parse("tcu2")}; Worker worker = WorkerWrapper.workerFromWorkSpec( mContext, @@ -653,7 +663,7 @@ public class WorkerWrapperTest extends DatabaseTest { OneTimeWorkRequest unscheduled = new OneTimeWorkRequest.Builder(TestWorker.class).build(); insertWork(unscheduled); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build() @@ -673,7 +683,7 @@ public class WorkerWrapperTest extends DatabaseTest { OneTimeWorkRequest unscheduled = new OneTimeWorkRequest.Builder(TestWorker.class).build(); insertWork(unscheduled); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build() @@ -691,7 +701,7 @@ public class WorkerWrapperTest extends DatabaseTest { insertWork(work); WorkerWrapper workerWrapper = - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build(); @@ -717,7 +727,7 @@ public class WorkerWrapperTest extends DatabaseTest { assertThat(worker.isStopped(), is(false)); WorkerWrapper workerWrapper = - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .withWorker(worker) @@ -744,7 +754,7 @@ public class WorkerWrapperTest extends DatabaseTest { assertThat(worker.isStopped(), is(false)); WorkerWrapper workerWrapper = - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .withWorker(worker) @@ -761,7 +771,7 @@ public class WorkerWrapperTest extends DatabaseTest { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ExceptionWorker.class).build(); insertWork(work); - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build() @@ -777,7 +787,7 @@ public class WorkerWrapperTest extends DatabaseTest { insertWork(work); WorkerWrapper workerWrapper = - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) .withSchedulers(Collections.singletonList(mMockScheduler)) .withListener(mMockListener) .build(); @@ -797,10 +807,10 @@ public class WorkerWrapperTest extends DatabaseTest { insertWork(work); WorkerWrapper workerWrapper = - new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId()) - .withSchedulers(Collections.singletonList(mMockScheduler)) - .withListener(mMockListener) - .build(); + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId()) + .withSchedulers(Collections.singletonList(mMockScheduler)) + .withListener(mMockListener) + .build(); Executors.newSingleThreadExecutor().submit(workerWrapper); Thread.sleep(1000L); 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 5e36dfd0c7..cfe2cef7db 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 @@ -34,6 +34,7 @@ import android.support.test.filters.LargeTest; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import androidx.work.Configuration; import androidx.work.Constraints; import androidx.work.DatabaseTest; import androidx.work.OneTimeWorkRequest; @@ -81,6 +82,7 @@ public class SystemAlarmDispatcherTest extends DatabaseTest { private Context mContext; private Scheduler mScheduler; private WorkManagerImpl mWorkManager; + private Configuration mConfiguration; private ExecutorService mExecutorService; private Processor mProcessor; private Processor mSpyProcessor; @@ -108,10 +110,13 @@ public class SystemAlarmDispatcherTest extends DatabaseTest { } }; + mConfiguration = new Configuration.Builder().build(); when(mWorkManager.getWorkDatabase()).thenReturn(mDatabase); + when(mWorkManager.getConfiguration()).thenReturn(mConfiguration); mExecutorService = Executors.newSingleThreadExecutor(); mProcessor = new Processor( mContext, + mConfiguration, mDatabase, Collections.singletonList(mScheduler), // simulate real world use-case diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java index a86df66041..6f136bf0b6 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java @@ -31,6 +31,7 @@ import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import androidx.work.Configuration; import androidx.work.Constraints; import androidx.work.Data; import androidx.work.DatabaseTest; @@ -76,6 +77,7 @@ public class ConstraintTrackingWorkerTest extends DatabaseTest implements Execut private ExecutorService mExecutorService; private WorkManagerImpl mWorkManagerImpl; + private Configuration mConfiguration; private Scheduler mScheduler; private Trackers mTracker; private BatteryChargingTracker mBatteryChargingTracker; @@ -89,10 +91,12 @@ public class ConstraintTrackingWorkerTest extends DatabaseTest implements Execut mHandler = new Handler(Looper.getMainLooper()); mExecutorService = Executors.newSingleThreadScheduledExecutor(); mLatch = new CountDownLatch(1); + mConfiguration = new Configuration.Builder().build(); mWorkManagerImpl = mock(WorkManagerImpl.class); mScheduler = mock(Scheduler.class); when(mWorkManagerImpl.getWorkDatabase()).thenReturn(mDatabase); + when(mWorkManagerImpl.getConfiguration()).thenReturn(mConfiguration); mBatteryChargingTracker = spy(new BatteryChargingTracker(mContext)); mBatteryNotLowTracker = spy(new BatteryNotLowTracker(mContext)); @@ -149,7 +153,8 @@ public class ConstraintTrackingWorkerTest extends DatabaseTest implements Execut ConstraintTrackingWorker spyWorker = spy(worker); when(spyWorker.getWorkDatabase()).thenReturn(mDatabase); - WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId); + WorkerWrapper.Builder builder = + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId); builder.withWorker(spyWorker) .withListener(this) .withSchedulers(Collections.singletonList(mScheduler)); @@ -194,7 +199,8 @@ public class ConstraintTrackingWorkerTest extends DatabaseTest implements Execut ConstraintTrackingWorker spyWorker = spy(worker); when(spyWorker.getWorkDatabase()).thenReturn(mDatabase); - WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId); + WorkerWrapper.Builder builder = + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId); builder.withWorker(spyWorker) .withListener(this) .withSchedulers(Collections.singletonList(mScheduler)); @@ -237,8 +243,8 @@ public class ConstraintTrackingWorkerTest extends DatabaseTest implements Execut ConstraintTrackingWorker spyWorker = spy(worker); when(spyWorker.getWorkDatabase()).thenReturn(mDatabase); - - WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId); + WorkerWrapper.Builder builder = + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId); builder.withWorker(spyWorker) .withListener(this) .withSchedulers(Collections.singletonList(mScheduler)); @@ -290,7 +296,8 @@ public class ConstraintTrackingWorkerTest extends DatabaseTest implements Execut ConstraintTrackingWorker spyWorker = spy(worker); when(spyWorker.getWorkDatabase()).thenReturn(mDatabase); - WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId); + WorkerWrapper.Builder builder = + new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId); builder.withWorker(spyWorker) .withListener(this) .withSchedulers(Collections.singletonList(mScheduler)); diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java new file mode 100644 index 0000000000..11763641ed --- /dev/null +++ b/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java @@ -0,0 +1,56 @@ +/* + * 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.worker; + +import static androidx.work.Worker.Result.SUCCESS; + +import android.support.annotation.NonNull; + +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.impl.Scheduler; +import androidx.work.impl.WorkManagerImpl; +import androidx.work.impl.model.WorkSpec; + +import java.util.List; + +public class CheckLimitsWorker extends Worker { + /* The limit to enforce */ + public static final String KEY_LIMIT_TO_ENFORCE = "limit"; + + /* The output key which tells us if we exceeded the scheduler limits. */ + public static final String KEY_EXCEEDS_SCHEDULER_LIMIT = "exceed_scheduler_limit"; + + @NonNull + @Override + public Result doWork() { + Data input = getInputData(); + int limitToEnforce = input.getInt(KEY_LIMIT_TO_ENFORCE, Scheduler.MAX_SCHEDULER_LIMIT); + WorkManagerImpl workManager = WorkManagerImpl.getInstance(); + List eligibleWorkSpecs = workManager.getWorkDatabase() + .workSpecDao() + .getEligibleWorkForScheduling(limitToEnforce); + int size = eligibleWorkSpecs != null ? eligibleWorkSpecs.size() : 0; + boolean exceedsLimits = size > limitToEnforce; + Data output = new Data.Builder() + .putBoolean(KEY_EXCEEDS_SCHEDULER_LIMIT, exceedsLimits) + .build(); + + setOutputData(output); + return SUCCESS; + } +} diff --git a/work/workmanager/src/main/java/androidx/work/Configuration.java b/work/workmanager/src/main/java/androidx/work/Configuration.java index 4fe1470424..77f5ffa9b4 100644 --- a/work/workmanager/src/main/java/androidx/work/Configuration.java +++ b/work/workmanager/src/main/java/androidx/work/Configuration.java @@ -16,7 +16,11 @@ package androidx.work; +import static androidx.work.impl.Scheduler.MAX_SCHEDULER_LIMIT; + +import android.os.Build; import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; import androidx.work.impl.utils.IdGenerator; @@ -28,9 +32,16 @@ import java.util.concurrent.Executors; */ public final class Configuration { + /** + * The minimum number of system requests which can be enqueued by {@link WorkManager} + * when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}. + */ + public static final int MIN_SCHEDULER_LIMIT = 20; + private final Executor mExecutor; private final int mMinJobSchedulerId; private final int mMaxJobSchedulerId; + private final int mMaxSchedulerLimit; private Configuration(@NonNull Configuration.Builder builder) { if (builder.mExecutor == null) { @@ -40,6 +51,7 @@ public final class Configuration { } mMinJobSchedulerId = builder.mMinJobSchedulerId; mMaxJobSchedulerId = builder.mMaxJobSchedulerId; + mMaxSchedulerLimit = builder.mMaxSchedulerLimit; } /** @@ -75,6 +87,22 @@ public final class Configuration { return mMaxJobSchedulerId; } + /** + * @return The maximum number of system requests which can be enqueued by {@link WorkManager} + * when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}. + * + * @hide + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public int getMaxSchedulerLimit() { + // We double schedule jobs in SDK 23. So use half the number of max slots specified. + if (Build.VERSION.SDK_INT == 23) { + return mMaxSchedulerLimit / 2; + } else { + return mMaxSchedulerLimit; + } + } + private Executor createDefaultExecutor() { return Executors.newFixedThreadPool( // This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR. @@ -88,6 +116,7 @@ public final class Configuration { int mMinJobSchedulerId = IdGenerator.INITIAL_ID; int mMaxJobSchedulerId = Integer.MAX_VALUE; + int mMaxSchedulerLimit = MIN_SCHEDULER_LIMIT; Executor mExecutor; /** @@ -121,6 +150,33 @@ public final class Configuration { return this; } + /** + * Specifies the maximum number of system requests made by {@link WorkManager} + * when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}. + * When the application exceeds this limit {@link WorkManager} maintains an internal queue + * of {@link WorkRequest}s, and enqueues when slots become free. + * + * {@link WorkManager} requires a minimum of {@link Configuration#MIN_SCHEDULER_LIMIT} + * slots. The total number of slots also cannot exceed {@code 100} which is + * the {@link android.app.job.JobScheduler} limit. + * + * @param maxSchedulerLimit The total number of jobs which can be enqueued by + * {@link WorkManager} when using + * {@link android.app.job.JobScheduler}. + * @return This {@link Builder} instance + * @throws IllegalArgumentException when the number of jobs < + * {@link Configuration#MIN_SCHEDULER_LIMIT} + */ + public Builder setMaxSchedulerLimit(int maxSchedulerLimit) { + if (maxSchedulerLimit < MIN_SCHEDULER_LIMIT) { + throw new IllegalArgumentException( + "WorkManager needs to be able to schedule at least 20 jobs in " + + "JobScheduler."); + } + mMaxSchedulerLimit = Math.min(maxSchedulerLimit, MAX_SCHEDULER_LIMIT); + return this; + } + /** * Specifies a custom {@link Executor} for WorkManager. * diff --git a/work/workmanager/src/main/java/androidx/work/impl/Processor.java b/work/workmanager/src/main/java/androidx/work/impl/Processor.java index 4092686fce..86b7edf5f3 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/Processor.java +++ b/work/workmanager/src/main/java/androidx/work/impl/Processor.java @@ -20,6 +20,8 @@ import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; import android.util.Log; +import androidx.work.Configuration; + import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -38,8 +40,8 @@ public class Processor implements ExecutionListener { private static final String TAG = "Processor"; private Context mAppContext; + private Configuration mConfiguration; private WorkDatabase mWorkDatabase; - private Map mEnqueuedWorkMap; private List mSchedulers; private Executor mExecutor; @@ -50,10 +52,12 @@ public class Processor implements ExecutionListener { public Processor( Context appContext, + Configuration configuration, WorkDatabase workDatabase, List schedulers, Executor executor) { mAppContext = appContext; + mConfiguration = configuration; mWorkDatabase = workDatabase; mEnqueuedWorkMap = new HashMap<>(); mSchedulers = schedulers; @@ -87,11 +91,12 @@ public class Processor implements ExecutionListener { return false; } - WorkerWrapper workWrapper = new WorkerWrapper.Builder(mAppContext, mWorkDatabase, id) - .withListener(this) - .withSchedulers(mSchedulers) - .withRuntimeExtras(runtimeExtras) - .build(); + WorkerWrapper workWrapper = + new WorkerWrapper.Builder(mAppContext, mConfiguration, mWorkDatabase, id) + .withListener(this) + .withSchedulers(mSchedulers) + .withRuntimeExtras(runtimeExtras) + .build(); mEnqueuedWorkMap.put(id, workWrapper); mExecutor.execute(workWrapper); Log.d(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id)); diff --git a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java index c6e5b81608..4944549d6f 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java +++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java @@ -25,6 +25,7 @@ import android.support.annotation.RestrictTo; import android.support.annotation.VisibleForTesting; import android.util.Log; +import androidx.work.Configuration; import androidx.work.impl.background.systemalarm.SystemAlarmScheduler; import androidx.work.impl.background.systemalarm.SystemAlarmService; import androidx.work.impl.background.systemjob.SystemJobScheduler; @@ -61,11 +62,14 @@ public class Schedulers { * @param schedulers The {@link List} of {@link Scheduler}s to delegate to. */ public static void schedule( + @NonNull Configuration configuration, @NonNull WorkDatabase workDatabase, List schedulers) { WorkSpecDao workSpecDao = workDatabase.workSpecDao(); - List eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List eligibleWorkSpecs = + workSpecDao.getEligibleWorkForScheduling( + configuration.getMaxSchedulerLimit()); scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs); } 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 48ab957fe7..0dcdda349c 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java +++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java @@ -167,6 +167,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag mTaskExecutor = WorkManagerTaskExecutor.getInstance(); mProcessor = new Processor( context, + mConfiguration, mWorkDatabase, getSchedulers(), configuration.getExecutor()); @@ -515,7 +516,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag // Delegate to the WorkManager's schedulers. // Using getters here so we can use from a mocked instance // of WorkManagerImpl. - Schedulers.schedule(getWorkDatabase(), getSchedulers()); + Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers()); } private void assertBackgroundThread(String errorMessage) { diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java index b6d4fc16cf..0b8c44c83e 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java +++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java @@ -30,6 +30,7 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; +import androidx.work.Configuration; import androidx.work.Data; import androidx.work.InputMerger; import androidx.work.State; @@ -63,6 +64,7 @@ public class WorkerWrapper implements Runnable { private WorkSpec mWorkSpec; Worker mWorker; + private Configuration mConfiguration; private WorkDatabase mWorkDatabase; private WorkSpecDao mWorkSpecDao; private DependencyDao mDependencyDao; @@ -78,6 +80,7 @@ public class WorkerWrapper implements Runnable { mRuntimeExtras = builder.mRuntimeExtras; mWorker = builder.mWorker; + mConfiguration = builder.mConfiguration; mWorkDatabase = builder.mWorkDatabase; mWorkSpecDao = mWorkDatabase.workSpecDao(); mDependencyDao = mWorkDatabase.dependencyDao(); @@ -300,7 +303,7 @@ public class WorkerWrapper implements Runnable { notifyListener(false, false); } - Schedulers.schedule(mWorkDatabase, mSchedulers); + Schedulers.schedule(mConfiguration, mWorkDatabase, mSchedulers); } private void recursivelyFailWorkAndDependents(String workSpecId) { @@ -370,7 +373,7 @@ public class WorkerWrapper implements Runnable { } // This takes of scheduling the dependent workers as they have been marked ENQUEUED. - Schedulers.schedule(mWorkDatabase, mSchedulers); + Schedulers.schedule(mConfiguration, mWorkDatabase, mSchedulers); } static Worker workerFromWorkSpec(@NonNull Context context, @@ -434,6 +437,7 @@ public class WorkerWrapper implements Runnable { private Context mAppContext; @Nullable private Worker mWorker; + private Configuration mConfiguration; private WorkDatabase mWorkDatabase; private String mWorkSpecId; private ExecutionListener mListener; @@ -441,9 +445,11 @@ public class WorkerWrapper implements Runnable { private Extras.RuntimeExtras mRuntimeExtras; public Builder(@NonNull Context context, + @NonNull Configuration configuration, @NonNull WorkDatabase database, @NonNull String workSpecId) { mAppContext = context.getApplicationContext(); + mConfiguration = configuration; mWorkDatabase = database; mWorkSpecId = workSpecId; } diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java index 94cc2db75a..6dbd752bea 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java +++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java @@ -60,9 +60,14 @@ class ConstraintsCommandHandler { @WorkerThread void handleConstraintsChanged() { + int schedulerLimit = mDispatcher + .getWorkManager() + .getConfiguration() + .getMaxSchedulerLimit(); + List candidates = mDispatcher.getWorkManager().getWorkDatabase() .workSpecDao() - .getEligibleWorkForScheduling(); + .getEligibleWorkForScheduling(schedulerLimit); // Filter candidates that are marked as SCHEDULE_NOT_REQUESTED_AT List eligibleWorkSpecs = new ArrayList<>(candidates.size()); diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java index f08658fb08..e15124fa8f 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java +++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java @@ -29,7 +29,6 @@ import android.support.annotation.NonNull; import androidx.work.Data; import androidx.work.State; -import androidx.work.impl.Scheduler; import java.util.List; @@ -285,12 +284,12 @@ public interface WorkSpecDao { // We only want WorkSpecs which have not been previously scheduled. + " AND schedule_requested_at=" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET + " LIMIT " - + "(SELECT " + Scheduler.MAX_SCHEDULER_LIMIT + "-COUNT(*) FROM workspec WHERE" + + "(SELECT :schedulerLimit" + "-COUNT(*) FROM workspec WHERE" + " schedule_requested_at<>" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET + " AND state NOT IN " + COMPLETED_STATES + ")" ) - List getEligibleWorkForScheduling(); + List getEligibleWorkForScheduling(int schedulerLimit); /** * Immediately prunes eligible work from the database meeting the following criteria: 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 e117c95bc9..842fdc2416 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 @@ -56,7 +56,10 @@ public abstract class CancelWorkRunnable implements Runnable { } void reschedulePendingWorkers(WorkManagerImpl workManagerImpl) { - Schedulers.schedule(workManagerImpl.getWorkDatabase(), workManagerImpl.getSchedulers()); + Schedulers.schedule( + workManagerImpl.getConfiguration(), + workManagerImpl.getWorkDatabase(), + workManagerImpl.getSchedulers()); } private void recursivelyCancelWorkAndDependents(WorkDatabase workDatabase, String workSpecId) { diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java index 004b895b89..00d7b8f920 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java +++ b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java @@ -106,7 +106,10 @@ public class EnqueueRunnable implements Runnable { @VisibleForTesting public void scheduleWorkInBackground() { WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl(); - Schedulers.schedule(workManager.getWorkDatabase(), workManager.getSchedulers()); + Schedulers.schedule( + workManager.getConfiguration(), + workManager.getWorkDatabase(), + workManager.getSchedulers()); } private static boolean processContinuation(@NonNull WorkContinuationImpl workContinuation) { -- cgit v1.2.3 From d865c2cf247d6b73d9f9510045dfd9ccd1f6ef12 Mon Sep 17 00:00:00 2001 From: Rahul Ravikumar Date: Fri, 8 Jun 2018 13:21:36 -0700 Subject: Rename `systemIdInfo` table to `SystemIdInfo`. - Update migration tests, DAO and the schemas. Test: Unit tests pass. Change-Id: Ic96751a6edc3498e2a3f8c9bca6642f114f23c8c --- .../java/androidx/work/WorkDatabaseMigrationTest.java | 8 ++++---- .../main/java/androidx/work/impl/WorkDatabaseMigrations.java | 12 ++++++------ .../src/main/java/androidx/work/impl/model/SystemIdInfo.java | 3 +-- .../main/java/androidx/work/impl/model/SystemIdInfoDao.java | 4 ++-- .../src/schemas/androidx.work.impl.WorkDatabase/2.json | 6 +++--- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java index 1c1893f2cf..ca82c50a44 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java @@ -53,12 +53,12 @@ public class WorkDatabaseMigrationTest { // Queries private static final String INSERT_ALARM_INFO = "INSERT INTO alarmInfo VALUES (?, ?)"; - private static final String INSERT_SYSTEM_ID_INFO = "INSERT INTO systemIdInfo VALUES (?, ?)"; - private static final String CHECK_SYSTEM_ID_INFO = "SELECT * FROM systemIdInfo"; + private static final String INSERT_SYSTEM_ID_INFO = "INSERT INTO SystemIdInfo VALUES (?, ?)"; + private static final String CHECK_SYSTEM_ID_INFO = "SELECT * FROM SystemIdInfo"; private static final String CHECK_ALARM_INFO = "SELECT * FROM alarmInfo"; private static final String CHECK_TABLE_NAME = "SELECT * FROM %s"; private static final String TABLE_ALARM_INFO = "alarmInfo"; - private static final String TABLE_SYSTEM_ID_INFO = "systemIdInfo"; + private static final String TABLE_SYSTEM_ID_INFO = "SystemIdInfo"; private static final String TABLE_WORKSPEC = "WorkSpec"; private static final String TABLE_WORKTAG = "WorkTag"; private static final String TABLE_WORKNAME = "WorkName"; @@ -127,7 +127,7 @@ public class WorkDatabaseMigrationTest { String workSpecId1 = UUID.randomUUID().toString(); String workSpecId2 = UUID.randomUUID().toString(); - // insert systemIdInfo + // insert SystemIdInfo database.execSQL(INSERT_SYSTEM_ID_INFO, new Object[]{workSpecId1, 1}); database.execSQL(INSERT_SYSTEM_ID_INFO, new Object[]{workSpecId2, 2}); diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java index 0c3e3db484..f5e67bf2ca 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java +++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java @@ -38,7 +38,7 @@ public class WorkDatabaseMigrations { private static final int VERSION_2 = 2; private static final String CREATE_SYSTEM_ID_INFO = - "CREATE TABLE IF NOT EXISTS `systemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`" + "CREATE TABLE IF NOT EXISTS `SystemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`" + " INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`), FOREIGN KEY(`work_spec_id`)" + " REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )"; @@ -49,20 +49,20 @@ public class WorkDatabaseMigrations { + "CASCADE )"; private static final String MIGRATE_ALARM_INFO_TO_SYSTEM_ID_INFO = - "INSERT INTO systemIdInfo(work_spec_id, system_id) " + "INSERT INTO SystemIdInfo(work_spec_id, system_id) " + "SELECT work_spec_id, alarm_id AS system_id FROM alarmInfo"; private static final String MIGRATE_SYSTEM_ID_INFO_TO_ALARM_INFO = "INSERT INTO alarmInfo(work_spec_id, alarm_id) " - + "SELECT work_spec_id, system_id AS alarm_id FROM systemIdInfo"; + + "SELECT work_spec_id, system_id AS alarm_id FROM SystemIdInfo"; private static final String REMOVE_ALARM_INFO = "DROP TABLE IF EXISTS alarmInfo"; - private static final String REMOVE_SYSTEM_ID_INFO = "DROP TABLE IF EXISTS systemIdInfo"; + private static final String REMOVE_SYSTEM_ID_INFO = "DROP TABLE IF EXISTS SystemIdInfo"; /** * Removes the {@code alarmInfo} table and substitutes it for a more general - * {@code systemIdInfo} table. + * {@code SystemIdInfo} table. */ public static Migration MIGRATION_1_2 = new Migration(VERSION_1, VERSION_2) { @Override @@ -75,7 +75,7 @@ public class WorkDatabaseMigrations { /** * Removes the {@code alarmInfo} table and substitutes it for a more general - * {@code systemIdInfo} table. + * {@code SystemIdInfo} table. */ public static Migration MIGRATION_2_1 = new Migration(VERSION_2, VERSION_1) { @Override diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java index e869762592..cd97958483 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java +++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java @@ -28,8 +28,7 @@ import android.support.annotation.RestrictTo; * * @hide */ -@Entity(tableName = "systemIdInfo", - foreignKeys = { +@Entity(foreignKeys = { @ForeignKey( entity = WorkSpec.class, parentColumns = "id", diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java index bcee05b767..383e51610c 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java +++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java @@ -42,7 +42,7 @@ public interface SystemIdInfoDao { * @return The instance of {@link SystemIdInfo} if exists. */ @Nullable - @Query("SELECT * FROM systemIdInfo WHERE work_spec_id=:workSpecId") + @Query("SELECT * FROM SystemIdInfo WHERE work_spec_id=:workSpecId") SystemIdInfo getSystemIdInfo(@NonNull String workSpecId); /** @@ -50,6 +50,6 @@ public interface SystemIdInfoDao { * * @param workSpecId The {@link WorkSpec} identifier. */ - @Query("DELETE FROM systemIdInfo where work_spec_id=:workSpecId") + @Query("DELETE FROM SystemIdInfo where work_spec_id=:workSpecId") void removeSystemIdInfo(@NonNull String workSpecId); } diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json index 00bc68d43e..400e545684 100644 --- a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json +++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "244d2ac5ecd0a7fb47b3755737585d7b", + "identityHash": "c45e5fcbdf3824dead9778f19e2fd8af", "entities": [ { "tableName": "Dependency", @@ -269,7 +269,7 @@ ] }, { - "tableName": "systemIdInfo", + "tableName": "SystemIdInfo", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`work_spec_id` TEXT NOT NULL, `system_id` INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`), FOREIGN KEY(`work_spec_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", "fields": [ { @@ -357,7 +357,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"244d2ac5ecd0a7fb47b3755737585d7b\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"c45e5fcbdf3824dead9778f19e2fd8af\")" ] } } \ No newline at end of file -- cgit v1.2.3 From 8dfe54b350e7f8ec3f79c0952460f9f82622d801 Mon Sep 17 00:00:00 2001 From: Sumir Kataria Date: Fri, 8 Jun 2018 12:24:55 -0700 Subject: Add implicit tags to all WorkRequests. Implicit tags are the worker class's name, in case you forget to add tags but still want to look up and cancel your workers in the future. Change-Id: I7027c9917b86d8e91b29f64f4bf656f1a69c3757 Fixes: 109572351 Fixes: 109675560 Test: Added and ran tests; updated other tests --- .../androidx/work/WorkDatabaseMigrationTest.java | 41 +++++++++++++++++++ .../androidx/work/impl/WorkManagerImplTest.java | 46 ++++++++++++++-------- .../src/main/java/androidx/work/WorkRequest.java | 1 + .../androidx/work/impl/WorkDatabaseMigrations.java | 5 ++- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java index ca82c50a44..c1311173b1 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java @@ -16,12 +16,15 @@ package androidx.work; +import static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory; import android.arch.persistence.room.testing.MigrationTestHelper; +import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.support.test.InstrumentationRegistry; @@ -30,6 +33,9 @@ import android.support.test.runner.AndroidJUnit4; import androidx.work.impl.WorkDatabase; import androidx.work.impl.WorkDatabaseMigrations; +import androidx.work.impl.model.WorkSpec; +import androidx.work.impl.model.WorkTypeConverters; +import androidx.work.worker.TestWorker; import org.junit.Before; import org.junit.Rule; @@ -86,6 +92,33 @@ public class WorkDatabaseMigrationTest { SupportSQLiteDatabase database = mMigrationTestHelper.createDatabase(TEST_DATABASE, OLD_VERSION); + String workSpecId0 = UUID.randomUUID().toString(); + ContentValues contentValues = new ContentValues(); + contentValues.put("id", workSpecId0); + contentValues.put("state", WorkTypeConverters.StateIds.ENQUEUED); + contentValues.put("worker_class_name", TestWorker.class.getName()); + contentValues.put("input_merger_class_name", OverwritingInputMerger.class.getName()); + contentValues.put("input", Data.toByteArray(Data.EMPTY)); + contentValues.put("output", Data.toByteArray(Data.EMPTY)); + contentValues.put("initial_delay", 0L); + contentValues.put("interval_duration", 0L); + contentValues.put("flex_duration", 0L); + contentValues.put("required_network_type", false); + contentValues.put("requires_charging", false); + contentValues.put("requires_device_idle", false); + contentValues.put("requires_battery_not_low", false); + contentValues.put("requires_storage_not_low", false); + contentValues.put("content_uri_triggers", + WorkTypeConverters.contentUriTriggersToByteArray(new ContentUriTriggers())); + contentValues.put("run_attempt_count", 0); + contentValues.put("backoff_policy", + WorkTypeConverters.backoffPolicyToInt(BackoffPolicy.EXPONENTIAL)); + contentValues.put("backoff_delay_duration", WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS); + contentValues.put("period_start_time", 0L); + contentValues.put("minimum_retention_duration", 0L); + contentValues.put("schedule_requested_at", WorkSpec.SCHEDULE_NOT_REQUESTED_YET); + database.insert("workspec", CONFLICT_FAIL, contentValues); + String workSpecId1 = UUID.randomUUID().toString(); String workSpecId2 = UUID.randomUUID().toString(); @@ -101,6 +134,14 @@ public class WorkDatabaseMigrationTest { VALIDATE_DROPPED_TABLES, WorkDatabaseMigrations.MIGRATION_1_2); + Cursor tagCursor = database.query("SELECT * FROM worktag"); + assertThat(tagCursor.getCount(), is(1)); + tagCursor.moveToFirst(); + assertThat(tagCursor.getString(tagCursor.getColumnIndex("tag")), + is(TestWorker.class.getName())); + assertThat(tagCursor.getString(tagCursor.getColumnIndex("work_spec_id")), is(workSpecId0)); + tagCursor.close(); + Cursor cursor = database.query(CHECK_SYSTEM_ID_INFO); assertThat(cursor.getCount(), is(2)); cursor.moveToFirst(); 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 a96c677ece..3e01164c25 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java @@ -169,6 +169,18 @@ public class WorkManagerImplTest { } } + @Test + @SmallTest + public void testEnqueue_AddsImplicitTags() { + OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); + mWorkManagerImpl.synchronous().enqueueSync(work); + + WorkTagDao workTagDao = mDatabase.workTagDao(); + List tags = workTagDao.getTagsForWorkSpecId(work.getStringId()); + assertThat(tags, is(notNullValue())); + assertThat(tags, contains(TestWorker.class.getName())); + } + @Test @SmallTest public void testEnqueue_insertMultipleWork() { @@ -841,12 +853,12 @@ public class WorkManagerImplTest { work0.getId(), ENQUEUED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(TestWorker.class.getName())); WorkStatus workStatus1 = new WorkStatus( work1.getId(), ENQUEUED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(TestWorker.class.getName())); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1)); WorkSpecDao workSpecDao = mDatabase.workSpecDao(); @@ -860,7 +872,7 @@ public class WorkManagerImplTest { work0.getId(), RUNNING, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(TestWorker.class.getName())); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1)); clearInvocations(mockObserver); @@ -874,7 +886,7 @@ public class WorkManagerImplTest { work1.getId(), RUNNING, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(TestWorker.class.getName())); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1)); liveData.removeObservers(testLifecycleOwner); @@ -907,17 +919,17 @@ public class WorkManagerImplTest { work0.getId(), RUNNING, Data.EMPTY, - Arrays.asList(firstTag, secondTag)); + Arrays.asList(TestWorker.class.getName(), firstTag, secondTag)); WorkStatus workStatus1 = new WorkStatus( work1.getId(), BLOCKED, Data.EMPTY, - Collections.singletonList(firstTag)); + Arrays.asList(TestWorker.class.getName(), firstTag)); WorkStatus workStatus2 = new WorkStatus( work2.getId(), SUCCEEDED, Data.EMPTY, - Collections.singletonList(secondTag)); + Arrays.asList(TestWorker.class.getName(), secondTag)); List workStatuses = mWorkManagerImpl.getStatusesByTagSync(firstTag); assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1)); @@ -969,12 +981,12 @@ public class WorkManagerImplTest { work0.getId(), RUNNING, Data.EMPTY, - Arrays.asList(firstTag, secondTag)); + Arrays.asList(TestWorker.class.getName(), firstTag, secondTag)); WorkStatus workStatus1 = new WorkStatus( work1.getId(), BLOCKED, Data.EMPTY, - Collections.singletonList(firstTag)); + Arrays.asList(TestWorker.class.getName(), firstTag)); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1)); workSpecDao.setState(ENQUEUED, work0.getStringId()); @@ -987,7 +999,7 @@ public class WorkManagerImplTest { work0.getId(), ENQUEUED, Data.EMPTY, - Arrays.asList(firstTag, secondTag)); + Arrays.asList(TestWorker.class.getName(), firstTag, secondTag)); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1)); liveData.removeObservers(testLifecycleOwner); @@ -1015,17 +1027,17 @@ public class WorkManagerImplTest { work0.getId(), RUNNING, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus1 = new WorkStatus( work1.getId(), BLOCKED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus2 = new WorkStatus( work2.getId(), BLOCKED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); List workStatuses = mWorkManagerImpl.getStatusesForUniqueWorkSync(uniqueName); assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1, workStatus2)); @@ -1069,17 +1081,17 @@ public class WorkManagerImplTest { work0.getId(), RUNNING, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus1 = new WorkStatus( work1.getId(), BLOCKED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus2 = new WorkStatus( work2.getId(), BLOCKED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2)); workSpecDao.setState(ENQUEUED, work0.getStringId()); @@ -1092,7 +1104,7 @@ public class WorkManagerImplTest { work0.getId(), ENQUEUED, Data.EMPTY, - Collections.emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2)); liveData.removeObservers(testLifecycleOwner); diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java index c1eae64b8e..cfb72b8f75 100644 --- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java +++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java @@ -121,6 +121,7 @@ public abstract class WorkRequest { public Builder(@NonNull Class workerClass) { mId = UUID.randomUUID(); mWorkSpec = new WorkSpec(mId.toString(), workerClass.getName()); + addTag(workerClass.getName()); } /** diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java index f5e67bf2ca..82a26ac58c 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java +++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java @@ -59,10 +59,10 @@ public class WorkDatabaseMigrations { private static final String REMOVE_ALARM_INFO = "DROP TABLE IF EXISTS alarmInfo"; private static final String REMOVE_SYSTEM_ID_INFO = "DROP TABLE IF EXISTS SystemIdInfo"; - /** * Removes the {@code alarmInfo} table and substitutes it for a more general * {@code SystemIdInfo} table. + * Adds implicit work tags for all work (a tag with the worker class name). */ public static Migration MIGRATION_1_2 = new Migration(VERSION_1, VERSION_2) { @Override @@ -70,6 +70,8 @@ public class WorkDatabaseMigrations { database.execSQL(CREATE_SYSTEM_ID_INFO); database.execSQL(MIGRATE_ALARM_INFO_TO_SYSTEM_ID_INFO); database.execSQL(REMOVE_ALARM_INFO); + database.execSQL("INSERT INTO worktag(tag, work_spec_id) " + + "SELECT worker_class_name AS tag, id AS work_spec_id FROM workspec"); } }; @@ -83,6 +85,7 @@ public class WorkDatabaseMigrations { database.execSQL(CREATE_ALARM_INFO); database.execSQL(MIGRATE_SYSTEM_ID_INFO_TO_ALARM_INFO); database.execSQL(REMOVE_SYSTEM_ID_INFO); + // Don't remove implicit tags; they may have been added by the developer. } }; } -- cgit v1.2.3 From e8abc12d5134bb86baa7e563800c7de4fd0311d4 Mon Sep 17 00:00:00 2001 From: Sergey Vasilinets Date: Wed, 6 Jun 2018 23:23:26 -0700 Subject: Correct setup for public docs without "tipOfTree" rules Test: ./gradlew publicDocs -PofflineDocs=true Change-Id: Ia76ec38b8264e7ca701ebc64fc50e720a3810791 Merged-In: Ia76ec38b8264e7ca701ebc64fc50e720a3810791 --- .../main/kotlin/androidx/build/PublishDocsRules.kt | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt index a40d6d938b..62e8c5b8bc 100644 --- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt +++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt @@ -24,7 +24,7 @@ import androidx.build.Strategy.Prebuilts import androidx.build.Strategy.Ignore val RELEASE_RULE = docsRules("public") { - val defaultVersion = "1.0.0-alpha1" + val defaultVersion = "1.0.0-alpha3" prebuilts(LibraryGroups.ANNOTATION, defaultVersion) prebuilts(LibraryGroups.APPCOMPAT, defaultVersion) prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, defaultVersion) @@ -33,9 +33,7 @@ val RELEASE_RULE = docsRules("public") { .addStubs("car/car-stubs/android.car.jar") prebuilts(LibraryGroups.CARDVIEW, defaultVersion) prebuilts(LibraryGroups.COLLECTION, defaultVersion) - // misses prebuilts, because it was released under different name in alpha1 - tipOfTree(LibraryGroups.CONTENTPAGER) - + prebuilts(LibraryGroups.CONTENTPAGER, defaultVersion) prebuilts(LibraryGroups.COORDINATORLAYOUT, defaultVersion) prebuilts(LibraryGroups.CORE, defaultVersion) prebuilts(LibraryGroups.CURSORADAPTER, defaultVersion) @@ -57,7 +55,6 @@ val RELEASE_RULE = docsRules("public") { prebuilts(LibraryGroups.MEDIAROUTER, defaultVersion) prebuilts(LibraryGroups.PALETTE, defaultVersion) prebuilts(LibraryGroups.PERCENTLAYOUT, defaultVersion) - ignore(LibraryGroups.PREFERENCE, "preference-ktx") prebuilts(LibraryGroups.PREFERENCE, defaultVersion) prebuilts(LibraryGroups.PRINT, defaultVersion) prebuilts(LibraryGroups.RECOMMENDATION, defaultVersion) @@ -76,17 +73,11 @@ val RELEASE_RULE = docsRules("public") { val flatfootVersion = "2.0.0-alpha1" prebuilts(LibraryGroups.ROOM, flatfootVersion) prebuilts(LibraryGroups.PERSISTENCE, flatfootVersion) - // lifecycle-viewmodel-ktx / lifecycle-process / lifecycle-service miss their prebuilts - tipOfTree(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-ktx") - tipOfTree(LibraryGroups.LIFECYCLE, "lifecycle-process") - tipOfTree(LibraryGroups.LIFECYCLE, "lifecycle-service") prebuilts(LibraryGroups.LIFECYCLE, flatfootVersion) prebuilts(LibraryGroups.ARCH_CORE, flatfootVersion) - prebuilts(LibraryGroups.PAGING, "paging-rxjava2", "1.0.0-alpha1") - prebuilts(LibraryGroups.PAGING, "2.0.0-alpha1") - // navigation & workmanager don't have prebuilts currently - tipOfTree(LibraryGroups.NAVIGATION) - tipOfTree(LibraryGroups.WORKMANAGER) + prebuilts(LibraryGroups.PAGING, flatfootVersion) + prebuilts(LibraryGroups.NAVIGATION, "1.0.0-alpha01") + prebuilts(LibraryGroups.WORKMANAGER, "1.0.0-alpha02") default(Ignore) } -- cgit v1.2.3