• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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