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.support.annotation.VisibleForTesting.PACKAGE_PRIVATE;
20 
21 import android.app.job.JobInfo;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.os.Build;
25 import android.os.PersistableBundle;
26 import android.support.annotation.NonNull;
27 import android.support.annotation.RequiresApi;
28 import android.support.annotation.RestrictTo;
29 import android.support.annotation.VisibleForTesting;
30 import android.util.Log;
31 
32 import androidx.work.BackoffPolicy;
33 import androidx.work.Constraints;
34 import androidx.work.ContentUriTriggers;
35 import androidx.work.NetworkType;
36 import androidx.work.impl.WorkManagerImpl;
37 import androidx.work.impl.model.WorkSpec;
38 
39 /**
40  * Converts a {@link WorkSpec} into a JobInfo.
41  *
42  * @hide
43  */
44 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
45 @RequiresApi(api = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
46 class SystemJobInfoConverter {
47     private static final String TAG = "SystemJobInfoConverter";
48 
49     static final String EXTRA_WORK_SPEC_ID = "EXTRA_WORK_SPEC_ID";
50     static final String EXTRA_IS_PERIODIC = "EXTRA_IS_PERIODIC";
51 
52     private final ComponentName mWorkServiceComponent;
53 
54     @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
SystemJobInfoConverter(@onNull Context context)55     SystemJobInfoConverter(@NonNull Context context) {
56         Context appContext = context.getApplicationContext();
57         mWorkServiceComponent = new ComponentName(appContext, SystemJobService.class);
58     }
59 
60     /**
61      * Converts a {@link WorkSpec} into a {@link JobInfo}.
62      *
63      * Note: All {@link JobInfo} are set to persist on reboot.
64      *
65      * @param workSpec The {@link WorkSpec} to convert
66      * @param jobId The {@code jobId} to use. This is useful when de-duping jobs on reschedule.
67      * @return The {@link JobInfo} representing the same information as the {@link WorkSpec}
68      */
convert(WorkSpec workSpec, int jobId)69     JobInfo convert(WorkSpec workSpec, int jobId) {
70         Constraints constraints = workSpec.constraints;
71         // TODO(janclarin): Support newer required network types if unsupported by API version.
72         int jobInfoNetworkType = convertNetworkType(constraints.getRequiredNetworkType());
73         PersistableBundle extras = new PersistableBundle();
74         extras.putString(EXTRA_WORK_SPEC_ID, workSpec.id);
75         extras.putBoolean(EXTRA_IS_PERIODIC, workSpec.isPeriodic());
76         JobInfo.Builder builder = new JobInfo.Builder(jobId, mWorkServiceComponent)
77                 .setRequiredNetworkType(jobInfoNetworkType)
78                 .setRequiresCharging(constraints.requiresCharging())
79                 .setRequiresDeviceIdle(constraints.requiresDeviceIdle())
80                 .setExtras(extras);
81 
82         if (!constraints.requiresDeviceIdle()) {
83             // Device Idle and Backoff Criteria cannot be set together
84             int backoffPolicy = workSpec.backoffPolicy == BackoffPolicy.LINEAR
85                     ? JobInfo.BACKOFF_POLICY_LINEAR : JobInfo.BACKOFF_POLICY_EXPONENTIAL;
86             builder.setBackoffCriteria(workSpec.backoffDelayDuration, backoffPolicy);
87         }
88 
89         if (workSpec.isPeriodic()) {
90             if (Build.VERSION.SDK_INT >= 24) {
91                 builder.setPeriodic(workSpec.intervalDuration, workSpec.flexDuration);
92             } else {
93                 Log.d(TAG,
94                         "Flex duration is currently not supported before API 24. Ignoring.");
95                 builder.setPeriodic(workSpec.intervalDuration);
96             }
97         } else {
98             // Even if a WorkRequest has no constraints, setMinimumLatency(0) still needs to be
99             // called due to an issue in JobInfo.Builder#build and JobInfo with no constraints. See
100             // b/67716867.
101             builder.setMinimumLatency(workSpec.initialDelay);
102         }
103 
104         if (Build.VERSION.SDK_INT >= 24 && constraints.hasContentUriTriggers()) {
105             for (ContentUriTriggers.Trigger trigger : constraints.getContentUriTriggers()) {
106                 builder.addTriggerContentUri(convertContentUriTrigger(trigger));
107             }
108         }
109 
110         // We don't want to persist these jobs because we reschedule these jobs on BOOT_COMPLETED.
111         // That way ForceStopRunnable correctly reschedules Jobs when necessary.
112         builder.setPersisted(false);
113         if (Build.VERSION.SDK_INT >= 26) {
114             builder.setRequiresBatteryNotLow(constraints.requiresBatteryNotLow());
115             builder.setRequiresStorageNotLow(constraints.requiresStorageNotLow());
116         }
117         return builder.build();
118     }
119 
120     @RequiresApi(24)
convertContentUriTrigger( ContentUriTriggers.Trigger trigger)121     private static JobInfo.TriggerContentUri convertContentUriTrigger(
122             ContentUriTriggers.Trigger trigger) {
123         int flag = trigger.shouldTriggerForDescendants()
124                 ? JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS : 0;
125         return new JobInfo.TriggerContentUri(trigger.getUri(), flag);
126     }
127 
128     /**
129      * Converts {@link NetworkType} into {@link JobInfo}'s network values.
130      *
131      * @param networkType The {@link NetworkType} network type
132      * @return The {@link JobInfo} network type
133      */
convertNetworkType(NetworkType networkType)134     static int convertNetworkType(NetworkType networkType) {
135         switch(networkType) {
136             case NOT_REQUIRED:
137                 return JobInfo.NETWORK_TYPE_NONE;
138             case CONNECTED:
139                 return JobInfo.NETWORK_TYPE_ANY;
140             case UNMETERED:
141                 return JobInfo.NETWORK_TYPE_UNMETERED;
142             case NOT_ROAMING:
143                 if (Build.VERSION.SDK_INT >= 24) {
144                     return JobInfo.NETWORK_TYPE_NOT_ROAMING;
145                 }
146                 break;
147             case METERED:
148                 if (Build.VERSION.SDK_INT >= 26) {
149                     return JobInfo.NETWORK_TYPE_METERED;
150                 }
151                 break;
152         }
153         Log.d(TAG, String.format(
154                 "API version too low. Cannot convert network type value %s", networkType));
155         return JobInfo.NETWORK_TYPE_ANY;
156     }
157 }
158