1 /* 2 * Copyright (C) 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 com.android.voicemail.impl.scheduling; 18 19 import android.annotation.TargetApi; 20 import android.app.job.JobInfo; 21 import android.app.job.JobParameters; 22 import android.app.job.JobScheduler; 23 import android.app.job.JobService; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.SharedPreferences; 27 import android.os.Build.VERSION_CODES; 28 import android.os.Bundle; 29 import android.os.Parcelable; 30 import android.preference.PreferenceManager; 31 import android.support.annotation.MainThread; 32 import com.android.dialer.constants.ScheduledJobIds; 33 import com.android.voicemail.impl.Assert; 34 import com.android.voicemail.impl.VvmLog; 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** A {@link JobService} that will trigger the background execution of {@link TaskExecutor}. */ 39 @TargetApi(VERSION_CODES.O) 40 public class TaskSchedulerJobService extends JobService implements TaskExecutor.Job { 41 42 private static final String TAG = "TaskSchedulerJobService"; 43 44 private static final String EXTRA_TASK_EXTRAS_ARRAY = "extra_task_extras_array"; 45 46 private static final String EXTRA_JOB_ID = "extra_job_id"; 47 48 private static final String EXPECTED_JOB_ID = 49 "com.android.voicemail.impl.scheduling.TaskSchedulerJobService.EXPECTED_JOB_ID"; 50 51 private static final String NEXT_JOB_ID = 52 "com.android.voicemail.impl.scheduling.TaskSchedulerJobService.NEXT_JOB_ID"; 53 54 private JobParameters jobParameters; 55 56 @Override 57 @MainThread onStartJob(JobParameters params)58 public boolean onStartJob(JobParameters params) { 59 int jobId = params.getTransientExtras().getInt(EXTRA_JOB_ID); 60 int expectedJobId = 61 PreferenceManager.getDefaultSharedPreferences(this).getInt(EXPECTED_JOB_ID, 0); 62 if (jobId != expectedJobId) { 63 VvmLog.e( 64 TAG, "Job " + jobId + " is not the last scheduled job " + expectedJobId + ", ignoring"); 65 return false; // nothing more to do. Job not running in background. 66 } 67 VvmLog.i(TAG, "starting " + jobId); 68 jobParameters = params; 69 TaskExecutor.createRunningInstance(this); 70 TaskExecutor.getRunningInstance() 71 .onStartJob( 72 this, 73 getBundleList( 74 jobParameters.getTransientExtras().getParcelableArray(EXTRA_TASK_EXTRAS_ARRAY))); 75 return true /* job still running in background */; 76 } 77 78 @Override 79 @MainThread onStopJob(JobParameters params)80 public boolean onStopJob(JobParameters params) { 81 TaskExecutor.getRunningInstance().onStopJob(); 82 jobParameters = null; 83 return false /* don't reschedule. TaskExecutor service will post a new job */; 84 } 85 86 /** 87 * Schedule a job to run the {@code pendingTasks}. If a job is already scheduled it will be 88 * appended to the back of the queue and the job will be rescheduled. A job may only be scheduled 89 * when the {@link TaskExecutor} is not running ({@link TaskExecutor#getRunningInstance()} 90 * returning {@code null}) 91 * 92 * @param delayMillis delay before running the job. Must be 0 if{@code isNewJob} is true. 93 * @param isNewJob a new job will be forced to run immediately. 94 */ 95 @MainThread scheduleJob( Context context, List<Bundle> pendingTasks, long delayMillis, boolean isNewJob)96 public static void scheduleJob( 97 Context context, List<Bundle> pendingTasks, long delayMillis, boolean isNewJob) { 98 Assert.isMainThread(); 99 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 100 JobInfo pendingJob = jobScheduler.getPendingJob(ScheduledJobIds.VVM_TASK_SCHEDULER_JOB); 101 VvmLog.i(TAG, "scheduling job with " + pendingTasks.size() + " tasks"); 102 if (pendingJob != null) { 103 if (isNewJob) { 104 List<Bundle> existingTasks = 105 getBundleList( 106 pendingJob.getTransientExtras().getParcelableArray(EXTRA_TASK_EXTRAS_ARRAY)); 107 VvmLog.i(TAG, "merging job with " + existingTasks.size() + " existing tasks"); 108 TaskQueue queue = new TaskQueue(); 109 queue.fromBundles(context, existingTasks); 110 for (Bundle pendingTask : pendingTasks) { 111 queue.add(Tasks.createTask(context, pendingTask)); 112 } 113 pendingTasks = queue.toBundles(); 114 } 115 VvmLog.i(TAG, "canceling existing job."); 116 jobScheduler.cancel(ScheduledJobIds.VVM_TASK_SCHEDULER_JOB); 117 } 118 Bundle extras = new Bundle(); 119 int jobId = createJobId(context); 120 extras.putInt(EXTRA_JOB_ID, jobId); 121 PreferenceManager.getDefaultSharedPreferences(context) 122 .edit() 123 .putInt(EXPECTED_JOB_ID, jobId) 124 .apply(); 125 126 extras.putParcelableArray( 127 EXTRA_TASK_EXTRAS_ARRAY, pendingTasks.toArray(new Bundle[pendingTasks.size()])); 128 JobInfo.Builder builder = 129 new JobInfo.Builder( 130 ScheduledJobIds.VVM_TASK_SCHEDULER_JOB, 131 new ComponentName(context, TaskSchedulerJobService.class)) 132 .setTransientExtras(extras) 133 .setMinimumLatency(delayMillis) 134 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 135 if (isNewJob) { 136 Assert.isTrue(delayMillis == 0); 137 builder.setOverrideDeadline(0); 138 VvmLog.i(TAG, "running job instantly."); 139 } 140 jobScheduler.schedule(builder.build()); 141 VvmLog.i(TAG, "job " + jobId + " scheduled"); 142 } 143 144 /** 145 * The system will hold a wakelock when {@link #onStartJob(JobParameters)} is called to ensure the 146 * device will not sleep when the job is still running. Finish the job so the system will release 147 * the wakelock 148 */ 149 @Override finishAsync()150 public void finishAsync() { 151 VvmLog.i(TAG, "finishing job"); 152 jobFinished(jobParameters, false); 153 jobParameters = null; 154 } 155 156 @MainThread 157 @Override isFinished()158 public boolean isFinished() { 159 Assert.isMainThread(); 160 return getSystemService(JobScheduler.class) 161 .getPendingJob(ScheduledJobIds.VVM_TASK_SCHEDULER_JOB) 162 == null; 163 } 164 getBundleList(Parcelable[] parcelables)165 private static List<Bundle> getBundleList(Parcelable[] parcelables) { 166 List<Bundle> result = new ArrayList<>(parcelables.length); 167 for (Parcelable parcelable : parcelables) { 168 result.add((Bundle) parcelable); 169 } 170 return result; 171 } 172 createJobId(Context context)173 private static int createJobId(Context context) { 174 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); 175 int jobId = sharedPreferences.getInt(NEXT_JOB_ID, 0); 176 sharedPreferences.edit().putInt(NEXT_JOB_ID, jobId + 1).apply(); 177 return jobId; 178 } 179 } 180