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 androidx.work.impl.background.systemjob.SystemJobInfoConverterExtKt.setRequiredNetworkRequest; 20 21 import android.app.job.JobInfo; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.net.NetworkCapabilities; 25 import android.net.NetworkRequest; 26 import android.os.Build; 27 import android.os.PersistableBundle; 28 29 import androidx.annotation.RequiresApi; 30 import androidx.annotation.RestrictTo; 31 import androidx.work.BackoffPolicy; 32 import androidx.work.Clock; 33 import androidx.work.Constraints; 34 import androidx.work.Logger; 35 import androidx.work.NetworkType; 36 import androidx.work.impl.WorkManagerImpl; 37 import androidx.work.impl.model.WorkSpec; 38 39 import org.jspecify.annotations.NonNull; 40 41 /** 42 * Converts a {@link WorkSpec} into a JobInfo. 43 * 44 */ 45 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 46 @RequiresApi(api = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) 47 class SystemJobInfoConverter { 48 private static final String TAG = Logger.tagWithPrefix("SystemJobInfoConverter"); 49 50 static final String EXTRA_WORK_SPEC_ID = "EXTRA_WORK_SPEC_ID"; 51 static final String EXTRA_IS_PERIODIC = "EXTRA_IS_PERIODIC"; 52 static final String EXTRA_WORK_SPEC_GENERATION = "EXTRA_WORK_SPEC_GENERATION"; 53 54 private final ComponentName mWorkServiceComponent; 55 private final Clock mClock; 56 private final boolean mMarkImportantWhileForeground; 57 SystemJobInfoConverter(@onNull Context context, Clock clock, boolean markImportantWhileForeground)58 SystemJobInfoConverter(@NonNull Context context, 59 Clock clock, boolean markImportantWhileForeground) { 60 mClock = clock; 61 Context appContext = context.getApplicationContext(); 62 mWorkServiceComponent = new ComponentName(appContext, SystemJobService.class); 63 mMarkImportantWhileForeground = markImportantWhileForeground; 64 } 65 66 /** 67 * Converts a {@link WorkSpec} into a {@link JobInfo}. 68 * 69 * Note: All {@link JobInfo} are set to persist on reboot. 70 * 71 * @param workSpec The {@link WorkSpec} to convert 72 * @param jobId The {@code jobId} to use. This is useful when de-duping jobs on reschedule. 73 * @return The {@link JobInfo} representing the same information as the {@link WorkSpec} 74 */ convert(WorkSpec workSpec, int jobId)75 JobInfo convert(WorkSpec workSpec, int jobId) { 76 Constraints constraints = workSpec.constraints; 77 PersistableBundle extras = new PersistableBundle(); 78 extras.putString(EXTRA_WORK_SPEC_ID, workSpec.id); 79 extras.putInt(EXTRA_WORK_SPEC_GENERATION, workSpec.getGeneration()); 80 extras.putBoolean(EXTRA_IS_PERIODIC, workSpec.isPeriodic()); 81 JobInfo.Builder builder = new JobInfo.Builder(jobId, mWorkServiceComponent) 82 .setRequiresCharging(constraints.requiresCharging()) 83 .setRequiresDeviceIdle(constraints.requiresDeviceIdle()) 84 .setExtras(extras); 85 NetworkRequest networkRequest = constraints.getRequiredNetworkRequest(); 86 if (Build.VERSION.SDK_INT >= 28 && networkRequest != null) { 87 setRequiredNetworkRequest(builder, networkRequest); 88 } else { 89 setRequiredNetwork(builder, constraints.getRequiredNetworkType()); 90 } 91 92 if (!constraints.requiresDeviceIdle()) { 93 // Device Idle and Backoff Criteria cannot be set together 94 int backoffPolicy = workSpec.backoffPolicy == BackoffPolicy.LINEAR 95 ? JobInfo.BACKOFF_POLICY_LINEAR : JobInfo.BACKOFF_POLICY_EXPONENTIAL; 96 builder.setBackoffCriteria(workSpec.backoffDelayDuration, backoffPolicy); 97 } 98 99 long nextRunTime = workSpec.calculateNextRunTime(); 100 long now = mClock.currentTimeMillis(); 101 long offset = Math.max(nextRunTime - now, 0); 102 103 if (Build.VERSION.SDK_INT <= 28) { 104 // Before API 29, Jobs needed at least one constraint. Therefore before API 29 we 105 // always setMinimumLatency to make sure we have at least one constraint. 106 // See aosp/5434530 & b/6771687 107 builder.setMinimumLatency(offset); 108 } else { 109 if (offset > 0) { 110 // Only set a minimum latency when applicable. 111 builder.setMinimumLatency(offset); 112 } else if (!workSpec.expedited && mMarkImportantWhileForeground) { 113 // Only set this if the workSpec is not expedited. 114 builder.setImportantWhileForeground(true); 115 } 116 } 117 118 if (Build.VERSION.SDK_INT >= 24 && constraints.hasContentUriTriggers()) { 119 //noinspection ConstantConditions 120 for (Constraints.ContentUriTrigger trigger : constraints.getContentUriTriggers()) { 121 builder.addTriggerContentUri(convertContentUriTrigger(trigger)); 122 } 123 builder.setTriggerContentUpdateDelay(constraints.getContentTriggerUpdateDelayMillis()); 124 builder.setTriggerContentMaxDelay(constraints.getContentTriggerMaxDelayMillis()); 125 } 126 127 // We don't want to persist these jobs because we reschedule these jobs on BOOT_COMPLETED. 128 // That way ForceStopRunnable correctly reschedules Jobs when necessary. 129 builder.setPersisted(false); 130 if (Build.VERSION.SDK_INT >= 26) { 131 builder.setRequiresBatteryNotLow(constraints.requiresBatteryNotLow()); 132 builder.setRequiresStorageNotLow(constraints.requiresStorageNotLow()); 133 } 134 // Retries cannot be expedited jobs, given they will occur at some point in the future. 135 boolean isRetry = workSpec.runAttemptCount > 0; 136 boolean isDelayed = offset > 0; 137 if (Build.VERSION.SDK_INT >= 31 && workSpec.expedited && !isRetry && !isDelayed) { 138 //noinspection NewApi 139 builder.setExpedited(true); 140 } 141 if (Build.VERSION.SDK_INT >= 35) { 142 // Add a trace tag that shows the actual worker running. 143 String traceTag = workSpec.getTraceTag(); 144 if (traceTag != null) { 145 builder.setTraceTag(traceTag); 146 } 147 } 148 return builder.build(); 149 } 150 151 @RequiresApi(24) convertContentUriTrigger( Constraints.ContentUriTrigger trigger)152 private static JobInfo.TriggerContentUri convertContentUriTrigger( 153 Constraints.ContentUriTrigger trigger) { 154 int flag = trigger.isTriggeredForDescendants() 155 ? JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS : 0; 156 return new JobInfo.TriggerContentUri(trigger.getUri(), flag); 157 } 158 159 /** 160 * Adds the required network capabilities on the {@link JobInfo.Builder} instance. 161 * 162 * @param builder The instance of {@link JobInfo.Builder}. 163 * @param networkType The {@link NetworkType} instance. 164 */ setRequiredNetwork( JobInfo.@onNull Builder builder, @NonNull NetworkType networkType)165 static void setRequiredNetwork( 166 JobInfo.@NonNull Builder builder, 167 @NonNull NetworkType networkType) { 168 169 if (Build.VERSION.SDK_INT >= 30 && networkType == NetworkType.TEMPORARILY_UNMETERED) { 170 NetworkRequest networkRequest = new NetworkRequest.Builder() 171 .addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED) 172 .build(); 173 174 builder.setRequiredNetwork(networkRequest); 175 } else { 176 builder.setRequiredNetworkType(convertNetworkType(networkType)); 177 } 178 } 179 180 /** 181 * Converts {@link NetworkType} into {@link JobInfo}'s network values. 182 * 183 * @param networkType The {@link NetworkType} network type 184 * @return The {@link JobInfo} network type 185 */ 186 @SuppressWarnings("MissingCasesInEnumSwitch") convertNetworkType(NetworkType networkType)187 static int convertNetworkType(NetworkType networkType) { 188 switch (networkType) { 189 case NOT_REQUIRED: 190 return JobInfo.NETWORK_TYPE_NONE; 191 case CONNECTED: 192 return JobInfo.NETWORK_TYPE_ANY; 193 case UNMETERED: 194 return JobInfo.NETWORK_TYPE_UNMETERED; 195 case NOT_ROAMING: 196 if (Build.VERSION.SDK_INT >= 24) { 197 return JobInfo.NETWORK_TYPE_NOT_ROAMING; 198 } 199 break; 200 case METERED: 201 if (Build.VERSION.SDK_INT >= 26) { 202 return JobInfo.NETWORK_TYPE_METERED; 203 } 204 break; 205 } 206 Logger.get().debug(TAG, "API version too low. Cannot convert network type value " + networkType); 207 return JobInfo.NETWORK_TYPE_ANY; 208 } 209 } 210