1 /* 2 * Copyright (C) 2014 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 android.app.job; 18 19 import android.app.Service; 20 import android.content.Intent; 21 import android.os.Handler; 22 import android.os.IBinder; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.RemoteException; 26 import android.util.Log; 27 28 import java.lang.ref.WeakReference; 29 30 /** 31 * Helper for implementing a {@link android.app.Service} that interacts with 32 * {@link JobScheduler}. This is not intended for use by regular applications, but 33 * allows frameworks built on top of the platform to create their own 34 * {@link android.app.Service} that interact with {@link JobScheduler} as well as 35 * add in additional functionality. If you just want to execute jobs normally, you 36 * should instead be looking at {@link JobService}. 37 */ 38 public abstract class JobServiceEngine { 39 private static final String TAG = "JobServiceEngine"; 40 41 /** 42 * Identifier for a message that will result in a call to 43 * {@link #onStartJob(android.app.job.JobParameters)}. 44 */ 45 private static final int MSG_EXECUTE_JOB = 0; 46 /** 47 * Message that will result in a call to {@link #onStopJob(android.app.job.JobParameters)}. 48 */ 49 private static final int MSG_STOP_JOB = 1; 50 /** 51 * Message that the client has completed execution of this job. 52 */ 53 private static final int MSG_JOB_FINISHED = 2; 54 55 private final IJobService mBinder; 56 57 /** 58 * Handler we post jobs to. Responsible for calling into the client logic, and handling the 59 * callback to the system. 60 */ 61 JobHandler mHandler; 62 63 static final class JobInterface extends IJobService.Stub { 64 final WeakReference<JobServiceEngine> mService; 65 JobInterface(JobServiceEngine service)66 JobInterface(JobServiceEngine service) { 67 mService = new WeakReference<>(service); 68 } 69 70 @Override startJob(JobParameters jobParams)71 public void startJob(JobParameters jobParams) throws RemoteException { 72 JobServiceEngine service = mService.get(); 73 if (service != null) { 74 Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams); 75 m.sendToTarget(); 76 } 77 } 78 79 @Override stopJob(JobParameters jobParams)80 public void stopJob(JobParameters jobParams) throws RemoteException { 81 JobServiceEngine service = mService.get(); 82 if (service != null) { 83 Message m = Message.obtain(service.mHandler, MSG_STOP_JOB, jobParams); 84 m.sendToTarget(); 85 } 86 } 87 } 88 89 /** 90 * Runs on application's main thread - callbacks are meant to offboard work to some other 91 * (app-specified) mechanism. 92 * @hide 93 */ 94 class JobHandler extends Handler { JobHandler(Looper looper)95 JobHandler(Looper looper) { 96 super(looper); 97 } 98 99 @Override handleMessage(Message msg)100 public void handleMessage(Message msg) { 101 final JobParameters params = (JobParameters) msg.obj; 102 switch (msg.what) { 103 case MSG_EXECUTE_JOB: 104 try { 105 boolean workOngoing = JobServiceEngine.this.onStartJob(params); 106 ackStartMessage(params, workOngoing); 107 } catch (Exception e) { 108 Log.e(TAG, "Error while executing job: " + params.getJobId()); 109 throw new RuntimeException(e); 110 } 111 break; 112 case MSG_STOP_JOB: 113 try { 114 boolean ret = JobServiceEngine.this.onStopJob(params); 115 ackStopMessage(params, ret); 116 } catch (Exception e) { 117 Log.e(TAG, "Application unable to handle onStopJob.", e); 118 throw new RuntimeException(e); 119 } 120 break; 121 case MSG_JOB_FINISHED: 122 final boolean needsReschedule = (msg.arg2 == 1); 123 IJobCallback callback = params.getCallback(); 124 if (callback != null) { 125 try { 126 callback.jobFinished(params.getJobId(), needsReschedule); 127 } catch (RemoteException e) { 128 Log.e(TAG, "Error reporting job finish to system: binder has gone" + 129 "away."); 130 } 131 } else { 132 Log.e(TAG, "finishJob() called for a nonexistent job id."); 133 } 134 break; 135 default: 136 Log.e(TAG, "Unrecognised message received."); 137 break; 138 } 139 } 140 ackStartMessage(JobParameters params, boolean workOngoing)141 private void ackStartMessage(JobParameters params, boolean workOngoing) { 142 final IJobCallback callback = params.getCallback(); 143 final int jobId = params.getJobId(); 144 if (callback != null) { 145 try { 146 callback.acknowledgeStartMessage(jobId, workOngoing); 147 } catch(RemoteException e) { 148 Log.e(TAG, "System unreachable for starting job."); 149 } 150 } else { 151 if (Log.isLoggable(TAG, Log.DEBUG)) { 152 Log.d(TAG, "Attempting to ack a job that has already been processed."); 153 } 154 } 155 } 156 ackStopMessage(JobParameters params, boolean reschedule)157 private void ackStopMessage(JobParameters params, boolean reschedule) { 158 final IJobCallback callback = params.getCallback(); 159 final int jobId = params.getJobId(); 160 if (callback != null) { 161 try { 162 callback.acknowledgeStopMessage(jobId, reschedule); 163 } catch(RemoteException e) { 164 Log.e(TAG, "System unreachable for stopping job."); 165 } 166 } else { 167 if (Log.isLoggable(TAG, Log.DEBUG)) { 168 Log.d(TAG, "Attempting to ack a job that has already been processed."); 169 } 170 } 171 } 172 } 173 174 /** 175 * Create a new engine, ready for use. 176 * 177 * @param service The {@link Service} that is creating this engine and in which it will run. 178 */ JobServiceEngine(Service service)179 public JobServiceEngine(Service service) { 180 mBinder = new JobInterface(this); 181 mHandler = new JobHandler(service.getMainLooper()); 182 } 183 184 /** 185 * Retrieve the engine's IPC interface that should be returned by 186 * {@link Service#onBind(Intent)}. 187 */ getBinder()188 public final IBinder getBinder() { 189 return mBinder.asBinder(); 190 } 191 192 /** 193 * Engine's report that a job has started. See 194 * {@link JobService#onStartJob(JobParameters) JobService.onStartJob} for more information. 195 */ onStartJob(JobParameters params)196 public abstract boolean onStartJob(JobParameters params); 197 198 /** 199 * Engine's report that a job has stopped. See 200 * {@link JobService#onStopJob(JobParameters) JobService.onStopJob} for more information. 201 */ onStopJob(JobParameters params)202 public abstract boolean onStopJob(JobParameters params); 203 204 /** 205 * Call in to engine to report that a job has finished executing. See 206 * {@link JobService#jobFinished(JobParameters, boolean)} for more information. 207 */ jobFinished(JobParameters params, boolean needsReschedule)208 public void jobFinished(JobParameters params, boolean needsReschedule) { 209 if (params == null) { 210 throw new NullPointerException("params"); 211 } 212 Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params); 213 m.arg2 = needsReschedule ? 1 : 0; 214 m.sendToTarget(); 215 } 216 }