Home | History | Annotate | Download | only in firebase
      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