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 }