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.firebase; 18 19 import android.os.Build; 20 import android.support.annotation.RequiresApi; 21 import android.util.Log; 22 23 import androidx.work.BackoffPolicy; 24 import androidx.work.Constraints; 25 import androidx.work.ContentUriTriggers; 26 import androidx.work.WorkRequest; 27 import androidx.work.impl.model.WorkSpec; 28 29 import com.firebase.jobdispatcher.Constraint; 30 import com.firebase.jobdispatcher.FirebaseJobDispatcher; 31 import com.firebase.jobdispatcher.Job; 32 import com.firebase.jobdispatcher.JobTrigger; 33 import com.firebase.jobdispatcher.Lifetime; 34 import com.firebase.jobdispatcher.ObservedUri; 35 import com.firebase.jobdispatcher.RetryStrategy; 36 import com.firebase.jobdispatcher.Trigger; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.concurrent.TimeUnit; 41 42 /** 43 * Converts a {@link WorkSpec} into a {@link Job}. 44 */ 45 46 class FirebaseJobConverter { 47 private static final String TAG = "FirebaseJobConverter"; 48 private FirebaseJobDispatcher mDispatcher; 49 50 FirebaseJobConverter(FirebaseJobDispatcher dispatcher) { 51 mDispatcher = dispatcher; 52 } 53 54 Job convert(WorkSpec workSpec) { 55 Job.Builder builder = mDispatcher.newJobBuilder() 56 .setService(FirebaseJobService.class) 57 .setTag(workSpec.id) 58 .setLifetime(Lifetime.FOREVER) 59 .setReplaceCurrent(true) 60 .setRetryStrategy(createRetryStrategy(workSpec)) 61 .setConstraints(getConstraints(workSpec)); 62 setExecutionTrigger(builder, workSpec); 63 return builder.build(); 64 } 65 66 private void setExecutionTrigger(Job.Builder builder, WorkSpec workSpec) { 67 if (Build.VERSION.SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) { 68 builder.setTrigger(createContentUriTriggers(workSpec)); 69 } else if (workSpec.isPeriodic()) { 70 builder.setTrigger(createPeriodicTrigger(workSpec)); 71 builder.setRecurring(true); 72 } else { 73 builder.setTrigger(Trigger.NOW); 74 } 75 } 76 77 @RequiresApi(24) 78 private static JobTrigger.ContentUriTrigger createContentUriTriggers(WorkSpec workSpec) { 79 List<ObservedUri> observedUris = new ArrayList<>(); 80 ContentUriTriggers triggers = workSpec.constraints.getContentUriTriggers(); 81 for (ContentUriTriggers.Trigger trigger : triggers) { 82 observedUris.add(convertContentUriTrigger(trigger)); 83 } 84 return Trigger.contentUriTrigger(observedUris); 85 } 86 87 /** 88 * Firebase accepts an execution window with a START and END. 89 * Internally, it sets the flex duration to be END-START and interval duration to be END. 90 * {@link com.firebase.jobdispatcher.GooglePlayJobWriter#writeExecutionWindowTriggerToBundle} 91 */ 92 private static JobTrigger.ExecutionWindowTrigger createPeriodicTrigger(WorkSpec workSpec) { 93 int windowEndSeconds = convertMillisecondsToSeconds(workSpec.intervalDuration); 94 int flexDurationSeconds = convertMillisecondsToSeconds(workSpec.flexDuration); 95 int windowStartSeconds = windowEndSeconds - flexDurationSeconds; 96 97 return Trigger.executionWindow(windowStartSeconds, windowEndSeconds); 98 } 99 100 static int convertMillisecondsToSeconds(long milliseconds) { 101 return (int) TimeUnit.SECONDS.convert(milliseconds, TimeUnit.MILLISECONDS); 102 } 103 104 private RetryStrategy createRetryStrategy(WorkSpec workSpec) { 105 int policy = workSpec.backoffPolicy == BackoffPolicy.LINEAR 106 ? RetryStrategy.RETRY_POLICY_LINEAR : RetryStrategy.RETRY_POLICY_EXPONENTIAL; 107 int initialBackoff = (int) TimeUnit.SECONDS 108 .convert(workSpec.backoffDelayDuration, TimeUnit.MILLISECONDS); 109 int maxBackoff = (int) TimeUnit.SECONDS 110 .convert(WorkRequest.MAX_BACKOFF_MILLIS, TimeUnit.MILLISECONDS); 111 return mDispatcher.newRetryStrategy(policy, initialBackoff, maxBackoff); 112 } 113 114 private static ObservedUri convertContentUriTrigger( 115 ContentUriTriggers.Trigger trigger) { 116 int flag = trigger.shouldTriggerForDescendants() 117 ? ObservedUri.Flags.FLAG_NOTIFY_FOR_DESCENDANTS : 0; 118 return new ObservedUri(trigger.getUri(), flag); 119 } 120 121 private int[] getConstraints(WorkSpec workSpec) { 122 Constraints constraints = workSpec.constraints; 123 List<Integer> mConstraints = new ArrayList<>(); 124 125 if (Build.VERSION.SDK_INT >= 23 && constraints.requiresDeviceIdle()) { 126 mConstraints.add(Constraint.DEVICE_IDLE); 127 } 128 129 if (constraints.requiresCharging()) { 130 mConstraints.add(Constraint.DEVICE_CHARGING); 131 } 132 133 if (constraints.requiresBatteryNotLow()) { 134 Log.w(TAG, 135 "Battery Not Low is not a supported constraint " 136 + "with FirebaseJobDispatcher"); 137 } 138 139 if (constraints.requiresStorageNotLow()) { 140 Log.w(TAG, "Storage Not Low is not a supported constraint " 141 + "with FirebaseJobDispatcher"); 142 } 143 144 switch (constraints.getRequiredNetworkType()) { 145 case NOT_REQUIRED: { 146 // Don't add a constraint. 147 break; 148 } 149 150 case CONNECTED: { 151 mConstraints.add(Constraint.ON_ANY_NETWORK); 152 break; 153 } 154 155 case UNMETERED: { 156 mConstraints.add(Constraint.ON_UNMETERED_NETWORK); 157 break; 158 } 159 160 case NOT_ROAMING: { 161 Log.w(TAG, "Not Roaming Network is not a supported constraint with " 162 + "FirebaseJobDispatcher. Falling back to Any Network constraint."); 163 mConstraints.add(Constraint.ON_ANY_NETWORK); 164 break; 165 } 166 167 case METERED: { 168 Log.w(TAG, "Metered Network is not a supported constraint with " 169 + "FirebaseJobDispatcher. Falling back to Any Network constraint."); 170 mConstraints.add(Constraint.ON_ANY_NETWORK); 171 break; 172 } 173 } 174 175 return toIntArray(mConstraints); 176 } 177 178 private int[] toIntArray(List<Integer> integers) { 179 int size = integers.size(); 180 int[] array = new int[size]; 181 for (int i = 0; i < size; i++) { 182 array[i] = integers.get(i); 183 } 184 return array; 185 } 186 } 187