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