package com.doist.jobschedulercompat.scheduler.jobscheduler; import com.doist.jobschedulercompat.JobInfo; import com.doist.jobschedulercompat.PersistableBundle; import com.doist.jobschedulercompat.util.BundleUtils; import com.doist.jobschedulercompat.util.JobCreator; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import android.annotation.TargetApi; import android.app.Application; import android.app.job.JobScheduler; import android.content.ClipData; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.NetworkRequest; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; import java.util.List; import java.util.concurrent.TimeUnit; import androidx.test.core.app.ApplicationProvider; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @TargetApi(Build.VERSION_CODES.N) @RunWith(RobolectricTestRunner.class) @Config(sdk = {Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.N, Build.VERSION_CODES.O, Build.VERSION_CODES.P}) public class JobSchedulerSchedulerTest { private Application application; private JobSchedulerSchedulerV21 scheduler; private JobScheduler jobScheduler; @Before public void setup() { application = ApplicationProvider.getApplicationContext(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { scheduler = new JobSchedulerSchedulerV28(application); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { scheduler = new JobSchedulerSchedulerV26(application); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { scheduler = new JobSchedulerSchedulerV24(application); } else { scheduler = new JobSchedulerSchedulerV21(application); } jobScheduler = (JobScheduler) application.getSystemService(Context.JOB_SCHEDULER_SERVICE); } @Test public void testScheduleJob() { JobInfo job = createJob(0).build(); scheduler.schedule(job); android.app.job.JobInfo nativeJob = getPendingJob(job.getId()); assertNotNull(nativeJob); assertNativeJobInfoMatchesJobInfo(nativeJob, job); job = createJob(1).build(); scheduler.schedule(job); nativeJob = getPendingJob(job.getId()); assertNotNull(nativeJob); assertNativeJobInfoMatchesJobInfo(nativeJob, job); job = createJob(2).build(); scheduler.schedule(job); nativeJob = getPendingJob(job.getId()); assertNotNull(nativeJob); assertNativeJobInfoMatchesJobInfo(nativeJob, job); } @Test @Config(sdk = Build.VERSION_CODES.O) public void testScheduleJobWithOreoOption() { JobInfo.Builder builder = createJob(2); builder.setClipData(new ClipData("TestClipData", new String[]{"application/*"}, new ClipData.Item( Uri.parse("content://com.doist.jobschedulercompat"))), Intent.FLAG_GRANT_READ_URI_PERMISSION); JobInfo job = builder.build(); scheduler.schedule(job); android.app.job.JobInfo nativeJob = getPendingJob(job.getId()); assertNotNull(nativeJob); assertNativeJobInfoMatchesJobInfo(nativeJob, job); } @Test @Config(sdk = Build.VERSION_CODES.P) public void testScheduleJobWithPieOption() { JobInfo.Builder builder = createJob(1); builder.setRequiredNetwork( new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_VALIDATED) .removeCapability(NET_CAPABILITY_NOT_VPN) .addCapability(NET_CAPABILITY_NOT_ROAMING) .build()); builder.setEstimatedNetworkBytes(1024, 128); builder.setImportantWhileForeground(true); builder.setPrefetch(true); JobInfo job = builder.build(); scheduler.schedule(job); android.app.job.JobInfo nativeJob = getPendingJob(job.getId()); assertNotNull(nativeJob); assertNativeJobInfoMatchesJobInfo(nativeJob, job); } @Test public void testCancelJob() { JobInfo job = createJob(0).build(); scheduler.schedule(job); scheduler.cancel(job.getId()); assertEquals(0, jobScheduler.getAllPendingJobs().size()); } @Test public void testCancelAllBroadcasts() { scheduler.schedule(createJob(0).build()); scheduler.schedule(createJob(1).build()); scheduler.schedule(createJob(2).build()); scheduler.cancelAll(); assertEquals(0, jobScheduler.getAllPendingJobs().size()); } private void assertNativeJobInfoMatchesJobInfo(android.app.job.JobInfo nativeJob, JobInfo job) { assertEquals(nativeJob.getId(), job.getId()); assertEquals(new PersistableBundle(nativeJob.getExtras()).toMap(10), job.getExtras().toMap(10)); assertEquals(nativeJob.getService(), new ComponentName(application, JobSchedulerJobService.class)); assertEquals(nativeJob.isRequireCharging(), job.isRequireCharging()); assertEquals(nativeJob.isRequireDeviceIdle(), job.isRequireDeviceIdle()); assertEquals(nativeJob.getNetworkType(), job.getNetworkType()); assertEquals(nativeJob.getMinLatencyMillis(), job.getMinLatencyMillis()); assertEquals(nativeJob.getMaxExecutionDelayMillis(), job.getMaxExecutionDelayMillis()); assertEquals(nativeJob.isPeriodic(), job.isPeriodic()); assertEquals(nativeJob.isPersisted(), job.isPersisted()); assertEquals(nativeJob.getInitialBackoffMillis(), job.getInitialBackoffMillis()); assertEquals(nativeJob.getBackoffPolicy(), job.getBackoffPolicy()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (job.getFlexMillis() == 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { assertEquals(nativeJob.getFlexMillis(), JobInfo.getMinFlexMillis()); } else { assertEquals(nativeJob.getFlexMillis(), job.getFlexMillis()); } assertArrayEquals(getUris(nativeJob.getTriggerContentUris()), getUris(job.getTriggerContentUris())); assertEquals(nativeJob.getTriggerContentUpdateDelay(), job.getTriggerContentUpdateDelay()); assertEquals(nativeJob.getTriggerContentMaxDelay(), job.getTriggerContentMaxDelay()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { assertEquals( BundleUtils.toMap(nativeJob.getTransientExtras(), 10), BundleUtils.toMap(job.getTransientExtras(), 10)); assertEquals(nativeJob.isRequireBatteryNotLow(), job.isRequireBatteryNotLow()); assertEquals(nativeJob.isRequireStorageNotLow(), job.isRequireBatteryNotLow()); assertEquals(nativeJob.getClipData(), job.getClipData()); assertEquals(nativeJob.getClipGrantFlags(), job.getClipGrantFlags()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { assertEquals(nativeJob.getRequiredNetwork(), job.getRequiredNetwork()); assertEquals(nativeJob.getEstimatedNetworkDownloadBytes(), job.getEstimatedNetworkDownloadBytes()); assertEquals(nativeJob.getEstimatedNetworkUploadBytes(), job.getEstimatedNetworkUploadBytes()); assertEquals(nativeJob.isImportantWhileForeground(), job.isImportantWhileForeground()); assertEquals(nativeJob.isPrefetch(), job.isPrefetch()); } } private JobInfo.Builder createJob(int type) { switch (type) { case 0: return JobCreator.create(application) .setMinimumLatency(TimeUnit.HOURS.toMillis(2)) .setOverrideDeadline(TimeUnit.DAYS.toMillis(1)) .setRequiresCharging(true) .setPersisted(true); case 1: PersistableBundle extras = new PersistableBundle(); extras.putString("test", "test"); Bundle transientExtras = new Bundle(); transientExtras.putString("test2", "test2"); return JobCreator.create(application) .setRequiresCharging(true) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setExtras(extras) .setTransientExtras(transientExtras) .addTriggerContentUri(new JobInfo.TriggerContentUri( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) .setTriggerContentUpdateDelay(TimeUnit.SECONDS.toMillis(1)) .setTriggerContentMaxDelay(TimeUnit.SECONDS.toMillis(30)); case 2: default: return JobCreator.create(application) .setPeriodic(TimeUnit.MINUTES.toMillis(30), TimeUnit.MINUTES.toMillis(5)) .setRequiresDeviceIdle(true) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); } } /* API 23 and below don't support getPendingJob, filter manually if necessary. */ private android.app.job.JobInfo getPendingJob(int jobId) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return jobScheduler.getPendingJob(jobId); } else { List<android.app.job.JobInfo> jobs = jobScheduler.getAllPendingJobs(); for (android.app.job.JobInfo job : jobs) { if (job.getId() == jobId) { return job; } } } return null; } private Uri[] getUris(JobInfo.TriggerContentUri[] triggerContentUris) { if (triggerContentUris == null) { return null; } else { Uri[] uris = new Uri[triggerContentUris.length]; for (int i = 0; i < uris.length; i++) { uris[i] = triggerContentUris[i].getUri(); } return uris; } } private Uri[] getUris(android.app.job.JobInfo.TriggerContentUri[] triggerContentUris) { if (triggerContentUris == null) { return null; } else { Uri[] uris = new Uri[triggerContentUris.length]; for (int i = 0; i < uris.length; i++) { uris[i] = triggerContentUris[i].getUri(); } return uris; } } }