diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-06-10 19:02:31 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-06-10 19:02:31 +0000 |
commit | ae78fb8a99ef9b253681ce60ac5f3be0b9c77c0f (patch) | |
tree | cf8c59fcb5503ff0bb68838949efeaa2c145b766 | |
parent | e5abe2d3eaf16febb29c11708b431f90e2e81289 (diff) | |
parent | f0a0a39e63da4265934e8bead2ddc619465371cd (diff) | |
download | support-android-cts-9.0_r19.tar.gz |
Snap for 4832339 from f0a0a39e63da4265934e8bead2ddc619465371cd to pi-releaseandroid-wear-9.0.0_r9android-wear-9.0.0_r8android-wear-9.0.0_r7android-wear-9.0.0_r6android-wear-9.0.0_r5android-wear-9.0.0_r4android-wear-9.0.0_r34android-wear-9.0.0_r33android-wear-9.0.0_r32android-wear-9.0.0_r31android-wear-9.0.0_r30android-wear-9.0.0_r3android-wear-9.0.0_r29android-wear-9.0.0_r28android-wear-9.0.0_r27android-wear-9.0.0_r26android-wear-9.0.0_r25android-wear-9.0.0_r24android-wear-9.0.0_r23android-wear-9.0.0_r22android-wear-9.0.0_r21android-wear-9.0.0_r20android-wear-9.0.0_r2android-wear-9.0.0_r19android-wear-9.0.0_r18android-wear-9.0.0_r17android-wear-9.0.0_r16android-wear-9.0.0_r15android-wear-9.0.0_r14android-wear-9.0.0_r13android-wear-9.0.0_r12android-wear-9.0.0_r11android-wear-9.0.0_r10android-wear-9.0.0_r1android-vts-9.0_r9android-vts-9.0_r8android-vts-9.0_r7android-vts-9.0_r6android-vts-9.0_r5android-vts-9.0_r4android-vts-9.0_r19android-vts-9.0_r18android-vts-9.0_r17android-vts-9.0_r16android-vts-9.0_r15android-vts-9.0_r14android-vts-9.0_r13android-vts-9.0_r12android-vts-9.0_r11android-vts-9.0_r10android-security-9.0.0_r76android-security-9.0.0_r75android-security-9.0.0_r74android-security-9.0.0_r73android-security-9.0.0_r72android-security-9.0.0_r71android-security-9.0.0_r70android-security-9.0.0_r69android-security-9.0.0_r68android-security-9.0.0_r67android-security-9.0.0_r66android-security-9.0.0_r65android-security-9.0.0_r64android-security-9.0.0_r63android-security-9.0.0_r62android-cts-9.0_r9android-cts-9.0_r8android-cts-9.0_r7android-cts-9.0_r6android-cts-9.0_r5android-cts-9.0_r4android-cts-9.0_r3android-cts-9.0_r20android-cts-9.0_r2android-cts-9.0_r19android-cts-9.0_r18android-cts-9.0_r17android-cts-9.0_r16android-cts-9.0_r15android-cts-9.0_r14android-cts-9.0_r13android-cts-9.0_r12android-cts-9.0_r11android-cts-9.0_r10android-cts-9.0_r1android-9.0.0_r9android-9.0.0_r8android-9.0.0_r7android-9.0.0_r61android-9.0.0_r60android-9.0.0_r6android-9.0.0_r59android-9.0.0_r58android-9.0.0_r57android-9.0.0_r56android-9.0.0_r55android-9.0.0_r54android-9.0.0_r53android-9.0.0_r52android-9.0.0_r51android-9.0.0_r50android-9.0.0_r5android-9.0.0_r49android-9.0.0_r48android-9.0.0_r3android-9.0.0_r2android-9.0.0_r18android-9.0.0_r17android-9.0.0_r10android-9.0.0_r1security-pi-releasepie-vts-releasepie-security-releasepie-s2-releasepie-release-2pie-releasepie-r2-s2-releasepie-r2-s1-releasepie-r2-releasepie-cts-release
Change-Id: If085d5207d41cfdc6d1a3bbcf62106ae510dad29
29 files changed, 547 insertions, 127 deletions
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt index a40d6d938b0..62e8c5b8bc5 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) } 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 f17fde4dbb0..5e549d25093 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/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java index 1c1893f2cf4..c1311173b1c 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; @@ -53,12 +59,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"; @@ -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(); @@ -127,7 +168,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/androidTest/java/androidx/work/WorkSpecDaoTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java index da5a8e019e9..6dd23cbfa81 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<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List<WorkSpec> 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<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List<WorkSpec> 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<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List<WorkSpec> 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<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List<WorkSpec> 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 cfaa1f38571..1e178b1d471 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 778320853f7..f4a3a7b09be 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<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class); verify(scheduler, times(1)).schedule(captor.capture()); List<WorkSpec> workSpecs = captor.getAllValues(); @@ -521,7 +524,10 @@ public class WorkContinuationImplTest extends WorkManagerTest { WorkDatabase workDatabase = continuation.getWorkManagerImpl().getWorkDatabase(); List<WorkSpec> eligibleWorkSpecs = - workDatabase.workSpecDao().getEligibleWorkForScheduling(); + workDatabase + .workSpecDao() + .getEligibleWorkForScheduling( + configuration.getMaxSchedulerLimit()); Set<String> 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 51e98e5728d..72ebe944b00 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<OneTimeWorkRequest> workRequests = new ArrayList<>(NUM_WORKERS); + final Set<UUID> 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<WorkSpec> 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<List<WorkStatus>>() { + @Override + public void onChanged(@Nullable List<WorkStatus> 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/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java index 71686008903..3e01164c251 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java @@ -171,6 +171,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<String> tags = workTagDao.getTagsForWorkSpecId(work.getStringId()); + assertThat(tags, is(notNullValue())); + assertThat(tags, contains(TestWorker.class.getName())); + } + + @Test + @SmallTest public void testEnqueue_insertMultipleWork() { OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build(); OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class).build(); @@ -841,12 +853,12 @@ public class WorkManagerImplTest { work0.getId(), ENQUEUED, Data.EMPTY, - Collections.<String>emptyList()); + Collections.singletonList(TestWorker.class.getName())); WorkStatus workStatus1 = new WorkStatus( work1.getId(), ENQUEUED, Data.EMPTY, - Collections.<String>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.<String>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.<String>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<WorkStatus> 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.<String>emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus1 = new WorkStatus( work1.getId(), BLOCKED, Data.EMPTY, - Collections.<String>emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus2 = new WorkStatus( work2.getId(), BLOCKED, Data.EMPTY, - Collections.<String>emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); List<WorkStatus> workStatuses = mWorkManagerImpl.getStatusesForUniqueWorkSync(uniqueName); assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1, workStatus2)); @@ -1069,17 +1081,17 @@ public class WorkManagerImplTest { work0.getId(), RUNNING, Data.EMPTY, - Collections.<String>emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus1 = new WorkStatus( work1.getId(), BLOCKED, Data.EMPTY, - Collections.<String>emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); WorkStatus workStatus2 = new WorkStatus( work2.getId(), BLOCKED, Data.EMPTY, - Collections.<String>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.<String>emptyList()); + Collections.singletonList(InfiniteTestWorker.class.getName())); assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2)); liveData.removeObservers(testLifecycleOwner); @@ -1333,6 +1345,38 @@ public class WorkManagerImplTest { @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() { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); insertWorkSpecAndTags(work); 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 4a50d10965b..739fcf7b626 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 5e36dfd0c77..cfe2cef7db5 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 a86df660418..6f136bf0b6e 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 00000000000..11763641ed8 --- /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<WorkSpec> 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 4fe1470424f..77f5ffa9b44 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; /** @@ -122,6 +151,33 @@ public final class Configuration { } /** + * 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. * * @param executor An {@link Executor} for processing work diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java index f01e363152a..9f633d8e3e0 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 @@ -129,6 +130,20 @@ public interface SynchronousWorkManager { 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. + * <p> + * <b>Use this method with caution</b>; 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 83891dccef4..5960e50fcab 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 @@ -288,6 +289,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. + * <p> + * <b>Use this method with caution</b>; 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 * must be updated or deleted in case someone cancels their work without their prior knowledge. diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java index c1eae64b8e7..cfb72b8f759 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<? extends Worker> 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/Processor.java b/work/workmanager/src/main/java/androidx/work/impl/Processor.java index 4092686fcee..86b7edf5f39 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<String, WorkerWrapper> mEnqueuedWorkMap; private List<Scheduler> mSchedulers; private Executor mExecutor; @@ -50,10 +52,12 @@ public class Processor implements ExecutionListener { public Processor( Context appContext, + Configuration configuration, WorkDatabase workDatabase, List<Scheduler> 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 c6e5b816083..4944549d6fa 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<Scheduler> schedulers) { WorkSpecDao workSpecDao = workDatabase.workSpecDao(); - List<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(); + List<WorkSpec> eligibleWorkSpecs = + workSpecDao.getEligibleWorkForScheduling( + configuration.getMaxSchedulerLimit()); scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs); } 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 0c3e3db484c..82a26ac58cd 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. + * 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,12 +70,14 @@ 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"); } }; /** * 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 @@ -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. } }; } 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 119325c412a..0dcdda349c6 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; @@ -166,6 +167,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag mTaskExecutor = WorkManagerTaskExecutor.getInstance(); mProcessor = new Processor( context, + mConfiguration, mWorkDatabase, getSchedulers(), configuration.getExecutor()); @@ -349,6 +351,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(); @@ -360,6 +363,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @Override + @WorkerThread public void cancelAllWorkSync() { assertBackgroundThread("Cannot cancelAllWorkSync on main thread!"); CancelWorkRunnable.forAll(this).run(); @@ -376,6 +380,18 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @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<WorkStatus> getStatusById(@NonNull UUID id) { WorkSpecDao dao = mWorkDatabase.workSpecDao(); LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData = @@ -500,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 b6d4fc16cf7..0b8c44c83e7 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 94cc2db75a7..6dbd752bea7 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<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase() .workSpecDao() - .getEligibleWorkForScheduling(); + .getEligibleWorkForScheduling(schedulerLimit); // Filter candidates that are marked as SCHEDULE_NOT_REQUESTED_AT List<WorkSpec> eligibleWorkSpecs = new ArrayList<>(candidates.size()); 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 e869762592d..cd97958483e 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 bcee05b767d..383e51610c3 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/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java index 7d9775d380f..e15124fa8f5 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,10 +284,23 @@ 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<WorkSpec> getEligibleWorkForScheduling(); + List<WorkSpec> getEligibleWorkForScheduling(int schedulerLimit); + + /** + * 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/CancelWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java index e117c95bc90..842fdc24166 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 004b895b894..00d7b8f920f 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) { 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 00000000000..bf93f4602a6 --- /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(); + } +} 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 00bc68d43eb..400e545684d 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 |