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
17package androidx.work.impl.background.systemjob;
18
19import static android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE;
20
21import android.app.job.JobInfo;
22import android.content.ComponentName;
23import android.content.Context;
24import android.os.Build;
25import android.os.PersistableBundle;
26import android.support.annotation.NonNull;
27import android.support.annotation.RequiresApi;
28import android.support.annotation.RestrictTo;
29import android.support.annotation.VisibleForTesting;
30import android.util.Log;
31
32import androidx.work.BackoffPolicy;
33import androidx.work.Constraints;
34import androidx.work.ContentUriTriggers;
35import androidx.work.NetworkType;
36import androidx.work.impl.WorkManagerImpl;
37import 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)
46class 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)
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     */
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)
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     */
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