1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.work.impl.background.systemjob;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
20 
21 import static androidx.work.NetworkType.CONNECTED;
22 import static androidx.work.NetworkType.METERED;
23 import static androidx.work.NetworkType.NOT_REQUIRED;
24 import static androidx.work.NetworkType.NOT_ROAMING;
25 import static androidx.work.NetworkType.UNMETERED;
26 
27 import static org.hamcrest.CoreMatchers.is;
28 import static org.hamcrest.MatcherAssert.assertThat;
29 import static org.hamcrest.Matchers.arrayContaining;
30 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertTrue;
33 
34 import android.app.job.JobInfo;
35 import android.net.NetworkCapabilities;
36 import android.net.NetworkRequest;
37 import android.net.Uri;
38 import android.os.Build;
39 
40 import androidx.test.core.app.ApplicationProvider;
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 import androidx.test.filters.SdkSuppress;
43 import androidx.test.filters.SmallTest;
44 import androidx.work.BackoffPolicy;
45 import androidx.work.Constraints;
46 import androidx.work.NetworkType;
47 import androidx.work.OneTimeWorkRequest;
48 import androidx.work.PeriodicWorkRequest;
49 import androidx.work.SystemClock;
50 import androidx.work.WorkManagerTest;
51 import androidx.work.impl.WorkManagerImpl;
52 import androidx.work.impl.model.WorkSpec;
53 import androidx.work.worker.TestWorker;
54 
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.util.concurrent.TimeUnit;
60 
61 @RunWith(AndroidJUnit4.class)
62 @SdkSuppress(minSdkVersion = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
63 public class SystemJobInfoConverterTest extends WorkManagerTest {
64 
65     private static final long TEST_INTERVAL_DURATION =
66             PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS + 1232L;
67     private static final long TEST_FLEX_DURATION =
68             PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS + 112L;
69     private static final int JOB_ID = 101;
70 
71     private SystemJobInfoConverter mConverter;
72 
73     @Before
setUp()74     public void setUp() {
75         mConverter = new SystemJobInfoConverter(
76                 ApplicationProvider.getApplicationContext(), new SystemClock(), true);
77     }
78 
79     @Test
80     @SmallTest
testConvert_ids()81     public void testConvert_ids() {
82         final String expectedWorkSpecId = "026e3422-9cd1-11e7-abc4-cec278b6b50a";
83         WorkSpec workSpec = new WorkSpec(expectedWorkSpecId, TestWorker.class.getName());
84         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
85         String actualWorkSpecId = jobInfo.getExtras().getString(
86                 SystemJobInfoConverter.EXTRA_WORK_SPEC_ID);
87         assertThat(actualWorkSpecId, is(expectedWorkSpecId));
88         assertThat(jobInfo.getId(), is(JOB_ID));
89     }
90 
91     @Test
92     @SmallTest
testConvert_setPersistedByDefault()93     public void testConvert_setPersistedByDefault() {
94         JobInfo jobInfo = mConverter.convert(
95                 new WorkSpec("id", TestWorker.class.getName()), JOB_ID);
96         assertThat(jobInfo.isPersisted(), is(false));
97     }
98 
99     /**
100      * Due to b/6771687, calling {@link JobInfo.Builder#build} with no constraints throws an
101      * {@link IllegalArgumentException}. This is testing that {@link SystemJobInfoConverter#convert}
102      * sets some dummy constraint to toggle some internal boolean flags in {@link JobInfo.Builder}
103      * to allow {@link OneTimeWorkRequest} with no constraints to be converted without affecting its
104      * runtime, e.g. calling builder.setMinLatencyMillis(0L).
105      */
106     @Test
107     @SmallTest
testConvert_noConstraints_doesNotThrowException()108     public void testConvert_noConstraints_doesNotThrowException() {
109         mConverter.convert(new WorkSpec("id", TestWorker.class.getName()), JOB_ID);
110     }
111 
112     @Test
113     @SmallTest
testConvert_retryPolicy()114     public void testConvert_retryPolicy() {
115         long expectedBackoffDelayDuration = 50000;
116         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
117         workSpec.setBackoffDelayDuration(expectedBackoffDelayDuration);
118         workSpec.backoffPolicy = BackoffPolicy.LINEAR;
119         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
120         assertThat(jobInfo.getInitialBackoffMillis(), is(expectedBackoffDelayDuration));
121         assertThat(jobInfo.getBackoffPolicy(), is(JobInfo.BACKOFF_POLICY_LINEAR));
122     }
123 
124     @Test
125     @SmallTest
testConvert_initialDelay()126     public void testConvert_initialDelay() {
127         final long expectedInitialDelay = 12123L;
128         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
129         workSpec.lastEnqueueTime = System.currentTimeMillis();
130         workSpec.initialDelay = expectedInitialDelay;
131         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
132         assertCloseValues(jobInfo.getMinLatencyMillis(), expectedInitialDelay);
133     }
134 
135     @Test
136     @SmallTest
testConvert_periodicWithNoFlex()137     public void testConvert_periodicWithNoFlex() {
138         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
139         workSpec.setPeriodic(TEST_INTERVAL_DURATION);
140         workSpec.lastEnqueueTime = System.currentTimeMillis();
141         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
142         assertThat(jobInfo.getMinLatencyMillis(), is(0L));
143     }
144 
145     @Test
146     @SmallTest
testConvert_periodicWithFlex()147     public void testConvert_periodicWithFlex() {
148         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
149         workSpec.setPeriodic(TEST_INTERVAL_DURATION, TEST_FLEX_DURATION);
150         workSpec.lastEnqueueTime = System.currentTimeMillis();
151         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
152         assertCloseValues(jobInfo.getMinLatencyMillis(),
153                 TEST_INTERVAL_DURATION - TEST_FLEX_DURATION);
154     }
155 
156     @Test
157     @SmallTest
testConvert_requireCharging()158     public void testConvert_requireCharging() {
159         final boolean expectedRequireCharging = true;
160         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
161                 .setRequiresCharging(expectedRequireCharging).build());
162         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
163         assertThat(jobInfo.isRequireCharging(), is(expectedRequireCharging));
164     }
165 
166     @Test
167     @SmallTest
168     @SdkSuppress(minSdkVersion = 24)
testConvert_requireContentUriTrigger()169     public void testConvert_requireContentUriTrigger() {
170         final Uri expectedUri = Uri.parse("TEST_URI");
171         final JobInfo.TriggerContentUri expectedTriggerContentUri =
172                 new JobInfo.TriggerContentUri(
173                         expectedUri, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
174         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
175                 .addContentUriTrigger(expectedUri, true)
176                 .setTriggerContentUpdateDelay(5, TimeUnit.SECONDS)
177                 .setTriggerContentMaxDelay(10, TimeUnit.SECONDS)
178                 .build());
179         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
180 
181         JobInfo.TriggerContentUri[] triggerContentUris = jobInfo.getTriggerContentUris();
182         assertThat(triggerContentUris, is(arrayContaining(expectedTriggerContentUri)));
183         assertThat(jobInfo.getTriggerContentUpdateDelay(), is(TimeUnit.SECONDS.toMillis(5)));
184         assertThat(jobInfo.getTriggerContentMaxDelay(), is(TimeUnit.SECONDS.toMillis(10)));
185     }
186 
187     @Test
188     @SmallTest
testConvert_requireDeviceIdle()189     public void testConvert_requireDeviceIdle() {
190         final boolean expectedRequireDeviceIdle = true;
191         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
192                 .setRequiresDeviceIdle(expectedRequireDeviceIdle).build());
193         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
194         assertThat(jobInfo.isRequireDeviceIdle(), is(expectedRequireDeviceIdle));
195     }
196 
197     @Test
198     @SmallTest
199     @SdkSuppress(minSdkVersion = 26)
testConvert_requireBatteryNotLow()200     public void testConvert_requireBatteryNotLow() {
201         final boolean expectedRequireBatteryNotLow = true;
202         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
203                 .setRequiresBatteryNotLow(expectedRequireBatteryNotLow).build());
204         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
205         assertThat(jobInfo.isRequireBatteryNotLow(), is(expectedRequireBatteryNotLow));
206     }
207 
208     @Test
209     @SmallTest
210     @SdkSuppress(minSdkVersion = 26)
testConvert_requireStorageNotLow()211     public void testConvert_requireStorageNotLow() {
212         final boolean expectedRequireStorageNotLow = true;
213         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
214                 .setRequiresStorageNotLow(expectedRequireStorageNotLow).build());
215         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
216         assertThat(jobInfo.isRequireStorageNotLow(), is(expectedRequireStorageNotLow));
217     }
218 
219     @Test
220     @SmallTest
testConvert_networkTypeNotRoamingRequiresApi24()221     public void testConvert_networkTypeNotRoamingRequiresApi24() {
222         convertWithRequiredNetworkType(NOT_ROAMING, JobInfo.NETWORK_TYPE_NOT_ROAMING, 24);
223     }
224 
225     @Test
226     @SmallTest
testConvert_networkTypeMeteredRequiresApi26()227     public void testConvert_networkTypeMeteredRequiresApi26() {
228         convertWithRequiredNetworkType(METERED, JobInfo.NETWORK_TYPE_METERED, 26);
229     }
230 
231     @Test
232     @SmallTest
233     @SdkSuppress(minSdkVersion = 29, maxSdkVersion = 34)
testConvert_setImportantWhileForeground()234     public void testConvert_setImportantWhileForeground() {
235         // Switch maxSdkVersion to 35 after B gets an official SDK version.
236         // setImportantWhileInForeground() turns into a no-op starting API 36.
237         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder().build());
238         workSpec.lastEnqueueTime = System.currentTimeMillis();
239         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
240         assertThat(jobInfo.isImportantWhileForeground(), is(true));
241     }
242 
243     @Test
244     @SmallTest
245     @SdkSuppress(minSdkVersion = 29)
testConvert_setImportantWhileForeground_respectFlag()246     public void testConvert_setImportantWhileForeground_respectFlag() {
247         mConverter = new SystemJobInfoConverter(
248                 ApplicationProvider.getApplicationContext(), new SystemClock(), false);
249         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder().build());
250         workSpec.lastEnqueueTime = System.currentTimeMillis();
251         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
252         assertThat(jobInfo.isImportantWhileForeground(), is(false));
253     }
254 
255     @Test
256     @SmallTest
257     @SdkSuppress(minSdkVersion = 29)
testConvert_setImportantWhileForeground_withTimingConstraints()258     public void testConvert_setImportantWhileForeground_withTimingConstraints() {
259         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
260         workSpec.setPeriodic(TEST_INTERVAL_DURATION, TEST_FLEX_DURATION);
261         workSpec.lastEnqueueTime = System.currentTimeMillis();
262         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
263         assertThat(jobInfo.isImportantWhileForeground(), is(false));
264     }
265 
266     @Test
267     @SmallTest
testConvert_expedited()268     public void testConvert_expedited() {
269         if (Build.VERSION.SDK_INT < 31) {
270             return;
271         }
272 
273         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
274         workSpec.lastEnqueueTime = System.currentTimeMillis();
275         workSpec.expedited = true;
276         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
277         assertThat(jobInfo.isExpedited(), is(true));
278     }
279 
280     @Test
281     @SmallTest
testConvertExpeditedJobs_retriesAreNotExpedited()282     public void testConvertExpeditedJobs_retriesAreNotExpedited() {
283         if (Build.VERSION.SDK_INT < 31) {
284             return;
285         }
286 
287         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
288         workSpec.expedited = true;
289         workSpec.runAttemptCount = 1; // retry
290         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
291         assertThat(jobInfo.isExpedited(), is(false));
292     }
293 
294     @Test
295     @SmallTest
testConvertExpeditedJobs_delaysAreNotExpedited()296     public void testConvertExpeditedJobs_delaysAreNotExpedited() {
297         if (Build.VERSION.SDK_INT < 31) {
298             return;
299         }
300 
301         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
302         workSpec.expedited = true;
303         workSpec.lastEnqueueTime = System.currentTimeMillis();
304         workSpec.initialDelay = 1000L; // delay
305         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
306         assertThat(jobInfo.isExpedited(), is(false));
307     }
308 
convertWithRequiredNetworkType(NetworkType networkType, int jobInfoNetworkType, int minSdkVersion)309     private void convertWithRequiredNetworkType(NetworkType networkType,
310             int jobInfoNetworkType,
311             int minSdkVersion) {
312         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
313                 .setRequiredNetworkType(networkType).build());
314         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
315         if (Build.VERSION.SDK_INT >= minSdkVersion) {
316             assertThat(jobInfo.getNetworkType(), is(jobInfoNetworkType));
317         } else {
318             assertThat(jobInfo.getNetworkType(), is(JobInfo.NETWORK_TYPE_ANY));
319         }
320     }
321 
322     @Test
323     @SmallTest
testConvertNetworkType_none()324     public void testConvertNetworkType_none() {
325         assertThat(SystemJobInfoConverter.convertNetworkType(NOT_REQUIRED),
326                 is(JobInfo.NETWORK_TYPE_NONE));
327     }
328 
329     @Test
330     @SmallTest
testConvertNetworkType_any()331     public void testConvertNetworkType_any() {
332         assertThat(SystemJobInfoConverter.convertNetworkType(CONNECTED),
333                 is(JobInfo.NETWORK_TYPE_ANY));
334     }
335 
336     @Test
337     @SmallTest
testConvertNetworkType_unmetered()338     public void testConvertNetworkType_unmetered() {
339         assertThat(SystemJobInfoConverter.convertNetworkType(UNMETERED),
340                 is(JobInfo.NETWORK_TYPE_UNMETERED));
341     }
342 
343     @Test
344     @SmallTest
345     @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 23)
testConvertNetworkType_notRoaming_returnAnyBeforeApi24()346     public void testConvertNetworkType_notRoaming_returnAnyBeforeApi24() {
347         assertThat(SystemJobInfoConverter.convertNetworkType(NOT_ROAMING),
348                 is(JobInfo.NETWORK_TYPE_ANY));
349     }
350 
351     @Test
352     @SmallTest
353     @SdkSuppress(minSdkVersion = 24)
testConvertNetworkType_notRoaming_returnsNotRoamingAtOrAfterApi24()354     public void testConvertNetworkType_notRoaming_returnsNotRoamingAtOrAfterApi24() {
355         assertThat(SystemJobInfoConverter.convertNetworkType(NOT_ROAMING),
356                 is(JobInfo.NETWORK_TYPE_NOT_ROAMING));
357     }
358 
359     @Test
360     @SmallTest
361     @SdkSuppress(minSdkVersion = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL, maxSdkVersion = 25)
testConvertNetworkType_metered_returnsAnyBeforeApi26()362     public void testConvertNetworkType_metered_returnsAnyBeforeApi26() {
363         assertThat(SystemJobInfoConverter.convertNetworkType(METERED),
364                 is(JobInfo.NETWORK_TYPE_ANY));
365     }
366 
367     @Test
368     @SmallTest
369     @SdkSuppress(minSdkVersion = 26)
testConvertNetworkType_metered_returnsMeteredAtOrAfterApi26()370     public void testConvertNetworkType_metered_returnsMeteredAtOrAfterApi26() {
371         assertThat(SystemJobInfoConverter.convertNetworkType(METERED),
372                 is(JobInfo.NETWORK_TYPE_METERED));
373     }
374 
375     @Test
376     @SmallTest
377     @SdkSuppress(minSdkVersion = 30)
testConvertNetworkType_temporarilyMetered()378     public void testConvertNetworkType_temporarilyMetered() {
379         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
380                 .setRequiredNetworkType(NetworkType.TEMPORARILY_UNMETERED)
381                 .build());
382         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
383         NetworkRequest networkRequest = jobInfo.getRequiredNetwork();
384         assertTrue(networkRequest.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
385     }
386 
387     @Test
388     @SmallTest
testNetworkRequest()389     public void testNetworkRequest() {
390         NetworkRequest networkRequest = new NetworkRequest.Builder()
391                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
392                 .addCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)
393                 .build();
394         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
395         workSpec.constraints = (new Constraints.Builder())
396                 .setRequiredNetworkRequest(networkRequest, METERED)
397                 .build();
398         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
399         if (Build.VERSION.SDK_INT >= 28) {
400             assertEquals(networkRequest, jobInfo.getRequiredNetwork());
401         } else {
402             assertEquals(jobInfo.getNetworkType(), JobInfo.NETWORK_TYPE_METERED);
403         }
404     }
405 
406     @Test
407     @SmallTest
testEnsureTraceTags()408     public void testEnsureTraceTags() {
409         if (Build.VERSION.SDK_INT < 35) {
410             return;
411         }
412 
413         final String id = "id";
414         WorkSpec workSpec = new WorkSpec(id, TestWorker.class.getName());
415         workSpec.setTraceTag(TestWorker.class.getSimpleName());
416         JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
417         assertEquals(jobInfo.getTraceTag(), TestWorker.class.getSimpleName());
418     }
419 
getTestWorkSpecWithConstraints(Constraints constraints)420     private WorkSpec getTestWorkSpecWithConstraints(Constraints constraints) {
421         return new OneTimeWorkRequest.Builder(TestWorker.class)
422                 .setConstraints(constraints)
423                 .build().getWorkSpec();
424     }
425 
assertCloseValues(long value, long target)426     private void assertCloseValues(long value, long target) {
427         double min = Math.min(value, target);
428         double max = Math.max(value, target);
429         double ratio = min / max;
430         assertThat(ratio, greaterThanOrEqualTo(0.999d));
431     }
432 }
433