• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.jobscheduler.cts.TestAppInterface.ENFORCE_MINIMUM_TIME_WINDOWS;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
22 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
23 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
24 
25 import android.app.compat.CompatChanges;
26 import android.app.job.Flags;
27 import android.app.job.JobInfo;
28 import android.content.ClipData;
29 import android.content.Intent;
30 import android.net.NetworkCapabilities;
31 import android.net.NetworkRequest;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.PersistableBundle;
35 import android.platform.test.annotations.RequiresFlagsDisabled;
36 import android.platform.test.annotations.RequiresFlagsEnabled;
37 import android.provider.ContactsContract;
38 import android.provider.MediaStore;
39 import android.util.Log;
40 
41 import com.android.compatibility.common.util.SystemUtil;
42 
43 import java.lang.reflect.Method;
44 import java.util.Set;
45 
46 /**
47  * Tests related to creating and reading JobInfo objects.
48  */
49 public class JobInfoTest extends BaseJobSchedulerTest {
50     private static final int JOB_ID = JobInfoTest.class.hashCode();
51     private static final String TAG = JobInfoTest.class.getSimpleName();
52 
53     private static final long REJECT_NEGATIVE_DELAYS_AND_DEADLINES = 323349338L;
54     private static final long THROW_ON_UNSUPPORTED_BIAS_USAGE = 300477393L;
55 
56     @Override
tearDown()57     public void tearDown() throws Exception {
58         mJobScheduler.cancel(JOB_ID);
59 
60         // The super method should be called at the end.
61         super.tearDown();
62     }
63 
testBackoffCriteria()64     public void testBackoffCriteria() {
65         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
66                 .setBackoffCriteria(12345, JobInfo.BACKOFF_POLICY_LINEAR)
67                 .build();
68         assertEquals(12345, ji.getInitialBackoffMillis());
69         assertEquals(JobInfo.BACKOFF_POLICY_LINEAR, ji.getBackoffPolicy());
70         // Confirm JobScheduler accepts the JobInfo object.
71         mJobScheduler.schedule(ji);
72 
73         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
74                 .setBackoffCriteria(54321, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
75                 .build();
76         assertEquals(54321, ji.getInitialBackoffMillis());
77         assertEquals(JobInfo.BACKOFF_POLICY_EXPONENTIAL, ji.getBackoffPolicy());
78         // Confirm JobScheduler accepts the JobInfo object.
79         mJobScheduler.schedule(ji);
80     }
81 
testBatteryNotLow()82     public void testBatteryNotLow() {
83         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
84                 .setRequiresBatteryNotLow(true)
85                 .build();
86         assertTrue(ji.isRequireBatteryNotLow());
87         // Confirm JobScheduler accepts the JobInfo object.
88         mJobScheduler.schedule(ji);
89 
90         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
91                 .setRequiresBatteryNotLow(false)
92                 .build();
93         assertFalse(ji.isRequireBatteryNotLow());
94         // Confirm JobScheduler accepts the JobInfo object.
95         mJobScheduler.schedule(ji);
96     }
97 
testBias()98     public void testBias() throws Exception {
99         JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, kJobServiceComponent);
100 
101         Method setBiasMethod = JobInfo.Builder.class.getDeclaredMethod("setBias", int.class);
102         setBiasMethod.setAccessible(true);
103         setBiasMethod.invoke(builder, 40);
104 
105         JobInfo ji = builder.build();
106         // Confirm JobScheduler rejects the JobInfo object.
107         // TODO(b/309023462): create separate tests for target SDK gated changes
108         if (CompatChanges.isChangeEnabled(THROW_ON_UNSUPPORTED_BIAS_USAGE)) {
109             assertScheduleFailsWithException(
110                     "Successfully scheduled a job with a modified bias",
111                     ji, SecurityException.class);
112         } else {
113             mJobScheduler.schedule(ji);
114 
115             assertEquals("Bias wasn't changed to default",
116                     0, getBias(mJobScheduler.getPendingJob(JOB_ID)));
117         }
118     }
119 
getBias(JobInfo job)120     private int getBias(JobInfo job) throws Exception {
121         Method getBiasMethod = JobInfo.class.getDeclaredMethod("getBias");
122         getBiasMethod.setAccessible(true);
123         return (Integer) getBiasMethod.invoke(job);
124     }
125 
testCharging()126     public void testCharging() {
127         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
128                 .setRequiresCharging(true)
129                 .build();
130         assertTrue(ji.isRequireCharging());
131         // Confirm JobScheduler accepts the JobInfo object.
132         mJobScheduler.schedule(ji);
133 
134         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
135                 .setRequiresCharging(false)
136                 .build();
137         assertFalse(ji.isRequireCharging());
138         // Confirm JobScheduler accepts the JobInfo object.
139         mJobScheduler.schedule(ji);
140     }
141 
testClipData()142     public void testClipData() {
143         final ClipData clipData = ClipData.newPlainText("test", "testText");
144         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
145                 .setClipData(clipData, Intent.FLAG_GRANT_READ_URI_PERMISSION)
146                 .build();
147         assertEquals(clipData, ji.getClipData());
148         assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION, ji.getClipGrantFlags());
149         // Confirm JobScheduler accepts the JobInfo object.
150         mJobScheduler.schedule(ji);
151 
152         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
153                 .setClipData(null, 0)
154                 .build();
155         assertNull(ji.getClipData());
156         assertEquals(0, ji.getClipGrantFlags());
157         // Confirm JobScheduler accepts the JobInfo object.
158         mJobScheduler.schedule(ji);
159     }
160 
161     // TODO(b/315035390): migrate to JUnit4
162     @RequiresFlagsEnabled(Flags.FLAG_JOB_DEBUG_INFO_APIS) // Doesn't work for JUnit3
testDebugTags()163     public void testDebugTags() {
164         if (!isAconfigFlagEnabled(Flags.FLAG_JOB_DEBUG_INFO_APIS)) {
165             return;
166         }
167         // Confirm defaults
168         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent).build();
169         assertEquals(0, ji.getDebugTags().size());
170         // Confirm JobScheduler accepts the JobInfo object.
171         mJobScheduler.schedule(ji);
172 
173         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
174                 .addDebugTag("a")
175                 .addDebugTag("b")
176                 .addDebugTag("c")
177                 .build();
178         assertEquals(Set.of("a", "b", "c"), ji.getDebugTags());
179         // Confirm JobScheduler accepts the JobInfo object.
180         mJobScheduler.schedule(ji);
181 
182         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
183                 .addDebugTag("a")
184                 .addDebugTag("b")
185                 .addDebugTag("c")
186                 .removeDebugTag("b")
187                 .build();
188         assertEquals(Set.of("a", "c"), ji.getDebugTags());
189         // Confirm JobScheduler accepts the JobInfo object.
190         mJobScheduler.schedule(ji);
191 
192         // Tag is at the character limit
193         final String maxLengthDebugTag =
194                 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
195                         + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
196         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
197                 .addDebugTag(maxLengthDebugTag)
198                 .build();
199         assertEquals(Set.of(maxLengthDebugTag), ji.getDebugTags());
200         // Confirm JobScheduler accepts the JobInfo object.
201         mJobScheduler.schedule(ji);
202 
203         try {
204             new JobInfo.Builder(JOB_ID, kJobServiceComponent).addDebugTag(null).build();
205             fail("Successfully built a JobInfo with a null debug tag");
206         } catch (Exception e) {
207             // Success
208         }
209         try {
210             new JobInfo.Builder(JOB_ID, kJobServiceComponent).addDebugTag("").build();
211             fail("Successfully built a JobInfo with an empty debug tag");
212         } catch (Exception e) {
213             // Success
214         }
215         try {
216             new JobInfo.Builder(JOB_ID, kJobServiceComponent).addDebugTag("        ").build();
217             fail("Successfully built a JobInfo with a whitespace-only debug tag");
218         } catch (Exception e) {
219             // Success
220         }
221         try {
222             new JobInfo.Builder(JOB_ID, kJobServiceComponent)
223                     .setTraceTag(maxLengthDebugTag + "x").build();
224             fail("Successfully built a JobInfo with a long debug tag");
225         } catch (Exception e) {
226             // Success
227         }
228         JobInfo.Builder jiBuilder = new JobInfo.Builder(JOB_ID, kJobServiceComponent);
229         for (int i = 0; i < 33; ++i) {
230             jiBuilder.addDebugTag(Integer.toString(i));
231         }
232         assertBuildFails("Successfully built a JobInfo with too many debug tags", jiBuilder);
233     }
234 
testDeviceIdle()235     public void testDeviceIdle() {
236         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
237                 .setRequiresDeviceIdle(true)
238                 .build();
239         assertTrue(ji.isRequireDeviceIdle());
240         // Confirm JobScheduler accepts the JobInfo object.
241         mJobScheduler.schedule(ji);
242 
243         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
244                 .setRequiresDeviceIdle(false)
245                 .build();
246         assertFalse(ji.isRequireDeviceIdle());
247         // Confirm JobScheduler accepts the JobInfo object.
248         mJobScheduler.schedule(ji);
249     }
250 
testEstimatedNetworkBytes()251     public void testEstimatedNetworkBytes() {
252         assertBuildFails(
253                 "Successfully built a JobInfo specifying estimated network bytes without"
254                         + " requesting network",
255                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
256                         .setEstimatedNetworkBytes(500, 1000));
257 
258         try {
259             assertBuildFails(
260                     "Successfully built a JobInfo specifying a negative download bytes value",
261                     new JobInfo.Builder(JOB_ID, kJobServiceComponent)
262                             .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
263                             .setEstimatedNetworkBytes(-500, JobInfo.NETWORK_BYTES_UNKNOWN));
264         } catch (IllegalArgumentException expected) {
265             // Success. setMinimumNetworkChunkBytes() should throw the exception.
266         }
267 
268         try {
269             assertBuildFails(
270                     "Successfully built a JobInfo specifying a negative upload bytes value",
271                     new JobInfo.Builder(JOB_ID, kJobServiceComponent)
272                             .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
273                             .setEstimatedNetworkBytes(JobInfo.NETWORK_BYTES_UNKNOWN, -500));
274         } catch (IllegalArgumentException expected) {
275             // Success. setMinimumNetworkChunkBytes() should throw the exception.
276         }
277 
278         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
279                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
280                 .setEstimatedNetworkBytes(500, 1000)
281                 .build();
282         assertEquals(500, ji.getEstimatedNetworkDownloadBytes());
283         assertEquals(1000, ji.getEstimatedNetworkUploadBytes());
284         // Confirm JobScheduler accepts the JobInfo object.
285         mJobScheduler.schedule(ji);
286 
287         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
288                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
289                 .setEstimatedNetworkBytes(
290                         JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN)
291                 .build();
292         assertEquals(JobInfo.NETWORK_BYTES_UNKNOWN, ji.getEstimatedNetworkDownloadBytes());
293         assertEquals(JobInfo.NETWORK_BYTES_UNKNOWN, ji.getEstimatedNetworkUploadBytes());
294         // Confirm JobScheduler accepts the JobInfo object.
295         mJobScheduler.schedule(ji);
296     }
297 
testExtras()298     public void testExtras() {
299         final PersistableBundle pb = new PersistableBundle();
300         pb.putInt("random_key", 42);
301         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
302                 .setPersisted(true)
303                 .setExtras(pb)
304                 .build();
305         final PersistableBundle extras = ji.getExtras();
306         assertNotNull(extras);
307         assertEquals(1, extras.keySet().size());
308         assertEquals(42, extras.getInt("random_key"));
309         // Confirm JobScheduler accepts the JobInfo object.
310         mJobScheduler.schedule(ji);
311     }
312 
testExpeditedJob()313     public void testExpeditedJob() {
314         // Test all allowed constraints.
315         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
316                 .setExpedited(true)
317                 .setPriority(JobInfo.PRIORITY_HIGH)
318                 .setPersisted(true)
319                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
320                 .setRequiresStorageNotLow(true)
321                 .build();
322         assertTrue(ji.isExpedited());
323         // Confirm JobScheduler accepts the JobInfo object.
324         mJobScheduler.schedule(ji);
325 
326         // Confirm default priority for EJs.
327         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
328                 .setExpedited(true)
329                 .build();
330         assertEquals(JobInfo.PRIORITY_MAX, ji.getPriority());
331         // Confirm JobScheduler accepts the JobInfo object.
332         mJobScheduler.schedule(ji);
333 
334         // Test disallowed constraints.
335         final String failureMessage =
336                 "Successfully built an expedited JobInfo object with disallowed constraints";
337         assertBuildFails(failureMessage,
338                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
339                         .setExpedited(true)
340                         .setMinimumLatency(100));
341         assertBuildFails(failureMessage,
342                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
343                         .setExpedited(true)
344                         .setOverrideDeadline(24 * HOUR_IN_MILLIS));
345         assertBuildFails(failureMessage,
346                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
347                         .setExpedited(true)
348                         .setPeriodic(15 * 60_000));
349         assertBuildFails(failureMessage,
350                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
351                         .setExpedited(true)
352                         .setPriority(JobInfo.PRIORITY_LOW));
353         assertBuildFails(failureMessage,
354                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
355                         .setExpedited(true)
356                         .setPriority(JobInfo.PRIORITY_DEFAULT));
357         assertBuildFails(failureMessage,
358                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
359                         .setExpedited(true)
360                         .setPrefetch(true));
361         assertBuildFails(failureMessage,
362                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
363                         .setExpedited(true)
364                         .setRequiresDeviceIdle(true));
365         assertBuildFails(failureMessage,
366                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
367                         .setExpedited(true)
368                         .setRequiresBatteryNotLow(true));
369         assertBuildFails(failureMessage,
370                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
371                         .setExpedited(true)
372                         .setRequiresCharging(true));
373         assertBuildFails(failureMessage,
374                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
375                         .setExpedited(true)
376                         .setUserInitiated(true));
377         final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
378                 Uri.parse("content://" + MediaStore.AUTHORITY + "/"),
379                 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
380         assertBuildFails(failureMessage,
381                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
382                         .setExpedited(true)
383                         .addTriggerContentUri(tcu));
384     }
385 
386     // TODO(b/315035390): migrate to JUnit4
387     @SuppressWarnings("deprecation")
388     @RequiresFlagsDisabled(Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
testImportantWhileForeground_Legacy()389     public void testImportantWhileForeground_Legacy() {
390         if (isAconfigFlagEnabled(Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)) {
391             return;
392         }
393         // Assert the default value is false
394         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
395                 .build();
396         assertFalse(ji.isImportantWhileForeground());
397         // Confirm JobScheduler accepts the JobInfo object.
398         mJobScheduler.schedule(ji);
399 
400         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
401                 .setImportantWhileForeground(true)
402                 .build();
403         assertTrue(ji.isImportantWhileForeground());
404         assertEquals(JobInfo.PRIORITY_HIGH, ji.getPriority());
405         // Confirm JobScheduler accepts the JobInfo object.
406         mJobScheduler.schedule(ji);
407 
408         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
409                 .setImportantWhileForeground(false)
410                 .build();
411         assertFalse(ji.isImportantWhileForeground());
412         // Confirm JobScheduler accepts the JobInfo object.
413         mJobScheduler.schedule(ji);
414 
415 
416         //noinspection deprecation
417         assertBuildFails("Successfully built a low-priority JobInfo object with"
418                 + " disallowed important while foreground flag",
419                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
420                         .setPriority(JobInfo.PRIORITY_LOW)
421                         .setImportantWhileForeground(true));
422 
423         assertBuildFails("Successfully built a user-initiated JobInfo object with"
424                 + " disallowed important while foreground flag",
425                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
426                         .setUserInitiated(true)
427                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
428                         .setImportantWhileForeground(true));
429 
430         assertBuildFails("Successfully built an expedited JobInfo object with"
431                 + " disallowed important while foreground flag",
432                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
433                         .setExpedited(true)
434                         .setImportantWhileForeground(true));
435     }
436 
437     @SuppressWarnings("deprecation")
438     @RequiresFlagsEnabled(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
testImportantWhileForeground_Ignored()439     public void testImportantWhileForeground_Ignored() {
440         if (!isAconfigFlagEnabled(Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)) {
441             return;
442         }
443 
444         // Assert the value is false always
445         final JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
446                 .setImportantWhileForeground(true)
447                 .setPriority(JobInfo.PRIORITY_LOW)
448                 .build();
449         assertFalse(ji.isImportantWhileForeground());
450         // No priority change.
451         assertEquals(JobInfo.PRIORITY_LOW, ji.getPriority());
452         // Confirm JobScheduler accepts the JobInfo object.
453         mJobScheduler.schedule(ji);
454     }
455 
testMinimumChunkSizeBytes()456     public void testMinimumChunkSizeBytes() {
457         assertBuildFails(
458                 "Successfully built a JobInfo specifying minimum chunk bytes without"
459                         + " requesting network",
460                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
461                         .setMinimumNetworkChunkBytes(500));
462         try {
463             assertBuildFails(
464                     "Successfully built a JobInfo specifying minimum chunk bytes a negative value",
465                     new JobInfo.Builder(JOB_ID, kJobServiceComponent)
466                             .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
467                             .setMinimumNetworkChunkBytes(-500));
468         } catch (IllegalArgumentException expected) {
469             // Success. setMinimumNetworkChunkBytes() should throw the exception.
470         }
471 
472         assertBuildFails(
473                 "Successfully built a JobInfo with a higher minimum chunk size than total"
474                         + " transfer size",
475                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
476                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
477                         .setMinimumNetworkChunkBytes(500)
478                         .setEstimatedNetworkBytes(5, 5));
479         assertBuildFails(
480                 "Successfully built a JobInfo with a higher minimum chunk size than total"
481                         + " transfer size",
482                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
483                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
484                         .setMinimumNetworkChunkBytes(500)
485                         .setEstimatedNetworkBytes(JobInfo.NETWORK_BYTES_UNKNOWN, 5));
486         assertBuildFails(
487                 "Successfully built a JobInfo with a higher minimum chunk size than total"
488                         + " transfer size",
489                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
490                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
491                         .setMinimumNetworkChunkBytes(500)
492                         .setEstimatedNetworkBytes(5, JobInfo.NETWORK_BYTES_UNKNOWN));
493 
494         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
495                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
496                 .setMinimumNetworkChunkBytes(500)
497                 .setEstimatedNetworkBytes(
498                         JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN)
499                 .build();
500         assertEquals(500, ji.getMinimumNetworkChunkBytes());
501         // Confirm JobScheduler accepts the JobInfo object.
502         mJobScheduler.schedule(ji);
503     }
504 
testMinimumLatency()505     public void testMinimumLatency() {
506         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
507                 .setMinimumLatency(1337)
508                 .build();
509         assertEquals(1337, ji.getMinLatencyMillis());
510         // Confirm JobScheduler accepts the JobInfo object.
511         mJobScheduler.schedule(ji);
512     }
513 
testMinimumLatency_negative()514     public void testMinimumLatency_negative() {
515         JobInfo.Builder jiBuilder = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
516                 .setMinimumLatency(-1);
517 
518         // TODO(b/309023462): create separate tests for target SDK gated changes
519         if (CompatChanges.isChangeEnabled(REJECT_NEGATIVE_DELAYS_AND_DEADLINES)) {
520             assertBuildFails("Successfully scheduled a job with a negative latency", jiBuilder);
521         } else {
522             // Confirm JobScheduler accepts the JobInfo object.
523             JobInfo ji = jiBuilder.build();
524             assertEquals(0, ji.getMinLatencyMillis());
525             mJobScheduler.schedule(ji);
526         }
527     }
528 
testOverrideDeadline()529     public void testOverrideDeadline() {
530         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
531                 .setOverrideDeadline(HOUR_IN_MILLIS)
532                 .build();
533         // ...why are the set/get methods named differently?? >.>
534         assertEquals(HOUR_IN_MILLIS, ji.getMaxExecutionDelayMillis());
535         // Confirm JobScheduler accepts the JobInfo object.
536         mJobScheduler.schedule(ji);
537     }
538 
testOverrideDeadline_minimumTimeWindows()539     public void testOverrideDeadline_minimumTimeWindows() throws Exception {
540         JobInfo.Builder jiBuilderShortFunctional = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
541                 .setRequiresCharging(true)
542                 .setMinimumLatency(MINUTE_IN_MILLIS)
543                 .setOverrideDeadline(16 * MINUTE_IN_MILLIS - 1);
544         JobInfo.Builder jiBuilderShortNonfunctional =
545                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
546                         .setMinimumLatency(MINUTE_IN_MILLIS)
547                         .setOverrideDeadline(16 * MINUTE_IN_MILLIS - 1);
548         JobInfo.Builder jiBuilderLongFunctional = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
549                 .setRequiresCharging(true)
550                 .setMinimumLatency(MINUTE_IN_MILLIS)
551                 .setOverrideDeadline(16 * MINUTE_IN_MILLIS);
552         JobInfo.Builder jiBuilderLongNonfunctional =
553                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
554                         .setMinimumLatency(MINUTE_IN_MILLIS)
555                         .setOverrideDeadline(16 * MINUTE_IN_MILLIS);
556 
557         // TODO(b/309023462): create separate tests for target SDK gated changes
558         if (CompatChanges.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS) && isAconfigFlagEnabled(
559                 "android.app.job.enforce_minimum_time_windows")) {
560             // Confirm JobScheduler rejects the bad JobInfo objects.
561             assertBuildFails(
562                     "Successfully scheduled a job with a short deadline and functional constraints",
563                     jiBuilderShortFunctional);
564         } else {
565             // Confirm JobScheduler accepts the JobInfo objects.
566             mJobScheduler.schedule(jiBuilderShortFunctional.build());
567         }
568         // Confirm JobScheduler accepts the good JobInfo objects.
569         mJobScheduler.schedule(jiBuilderShortNonfunctional.build());
570         mJobScheduler.schedule(jiBuilderLongFunctional.build());
571         mJobScheduler.schedule(jiBuilderLongNonfunctional.build());
572     }
573 
testOverrideDeadline_negative()574     public void testOverrideDeadline_negative() {
575         JobInfo.Builder jiBuilder = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
576                 .setOverrideDeadline(-1);
577 
578         // TODO(b/309023462): create separate tests for target SDK gated changes
579         if (CompatChanges.isChangeEnabled(REJECT_NEGATIVE_DELAYS_AND_DEADLINES)) {
580             assertBuildFails("Successfully scheduled a job with a negative deadline", jiBuilder);
581         } else {
582             // Confirm JobScheduler accepts the JobInfo object.
583             JobInfo ji = jiBuilder.build();
584             assertTrue(ji.getMaxExecutionDelayMillis() >= 0);
585             mJobScheduler.schedule(ji);
586         }
587     }
588 
testPeriodic()589     public void testPeriodic() {
590         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
591                 .setPeriodic(60 * 60 * 1000L)
592                 .build();
593         assertTrue(ji.isPeriodic());
594         assertEquals(60 * 60 * 1000L, ji.getIntervalMillis());
595         assertEquals(60 * 60 * 1000L, ji.getFlexMillis());
596         // Confirm JobScheduler accepts the JobInfo object.
597         mJobScheduler.schedule(ji);
598 
599         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
600                 .setPeriodic(120 * 60 * 1000L, 20 * 60 * 1000L)
601                 .build();
602         assertTrue(ji.isPeriodic());
603         assertEquals(120 * 60 * 1000L, ji.getIntervalMillis());
604         assertEquals(20 * 60 * 1000L, ji.getFlexMillis());
605         // Confirm JobScheduler accepts the JobInfo object.
606         mJobScheduler.schedule(ji);
607     }
608 
testPersisted()609     public void testPersisted() {
610         // Assert the default value is false
611         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
612                 .build();
613         assertFalse(ji.isPersisted());
614         // Confirm JobScheduler accepts the JobInfo object.
615         mJobScheduler.schedule(ji);
616 
617         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
618                 .setPersisted(true)
619                 .build();
620         assertTrue(ji.isPersisted());
621         // Confirm JobScheduler accepts the JobInfo object.
622         mJobScheduler.schedule(ji);
623 
624         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
625                 .setPersisted(false)
626                 .build();
627         assertFalse(ji.isPersisted());
628         // Confirm JobScheduler accepts the JobInfo object.
629         mJobScheduler.schedule(ji);
630     }
631 
testPrefetch()632     public void testPrefetch() {
633         // Assert the default value is false
634         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
635                 .build();
636         assertFalse(ji.isPrefetch());
637         // Confirm JobScheduler accepts the JobInfo object.
638         mJobScheduler.schedule(ji);
639 
640         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
641                 .setPrefetch(true)
642                 .build();
643         assertTrue(ji.isPrefetch());
644         // Confirm JobScheduler accepts the JobInfo object.
645         mJobScheduler.schedule(ji);
646 
647         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
648                 .setPrefetch(false)
649                 .build();
650         assertFalse(ji.isPrefetch());
651         // Confirm JobScheduler accepts the JobInfo object.
652         mJobScheduler.schedule(ji);
653 
654         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
655                 .setMinimumLatency(60_000L)
656                 .setPrefetch(true)
657                 .build();
658         assertTrue(ji.isPrefetch());
659         // Confirm JobScheduler accepts the JobInfo object.
660         mJobScheduler.schedule(ji);
661 
662         // CTS naturally targets latest SDK version. Compat change should be enabled by default.
663         assertBuildFails("Modern prefetch jobs can't have a deadline",
664                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
665                         .setMinimumLatency(60_000L)
666                         .setOverrideDeadline(24 * HOUR_IN_MILLIS)
667                         .setPrefetch(true));
668     }
669 
testPriority()670     public void testPriority() {
671         // Assert the default value is DEFAULT
672         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
673                 .build();
674         assertEquals(JobInfo.PRIORITY_DEFAULT, ji.getPriority());
675         // Confirm JobScheduler accepts the JobInfo object.
676         mJobScheduler.schedule(ji);
677 
678         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
679                 .setPriority(JobInfo.PRIORITY_LOW)
680                 .build();
681         assertEquals(JobInfo.PRIORITY_LOW, ji.getPriority());
682         // Confirm JobScheduler accepts the JobInfo object.
683         mJobScheduler.schedule(ji);
684 
685         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
686                 .setPriority(JobInfo.PRIORITY_MIN)
687                 .build();
688         assertEquals(JobInfo.PRIORITY_MIN, ji.getPriority());
689         // Confirm JobScheduler accepts the JobInfo object.
690         mJobScheduler.schedule(ji);
691 
692         // Attempt an invalid number
693         try {
694             // It's over 9000!!!
695             new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(9001).build();
696             fail("Successfully built a job with an invalid priority level");
697         } catch (Exception e) {
698             // Success
699         }
700         try {
701             new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(-1).build();
702             fail("Successfully built a job with an invalid priority level");
703         } catch (Exception e) {
704             // Success
705         }
706         try {
707             new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(123).build();
708             fail("Successfully built a job with an invalid priority level");
709         } catch (Exception e) {
710             // Success
711         }
712 
713         // Test other invalid configurations.
714         final String failureMessage =
715                 "Successfully built a JobInfo object with disallowed priority configurations";
716         assertBuildFails(failureMessage,
717                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
718                         .setPriority(JobInfo.PRIORITY_MAX));
719         assertBuildFails(failureMessage,
720                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
721                         .setPriority(JobInfo.PRIORITY_HIGH)
722                         .setPrefetch(true));
723         assertBuildFails(failureMessage,
724                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
725                         .setPriority(JobInfo.PRIORITY_HIGH)
726                         .setPeriodic(JobInfo.getMinPeriodMillis()));
727     }
728 
testRequiredNetwork()729     public void testRequiredNetwork() {
730         final NetworkRequest nr = new NetworkRequest.Builder()
731                 .addCapability(NET_CAPABILITY_INTERNET)
732                 .addCapability(NET_CAPABILITY_VALIDATED)
733                 .build();
734         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
735                 .setRequiredNetwork(nr)
736                 .build();
737         assertEquals(nr, ji.getRequiredNetwork());
738         // Confirm JobScheduler accepts the JobInfo object.
739         mJobScheduler.schedule(ji);
740 
741         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
742                 .setRequiredNetwork(null)
743                 .build();
744         assertNull(ji.getRequiredNetwork());
745         // Confirm JobScheduler accepts the JobInfo object.
746         mJobScheduler.schedule(ji);
747     }
748 
749     @SuppressWarnings("deprecation")
testRequiredNetworkType()750     public void testRequiredNetworkType() {
751         // Assert the default value is NONE
752         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
753                 .build();
754         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
755         assertNull(ji.getRequiredNetwork());
756         // Confirm JobScheduler accepts the JobInfo object.
757         mJobScheduler.schedule(ji);
758 
759         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
760                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
761                 .build();
762         assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
763         assertTrue(ji.getRequiredNetwork()
764                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
765         assertTrue(ji.getRequiredNetwork()
766                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
767         assertFalse(ji.getRequiredNetwork()
768                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
769         assertFalse(ji.getRequiredNetwork()
770                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
771         // Confirm JobScheduler accepts the JobInfo object.
772         mJobScheduler.schedule(ji);
773 
774         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
775                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
776                 .build();
777         assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
778         assertTrue(ji.getRequiredNetwork()
779                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
780         assertTrue(ji.getRequiredNetwork()
781                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
782         assertFalse(ji.getRequiredNetwork()
783                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
784         assertFalse(ji.getRequiredNetwork()
785                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
786         // Confirm JobScheduler accepts the JobInfo object.
787         mJobScheduler.schedule(ji);
788 
789         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
790                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
791                 .build();
792         assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
793         assertTrue(ji.getRequiredNetwork()
794                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
795         assertTrue(ji.getRequiredNetwork()
796                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
797         assertFalse(ji.getRequiredNetwork()
798                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
799         assertFalse(ji.getRequiredNetwork()
800                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
801         // Confirm JobScheduler accepts the JobInfo object.
802         mJobScheduler.schedule(ji);
803 
804         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
805                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
806                 .build();
807         assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
808         assertTrue(ji.getRequiredNetwork()
809                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
810         assertTrue(ji.getRequiredNetwork()
811                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
812         assertFalse(ji.getRequiredNetwork()
813                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
814         assertFalse(ji.getRequiredNetwork()
815                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
816         // Confirm JobScheduler accepts the JobInfo object.
817         mJobScheduler.schedule(ji);
818 
819         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
820                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
821                 .build();
822         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
823         assertNull(ji.getRequiredNetwork());
824         // Confirm JobScheduler accepts the JobInfo object.
825         mJobScheduler.schedule(ji);
826     }
827 
testStorageNotLow()828     public void testStorageNotLow() {
829         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
830                 .setRequiresStorageNotLow(true)
831                 .build();
832         assertTrue(ji.isRequireStorageNotLow());
833         // Confirm JobScheduler accepts the JobInfo object.
834         mJobScheduler.schedule(ji);
835 
836         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
837                 .setRequiresStorageNotLow(false)
838                 .build();
839         assertFalse(ji.isRequireStorageNotLow());
840         // Confirm JobScheduler accepts the JobInfo object.
841         mJobScheduler.schedule(ji);
842     }
843 
844     // TODO(b/315035390): migrate to JUnit4
845     @RequiresFlagsEnabled(Flags.FLAG_JOB_DEBUG_INFO_APIS) // Doesn't work for JUnit3
testTraceTag()846     public void testTraceTag() {
847         if (!isAconfigFlagEnabled(Flags.FLAG_JOB_DEBUG_INFO_APIS)) {
848             return;
849         }
850         // Confirm defaults
851         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent).build();
852         assertNull(ji.getTraceTag());
853         // Confirm JobScheduler accepts the JobInfo object.
854         mJobScheduler.schedule(ji);
855 
856         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent).setTraceTag("tracing").build();
857         assertEquals("tracing", ji.getTraceTag());
858         // Confirm JobScheduler accepts the JobInfo object.
859         mJobScheduler.schedule(ji);
860 
861         // Tag is at the character limit
862         final String maxLengthTraceTag =
863                 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
864                         + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
865         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
866                 .setTraceTag(maxLengthTraceTag)
867                 .build();
868         assertEquals(maxLengthTraceTag, ji.getTraceTag());
869         // Confirm JobScheduler accepts the JobInfo object.
870         mJobScheduler.schedule(ji);
871 
872         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
873                 .setTraceTag(null)
874                 .build();
875         assertNull(null, ji.getTraceTag());
876         // Confirm JobScheduler accepts the JobInfo object.
877         mJobScheduler.schedule(ji);
878 
879         try {
880             new JobInfo.Builder(JOB_ID, kJobServiceComponent).setTraceTag("").build();
881             fail("Successfully built a JobInfo with an empty trace tag");
882         } catch (Exception e) {
883             // Success
884         }
885         try {
886             new JobInfo.Builder(JOB_ID, kJobServiceComponent).setTraceTag("        ").build();
887             fail("Successfully built a JobInfo with a whitespace-only trace tag");
888         } catch (Exception e) {
889             // Success
890         }
891         try {
892             new JobInfo.Builder(JOB_ID, kJobServiceComponent)
893                     .setTraceTag(maxLengthTraceTag + "x").build();
894             fail("Successfully built a JobInfo with a long trace tag");
895         } catch (Exception e) {
896             // Success
897         }
898     }
899 
testTransientExtras()900     public void testTransientExtras() {
901         final Bundle b = new Bundle();
902         b.putBoolean("random_bool", true);
903         assertBuildFails("Successfully built a persisted JobInfo object with transient extras",
904                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
905                         .setPersisted(true)
906                         .setTransientExtras(b));
907 
908         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
909                 .setTransientExtras(b)
910                 .build();
911         assertEquals(b.size(), ji.getTransientExtras().size());
912         for (String key : b.keySet()) {
913             assertEquals(b.get(key), ji.getTransientExtras().get(key));
914         }
915         // Confirm JobScheduler accepts the JobInfo object.
916         mJobScheduler.schedule(ji);
917     }
918 
testTriggerContentMaxDelay()919     public void testTriggerContentMaxDelay() {
920         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
921                 .setTriggerContentMaxDelay(1337)
922                 .build();
923         assertEquals(1337, ji.getTriggerContentMaxDelay());
924         // Confirm JobScheduler accepts the JobInfo object.
925         mJobScheduler.schedule(ji);
926     }
927 
testTriggerContentUpdateDelay()928     public void testTriggerContentUpdateDelay() {
929         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
930                 .setTriggerContentUpdateDelay(1337)
931                 .build();
932         assertEquals(1337, ji.getTriggerContentUpdateDelay());
933         // Confirm JobScheduler accepts the JobInfo object.
934         mJobScheduler.schedule(ji);
935     }
936 
testTriggerContentUri()937     public void testTriggerContentUri() {
938         final Uri u = Uri.parse("content://" + MediaStore.AUTHORITY + "/");
939         final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
940                 u, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
941         assertEquals(u, tcu.getUri());
942         assertEquals(JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS, tcu.getFlags());
943         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
944                 .addTriggerContentUri(tcu)
945                 .build();
946         assertEquals(1, ji.getTriggerContentUris().length);
947         assertEquals(tcu, ji.getTriggerContentUris()[0]);
948         // Confirm JobScheduler accepts the JobInfo object.
949         mJobScheduler.schedule(ji);
950 
951         final Uri u2 = Uri.parse("content://" + ContactsContract.AUTHORITY + "/");
952         final JobInfo.TriggerContentUri tcu2 = new JobInfo.TriggerContentUri(u2, 0);
953         assertEquals(u2, tcu2.getUri());
954         assertEquals(0, tcu2.getFlags());
955         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
956                 .addTriggerContentUri(tcu)
957                 .addTriggerContentUri(tcu2)
958                 .build();
959         assertEquals(2, ji.getTriggerContentUris().length);
960         assertEquals(tcu, ji.getTriggerContentUris()[0]);
961         assertEquals(tcu2, ji.getTriggerContentUris()[1]);
962         // Confirm JobScheduler accepts the JobInfo object.
963         mJobScheduler.schedule(ji);
964     }
965 
testUserInitiatedJob()966     public void testUserInitiatedJob() {
967         // Test all allowed constraints.
968         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
969                 .setUserInitiated(true)
970                 .setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
971                 .setPriority(JobInfo.PRIORITY_MAX)
972                 .setPersisted(true)
973                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
974                 .setRequiresStorageNotLow(true)
975                 .setRequiresBatteryNotLow(true)
976                 .setRequiresCharging(true)
977                 .build();
978         assertTrue(ji.isUserInitiated());
979         // Confirm JobScheduler accepts the JobInfo object.
980         mJobScheduler.schedule(ji);
981 
982         // Confirm default priority for UIJs.
983         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
984                 .setUserInitiated(true)
985                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
986                 .build();
987         assertEquals(JobInfo.PRIORITY_MAX, ji.getPriority());
988         // Confirm JobScheduler accepts the JobInfo object.
989         mJobScheduler.schedule(ji);
990 
991         // Confirm linear backoff allowed
992         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
993                 .setUserInitiated(true)
994                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
995                 .setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_LINEAR)
996                 .build();
997         assertTrue(ji.isUserInitiated());
998         // Confirm JobScheduler accepts the JobInfo object.
999         mJobScheduler.schedule(ji);
1000 
1001         // Test disallowed constraints.
1002         final String failureMessage =
1003                 "Successfully built a user-initiated JobInfo object with disallowed constraints";
1004 
1005         assertBuildFails(failureMessage,
1006                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1007                         .setUserInitiated(true));
1008         assertBuildFails(failureMessage,
1009                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1010                         .setUserInitiated(true)
1011                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1012                         .setMinimumLatency(100));
1013         assertBuildFails(failureMessage,
1014                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1015                         .setUserInitiated(true)
1016                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1017                         .setOverrideDeadline(24 * HOUR_IN_MILLIS));
1018         assertBuildFails(failureMessage,
1019                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1020                         .setUserInitiated(true)
1021                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1022                         .setPeriodic(15 * 60_000));
1023         assertBuildFails(failureMessage,
1024                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1025                         .setUserInitiated(true)
1026                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1027                         .setPriority(JobInfo.PRIORITY_LOW));
1028         assertBuildFails(failureMessage,
1029                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1030                         .setUserInitiated(true)
1031                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1032                         .setPriority(JobInfo.PRIORITY_HIGH));
1033         assertBuildFails(failureMessage,
1034                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1035                         .setUserInitiated(true)
1036                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1037                         .setPriority(JobInfo.PRIORITY_DEFAULT));
1038         assertBuildFails(failureMessage,
1039                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1040                         .setUserInitiated(true)
1041                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1042                         .setPrefetch(true));
1043         assertBuildFails(failureMessage,
1044                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1045                         .setUserInitiated(true)
1046                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1047                         .setRequiresDeviceIdle(true));
1048         assertBuildFails(failureMessage,
1049                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1050                         .setExpedited(true)
1051                         .setUserInitiated(true)
1052                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1053         final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
1054                 Uri.parse("content://" + MediaStore.AUTHORITY + "/"),
1055                 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
1056         assertBuildFails(failureMessage,
1057                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
1058                         .setUserInitiated(true)
1059                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
1060                         .addTriggerContentUri(tcu));
1061     }
1062 
assertBuildFails(String message, JobInfo.Builder builder)1063     private void assertBuildFails(String message, JobInfo.Builder builder) {
1064         try {
1065             builder.build();
1066             fail(message);
1067         } catch (IllegalArgumentException e) {
1068             // Expected
1069         }
1070     }
1071 
assertScheduleFailsWithException( String message, JobInfo jobInfo, Class<? extends Exception> expectedExceptionClass)1072     private void assertScheduleFailsWithException(
1073             String message, JobInfo jobInfo, Class<? extends Exception> expectedExceptionClass) {
1074         try {
1075             mJobScheduler.schedule(jobInfo);
1076             fail(message);
1077         } catch (Exception e) {
1078             if (expectedExceptionClass.isInstance(e)) {
1079                 // Expected
1080             } else {
1081                 fail("Scheduling failed with wrong exception class."
1082                         + " Got " + e.getClass().getSimpleName()
1083                         + ", wanted " + expectedExceptionClass.getSimpleName());
1084             }
1085         }
1086     }
1087 
isAconfigFlagEnabled(String fullFlagName)1088     private boolean isAconfigFlagEnabled(String fullFlagName) {
1089         final String ogValue = SystemUtil.runShellCommand(
1090                 "cmd jobscheduler get-aconfig-flag-state " + fullFlagName).trim();
1091         final boolean enabled = Boolean.parseBoolean(ogValue);
1092         Log.d(TAG, fullFlagName + "=" + ogValue  + " ... enabled=" + enabled);
1093         return enabled;
1094     }
1095 }
1096