Home | History | Annotate | Download | only in systemjob
      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 package androidx.work.impl.background.systemjob;
     17 
     18 import android.annotation.TargetApi;
     19 import android.app.job.JobInfo;
     20 import android.app.job.JobScheduler;
     21 import android.content.Context;
     22 import android.os.Build;
     23 import android.os.PersistableBundle;
     24 import android.support.annotation.NonNull;
     25 import android.support.annotation.RestrictTo;
     26 import android.support.annotation.VisibleForTesting;
     27 import android.util.Log;
     28 
     29 import androidx.work.impl.Scheduler;
     30 import androidx.work.impl.WorkDatabase;
     31 import androidx.work.impl.WorkManagerImpl;
     32 import androidx.work.impl.model.SystemIdInfo;
     33 import androidx.work.impl.model.WorkSpec;
     34 import androidx.work.impl.utils.IdGenerator;
     35 
     36 import java.util.List;
     37 
     38 /**
     39  * A class that schedules work using {@link android.app.job.JobScheduler}.
     40  *
     41  * @hide
     42  */
     43 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     44 @TargetApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
     45 public class SystemJobScheduler implements Scheduler {
     46 
     47     private static final String TAG = "SystemJobScheduler";
     48 
     49     private final JobScheduler mJobScheduler;
     50     private final WorkManagerImpl mWorkManager;
     51     private final IdGenerator mIdGenerator;
     52     private final SystemJobInfoConverter mSystemJobInfoConverter;
     53 
     54     public SystemJobScheduler(@NonNull Context context, @NonNull WorkManagerImpl workManager) {
     55         this(context,
     56                 workManager,
     57                 (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE),
     58                 new SystemJobInfoConverter(context));
     59     }
     60 
     61     @VisibleForTesting
     62     public SystemJobScheduler(
     63             Context context,
     64             WorkManagerImpl workManager,
     65             JobScheduler jobScheduler,
     66             SystemJobInfoConverter systemJobInfoConverter) {
     67         mWorkManager = workManager;
     68         mJobScheduler = jobScheduler;
     69         mIdGenerator = new IdGenerator(context);
     70         mSystemJobInfoConverter = systemJobInfoConverter;
     71     }
     72 
     73     @Override
     74     public void schedule(WorkSpec... workSpecs) {
     75         WorkDatabase workDatabase = mWorkManager.getWorkDatabase();
     76 
     77         for (WorkSpec workSpec : workSpecs) {
     78             try {
     79                 workDatabase.beginTransaction();
     80 
     81                 SystemIdInfo info = workDatabase.systemIdInfoDao()
     82                         .getSystemIdInfo(workSpec.id);
     83 
     84                 int jobId = info != null ? info.systemId : mIdGenerator.nextJobSchedulerIdWithRange(
     85                         mWorkManager.getConfiguration().getMinJobSchedulerID(),
     86                         mWorkManager.getConfiguration().getMaxJobSchedulerID());
     87 
     88                 if (info == null) {
     89                     SystemIdInfo newSystemIdInfo = new SystemIdInfo(workSpec.id, jobId);
     90                     mWorkManager.getWorkDatabase()
     91                             .systemIdInfoDao()
     92                             .insertSystemIdInfo(newSystemIdInfo);
     93                 }
     94 
     95                 scheduleInternal(workSpec, jobId);
     96 
     97                 // API 23 JobScheduler only kicked off jobs if there were at least two jobs in the
     98                 // queue, even if the job constraints were met.  This behavior was considered
     99                 // undesirable and later changed in Marshmallow MR1.  To match the new behavior,
    100                 // we will double-schedule jobs on API 23 and de-dupe them
    101                 // in SystemJobService as needed.
    102                 if (Build.VERSION.SDK_INT == 23) {
    103                     int nextJobId = mIdGenerator.nextJobSchedulerIdWithRange(
    104                             mWorkManager.getConfiguration().getMinJobSchedulerID(),
    105                             mWorkManager.getConfiguration().getMaxJobSchedulerID());
    106 
    107                     scheduleInternal(workSpec, nextJobId);
    108                 }
    109 
    110                 workDatabase.setTransactionSuccessful();
    111             } finally {
    112                 workDatabase.endTransaction();
    113             }
    114         }
    115     }
    116 
    117     /**
    118      * Schedules one job with JobScheduler.
    119      *
    120      * @param workSpec The {@link WorkSpec} to schedule with JobScheduler.
    121      */
    122     @VisibleForTesting
    123     public void scheduleInternal(WorkSpec workSpec, int jobId) {
    124         JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec, jobId);
    125         Log.d(TAG, String.format("Scheduling work ID %s Job ID %s", workSpec.id, jobId));
    126         mJobScheduler.schedule(jobInfo);
    127     }
    128 
    129     @Override
    130     public void cancel(@NonNull String workSpecId) {
    131         // Note: despite what the word "pending" and the associated Javadoc might imply, this is
    132         // actually a list of all unfinished jobs that JobScheduler knows about for the current
    133         // process.
    134         List<JobInfo> allJobInfos = mJobScheduler.getAllPendingJobs();
    135         if (allJobInfos != null) {  // Apparently this CAN be null on API 23?
    136             for (JobInfo jobInfo : allJobInfos) {
    137                 if (workSpecId.equals(
    138                         jobInfo.getExtras().getString(SystemJobInfoConverter.EXTRA_WORK_SPEC_ID))) {
    139 
    140                     // Its safe to call this method twice.
    141                     mWorkManager.getWorkDatabase()
    142                             .systemIdInfoDao()
    143                             .removeSystemIdInfo(workSpecId);
    144 
    145                     mJobScheduler.cancel(jobInfo.getId());
    146 
    147                     // See comment in #schedule.
    148                     if (Build.VERSION.SDK_INT != 23) {
    149                         return;
    150                     }
    151                 }
    152             }
    153         }
    154     }
    155 
    156     /**
    157      * Cancels all the jobs owned by {@link androidx.work.WorkManager} in {@link JobScheduler}.
    158      */
    159     public static void jobSchedulerCancelAll(@NonNull Context context) {
    160         JobScheduler jobScheduler = (JobScheduler)
    161                 context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    162 
    163         if (jobScheduler != null) {
    164             List<JobInfo> jobInfos = jobScheduler.getAllPendingJobs();
    165             // Apparently this can be null on API 23?
    166             if (jobInfos != null) {
    167                 for (JobInfo jobInfo : jobInfos) {
    168                     PersistableBundle extras = jobInfo.getExtras();
    169                     // This is a job scheduled by WorkManager.
    170                     if (extras.containsKey(SystemJobInfoConverter.EXTRA_WORK_SPEC_ID)) {
    171                         jobScheduler.cancel(jobInfo.getId());
    172                     }
    173                 }
    174             }
    175         }
    176     }
    177 }
    178