1 /*
2  * Copyright (C) 2020 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 android.jobscheduler.cts;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
21 
22 import android.app.job.JobInfo;
23 import android.content.ClipData;
24 import android.content.Intent;
25 import android.net.NetworkRequest;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.PersistableBundle;
29 import android.provider.ContactsContract;
30 import android.provider.MediaStore;
31 
32 /**
33  * Tests related to created and reading JobInfo objects.
34  */
35 public class JobInfoTest extends BaseJobSchedulerTest {
36     private static final int JOB_ID = JobInfoTest.class.hashCode();
37 
38     @Override
tearDown()39     public void tearDown() throws Exception {
40         mJobScheduler.cancel(JOB_ID);
41 
42         // The super method should be called at the end.
43         super.tearDown();
44     }
45 
testBackoffCriteria()46     public void testBackoffCriteria() {
47         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
48                 .setBackoffCriteria(12345, JobInfo.BACKOFF_POLICY_LINEAR)
49                 .build();
50         assertEquals(12345, ji.getInitialBackoffMillis());
51         assertEquals(JobInfo.BACKOFF_POLICY_LINEAR, ji.getBackoffPolicy());
52         // Confirm JobScheduler accepts the JobInfo object.
53         mJobScheduler.schedule(ji);
54 
55         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
56                 .setBackoffCriteria(54321, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
57                 .build();
58         assertEquals(54321, ji.getInitialBackoffMillis());
59         assertEquals(JobInfo.BACKOFF_POLICY_EXPONENTIAL, ji.getBackoffPolicy());
60         // Confirm JobScheduler accepts the JobInfo object.
61         mJobScheduler.schedule(ji);
62     }
63 
testBatteryNotLow()64     public void testBatteryNotLow() {
65         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
66                 .setRequiresBatteryNotLow(true)
67                 .build();
68         assertTrue(ji.isRequireBatteryNotLow());
69         // Confirm JobScheduler accepts the JobInfo object.
70         mJobScheduler.schedule(ji);
71 
72         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
73                 .setRequiresBatteryNotLow(false)
74                 .build();
75         assertFalse(ji.isRequireBatteryNotLow());
76         // Confirm JobScheduler accepts the JobInfo object.
77         mJobScheduler.schedule(ji);
78     }
79 
testCharging()80     public void testCharging() {
81         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
82                 .setRequiresCharging(true)
83                 .build();
84         assertTrue(ji.isRequireCharging());
85         // Confirm JobScheduler accepts the JobInfo object.
86         mJobScheduler.schedule(ji);
87 
88         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
89                 .setRequiresCharging(false)
90                 .build();
91         assertFalse(ji.isRequireCharging());
92         // Confirm JobScheduler accepts the JobInfo object.
93         mJobScheduler.schedule(ji);
94     }
95 
testClipData()96     public void testClipData() {
97         final ClipData clipData = ClipData.newPlainText("test", "testText");
98         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
99                 .setClipData(clipData, Intent.FLAG_GRANT_READ_URI_PERMISSION)
100                 .build();
101         assertEquals(clipData, ji.getClipData());
102         assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION, ji.getClipGrantFlags());
103         // Confirm JobScheduler accepts the JobInfo object.
104         mJobScheduler.schedule(ji);
105 
106         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
107                 .setClipData(null, 0)
108                 .build();
109         assertNull(ji.getClipData());
110         assertEquals(0, ji.getClipGrantFlags());
111         // Confirm JobScheduler accepts the JobInfo object.
112         mJobScheduler.schedule(ji);
113     }
114 
testDeviceIdle()115     public void testDeviceIdle() {
116         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
117                 .setRequiresDeviceIdle(true)
118                 .build();
119         assertTrue(ji.isRequireDeviceIdle());
120         // Confirm JobScheduler accepts the JobInfo object.
121         mJobScheduler.schedule(ji);
122 
123         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
124                 .setRequiresDeviceIdle(false)
125                 .build();
126         assertFalse(ji.isRequireDeviceIdle());
127         // Confirm JobScheduler accepts the JobInfo object.
128         mJobScheduler.schedule(ji);
129     }
130 
testEstimatedNetworkBytes()131     public void testEstimatedNetworkBytes() {
132         assertBuildFails(
133                 "Successfully built a JobInfo specifying estimated network bytes without"
134                         + " requesting network",
135                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
136                         .setEstimatedNetworkBytes(500, 1000));
137 
138         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
139                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
140                 .setEstimatedNetworkBytes(500, 1000)
141                 .build();
142         assertEquals(500, ji.getEstimatedNetworkDownloadBytes());
143         assertEquals(1000, ji.getEstimatedNetworkUploadBytes());
144         // Confirm JobScheduler accepts the JobInfo object.
145         mJobScheduler.schedule(ji);
146     }
147 
testExtras()148     public void testExtras() {
149         final PersistableBundle pb = new PersistableBundle();
150         pb.putInt("random_key", 42);
151         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
152                 .setPersisted(true)
153                 .setExtras(pb)
154                 .build();
155         final PersistableBundle extras = ji.getExtras();
156         assertNotNull(extras);
157         assertEquals(1, extras.keySet().size());
158         assertEquals(42, extras.getInt("random_key"));
159         // Confirm JobScheduler accepts the JobInfo object.
160         mJobScheduler.schedule(ji);
161     }
162 
testExpeditedJob()163     public void testExpeditedJob() {
164         // Test all allowed constraints.
165         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
166                 .setExpedited(true)
167                 .setPersisted(true)
168                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
169                 .setRequiresStorageNotLow(true)
170                 .build();
171         assertTrue(ji.isExpedited());
172         // Confirm JobScheduler accepts the JobInfo object.
173         mJobScheduler.schedule(ji);
174 
175         // Test disallowed constraints.
176         final String failureMessage =
177                 "Successfully built an expedited JobInfo object with disallowed constraints";
178         assertBuildFails(failureMessage,
179                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
180                         .setExpedited(true)
181                         .setMinimumLatency(100));
182         assertBuildFails(failureMessage,
183                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
184                         .setExpedited(true)
185                         .setOverrideDeadline(200));
186         assertBuildFails(failureMessage,
187                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
188                         .setExpedited(true)
189                         .setPeriodic(15 * 60_000));
190         assertBuildFails(failureMessage,
191                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
192                         .setExpedited(true)
193                         .setImportantWhileForeground(true));
194         assertBuildFails(failureMessage,
195                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
196                         .setExpedited(true)
197                         .setPrefetch(true));
198         assertBuildFails(failureMessage,
199                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
200                         .setExpedited(true)
201                         .setRequiresDeviceIdle(true));
202         assertBuildFails(failureMessage,
203                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
204                         .setExpedited(true)
205                         .setRequiresBatteryNotLow(true));
206         assertBuildFails(failureMessage,
207                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
208                         .setExpedited(true)
209                         .setRequiresCharging(true));
210         final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
211                 Uri.parse("content://" + MediaStore.AUTHORITY + "/"),
212                 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
213         assertBuildFails(failureMessage,
214                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
215                         .setExpedited(true)
216                         .addTriggerContentUri(tcu));
217     }
218 
testImportantWhileForeground()219     public void testImportantWhileForeground() {
220         // Assert the default value is false
221         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
222                 .build();
223         assertFalse(ji.isImportantWhileForeground());
224         // Confirm JobScheduler accepts the JobInfo object.
225         mJobScheduler.schedule(ji);
226 
227         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
228                 .setImportantWhileForeground(true)
229                 .build();
230         assertTrue(ji.isImportantWhileForeground());
231         // Confirm JobScheduler accepts the JobInfo object.
232         mJobScheduler.schedule(ji);
233 
234         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
235                 .setImportantWhileForeground(false)
236                 .build();
237         assertFalse(ji.isImportantWhileForeground());
238         // Confirm JobScheduler accepts the JobInfo object.
239         mJobScheduler.schedule(ji);
240     }
241 
testMinimumLatency()242     public void testMinimumLatency() {
243         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
244                 .setMinimumLatency(1337)
245                 .build();
246         assertEquals(1337, ji.getMinLatencyMillis());
247         // Confirm JobScheduler accepts the JobInfo object.
248         mJobScheduler.schedule(ji);
249     }
250 
testOverrideDeadline()251     public void testOverrideDeadline() {
252         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
253                 .setOverrideDeadline(7357)
254                 .build();
255         // ...why are the set/get methods named differently?? >.>
256         assertEquals(7357, ji.getMaxExecutionDelayMillis());
257     }
258 
testPeriodic()259     public void testPeriodic() {
260         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
261                 .setPeriodic(60 * 60 * 1000L)
262                 .build();
263         assertTrue(ji.isPeriodic());
264         assertEquals(60 * 60 * 1000L, ji.getIntervalMillis());
265         assertEquals(60 * 60 * 1000L, ji.getFlexMillis());
266         // Confirm JobScheduler accepts the JobInfo object.
267         mJobScheduler.schedule(ji);
268 
269         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
270                 .setPeriodic(120 * 60 * 1000L, 20 * 60 * 1000L)
271                 .build();
272         assertTrue(ji.isPeriodic());
273         assertEquals(120 * 60 * 1000L, ji.getIntervalMillis());
274         assertEquals(20 * 60 * 1000L, ji.getFlexMillis());
275         // Confirm JobScheduler accepts the JobInfo object.
276         mJobScheduler.schedule(ji);
277     }
278 
testPersisted()279     public void testPersisted() {
280         // Assert the default value is false
281         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
282                 .build();
283         assertFalse(ji.isPersisted());
284         // Confirm JobScheduler accepts the JobInfo object.
285         mJobScheduler.schedule(ji);
286 
287         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
288                 .setPersisted(true)
289                 .build();
290         assertTrue(ji.isPersisted());
291         // Confirm JobScheduler accepts the JobInfo object.
292         mJobScheduler.schedule(ji);
293 
294         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
295                 .setPersisted(false)
296                 .build();
297         assertFalse(ji.isPersisted());
298         // Confirm JobScheduler accepts the JobInfo object.
299         mJobScheduler.schedule(ji);
300     }
301 
testPrefetch()302     public void testPrefetch() {
303         // Assert the default value is false
304         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
305                 .build();
306         assertFalse(ji.isPrefetch());
307         // Confirm JobScheduler accepts the JobInfo object.
308         mJobScheduler.schedule(ji);
309 
310         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
311                 .setPrefetch(true)
312                 .build();
313         assertTrue(ji.isPrefetch());
314         // Confirm JobScheduler accepts the JobInfo object.
315         mJobScheduler.schedule(ji);
316 
317         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
318                 .setPrefetch(false)
319                 .build();
320         assertFalse(ji.isPrefetch());
321         // Confirm JobScheduler accepts the JobInfo object.
322         mJobScheduler.schedule(ji);
323     }
324 
testRequiredNetwork()325     public void testRequiredNetwork() {
326         final NetworkRequest nr = new NetworkRequest.Builder()
327                 .addCapability(NET_CAPABILITY_INTERNET)
328                 .addCapability(NET_CAPABILITY_VALIDATED)
329                 .build();
330         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
331                 .setRequiredNetwork(nr)
332                 .build();
333         assertEquals(nr, ji.getRequiredNetwork());
334         // Confirm JobScheduler accepts the JobInfo object.
335         mJobScheduler.schedule(ji);
336 
337         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
338                 .setRequiredNetwork(null)
339                 .build();
340         assertNull(ji.getRequiredNetwork());
341         // Confirm JobScheduler accepts the JobInfo object.
342         mJobScheduler.schedule(ji);
343     }
344 
345     @SuppressWarnings("deprecation")
testRequiredNetworkType()346     public void testRequiredNetworkType() {
347         // Assert the default value is NONE
348         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
349                 .build();
350         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
351         // Confirm JobScheduler accepts the JobInfo object.
352         mJobScheduler.schedule(ji);
353 
354         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
355                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
356                 .build();
357         assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
358         // Confirm JobScheduler accepts the JobInfo object.
359         mJobScheduler.schedule(ji);
360 
361         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
362                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
363                 .build();
364         assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
365         // Confirm JobScheduler accepts the JobInfo object.
366         mJobScheduler.schedule(ji);
367 
368         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
369                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
370                 .build();
371         assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
372         // Confirm JobScheduler accepts the JobInfo object.
373         mJobScheduler.schedule(ji);
374 
375         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
376                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
377                 .build();
378         assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
379         // Confirm JobScheduler accepts the JobInfo object.
380         mJobScheduler.schedule(ji);
381 
382         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
383                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
384                 .build();
385         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
386         // Confirm JobScheduler accepts the JobInfo object.
387         mJobScheduler.schedule(ji);
388     }
389 
testStorageNotLow()390     public void testStorageNotLow() {
391         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
392                 .setRequiresStorageNotLow(true)
393                 .build();
394         assertTrue(ji.isRequireStorageNotLow());
395         // Confirm JobScheduler accepts the JobInfo object.
396         mJobScheduler.schedule(ji);
397 
398         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
399                 .setRequiresStorageNotLow(false)
400                 .build();
401         assertFalse(ji.isRequireStorageNotLow());
402         // Confirm JobScheduler accepts the JobInfo object.
403         mJobScheduler.schedule(ji);
404     }
405 
testTransientExtras()406     public void testTransientExtras() {
407         final Bundle b = new Bundle();
408         b.putBoolean("random_bool", true);
409         assertBuildFails("Successfully built a persisted JobInfo object with transient extras",
410                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
411                         .setPersisted(true)
412                         .setTransientExtras(b));
413 
414         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
415                 .setTransientExtras(b)
416                 .build();
417         assertEquals(b.size(), ji.getTransientExtras().size());
418         for (String key : b.keySet()) {
419             assertEquals(b.get(key), ji.getTransientExtras().get(key));
420         }
421         // Confirm JobScheduler accepts the JobInfo object.
422         mJobScheduler.schedule(ji);
423     }
424 
testTriggerContentMaxDelay()425     public void testTriggerContentMaxDelay() {
426         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
427                 .setTriggerContentMaxDelay(1337)
428                 .build();
429         assertEquals(1337, ji.getTriggerContentMaxDelay());
430         // Confirm JobScheduler accepts the JobInfo object.
431         mJobScheduler.schedule(ji);
432     }
433 
testTriggerContentUpdateDelay()434     public void testTriggerContentUpdateDelay() {
435         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
436                 .setTriggerContentUpdateDelay(1337)
437                 .build();
438         assertEquals(1337, ji.getTriggerContentUpdateDelay());
439         // Confirm JobScheduler accepts the JobInfo object.
440         mJobScheduler.schedule(ji);
441     }
442 
testTriggerContentUri()443     public void testTriggerContentUri() {
444         final Uri u = Uri.parse("content://" + MediaStore.AUTHORITY + "/");
445         final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
446                 u, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
447         assertEquals(u, tcu.getUri());
448         assertEquals(JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS, tcu.getFlags());
449         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
450                 .addTriggerContentUri(tcu)
451                 .build();
452         assertEquals(1, ji.getTriggerContentUris().length);
453         assertEquals(tcu, ji.getTriggerContentUris()[0]);
454         // Confirm JobScheduler accepts the JobInfo object.
455         mJobScheduler.schedule(ji);
456 
457         final Uri u2 = Uri.parse("content://" + ContactsContract.AUTHORITY + "/");
458         final JobInfo.TriggerContentUri tcu2 = new JobInfo.TriggerContentUri(u2, 0);
459         assertEquals(u2, tcu2.getUri());
460         assertEquals(0, tcu2.getFlags());
461         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
462                 .addTriggerContentUri(tcu)
463                 .addTriggerContentUri(tcu2)
464                 .build();
465         assertEquals(2, ji.getTriggerContentUris().length);
466         assertEquals(tcu, ji.getTriggerContentUris()[0]);
467         assertEquals(tcu2, ji.getTriggerContentUris()[1]);
468         // Confirm JobScheduler accepts the JobInfo object.
469         mJobScheduler.schedule(ji);
470     }
471 
assertBuildFails(String message, JobInfo.Builder builder)472     private void assertBuildFails(String message, JobInfo.Builder builder) {
473         try {
474             builder.build();
475             fail(message);
476         } catch (IllegalArgumentException e) {
477             // Expected
478         }
479     }
480 }
481