Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit d3623ae

Browse files
authored
Merge pull request #105 from firebase/fix_npe
Fix NPE when jobFinished() was called before onStartJob() returns.
2 parents 1eea1f0 + 21a03b4 commit d3623ae

File tree

4 files changed

+94
-9
lines changed

4 files changed

+94
-9
lines changed

jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobInvocation.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ public Builder setReplaceCurrent(boolean mReplaceCurrent) {
194194
}
195195

196196
/**
197-
* @return true if the tag, the service, and the trigger of provided {@link JobInvocation} have
198-
* the same values.
197+
* @return true if the tag and the service of provided {@link JobInvocation} have the same
198+
* values.
199199
*/
200200
@Override
201201
public boolean equals(Object o) {
@@ -209,15 +209,13 @@ public boolean equals(Object o) {
209209
JobInvocation jobInvocation = (JobInvocation) o;
210210

211211
return mTag.equals(jobInvocation.mTag)
212-
&& mService.equals(jobInvocation.mService)
213-
&& mTrigger.equals(jobInvocation.mTrigger);
212+
&& mService.equals(jobInvocation.mService);
214213
}
215214

216215
@Override
217216
public int hashCode() {
218217
int result = mTag.hashCode();
219218
result = 31 * result + mService.hashCode();
220-
result = 31 * result + mTrigger.hashCode();
221219
return result;
222220
}
223221
}

jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobService.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import android.os.IBinder;
2424
import android.os.Message;
2525
import android.support.annotation.IntDef;
26+
import android.support.annotation.MainThread;
2627
import android.support.annotation.NonNull;
2728
import android.support.annotation.Nullable;
2829
import android.support.annotation.VisibleForTesting;
@@ -84,10 +85,16 @@ public abstract class JobService extends Service {
8485

8586
/**
8687
* The entry point to your Job. Implementations should offload work to another thread of
87-
* execution as soon as possible.
88+
* execution as soon as possible because this runs on the main thread. If work was offloaded,
89+
* call {@link JobService#jobFinished(JobParameters, boolean)} to notify the scheduling service
90+
* that the work is completed.
8891
*
89-
* @return whether there is more work remaining.
92+
* In order to reschedule use {@link JobService#jobFinished(JobParameters, boolean)}.
93+
*
94+
* @return {@code true} if there is more work remaining in the worker thread, {@code false} if
95+
* the job was completed.
9096
*/
97+
@MainThread
9198
public abstract boolean onStartJob(JobParameters job);
9299

93100
/**
@@ -99,8 +106,10 @@ public abstract class JobService extends Service {
99106
* @see com.firebase.jobdispatcher.JobInvocation.Builder#setRetryStrategy(RetryStrategy)
100107
* @see RetryStrategy
101108
*/
109+
@MainThread
102110
public abstract boolean onStopJob(JobParameters job);
103111

112+
@MainThread
104113
void start(JobParameters job, Message msg) {
105114
synchronized (runningJobs) {
106115
if (runningJobs.containsKey(job.getTag())) {
@@ -112,11 +121,15 @@ void start(JobParameters job, Message msg) {
112121

113122
boolean moreWork = onStartJob(job);
114123
if (!moreWork) {
115-
runningJobs.remove(job.getTag()).sendResult(RESULT_SUCCESS);
124+
JobCallback callback = runningJobs.remove(job.getTag());
125+
if (callback != null) {
126+
callback.sendResult(RESULT_SUCCESS);
127+
}
116128
}
117129
}
118130
}
119131

132+
@MainThread
120133
void stop(JobInvocation job) {
121134
synchronized (runningJobs) {
122135
JobCallback jobCallback = runningJobs.remove(job.getTag());
@@ -133,7 +146,8 @@ void stop(JobInvocation job) {
133146
}
134147

135148
/**
136-
* Called to indicate that execution of the provided {@code job} has completed.
149+
* Callback to inform the scheduling driver that you've finished executing. Can be called from
150+
* any thread. When the system receives this message, it will release the wakelock being held.
137151
*
138152
* @param job
139153
* @param needsReschedule whether the job should be rescheduled

jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobInvocationTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,12 @@ public void contract_hashCode_equals() {
7272
assertNotEquals(jobInvocation, jobInvocationNew);
7373
assertNotEquals(jobInvocation.hashCode(), jobInvocationNew.hashCode());
7474
}
75+
76+
@Test
77+
public void contract_hashCode_equals_triggerShouldBeIgnored() {
78+
JobInvocation jobInvocation = builder.build();
79+
JobInvocation periodic = builder.setTrigger(Trigger.executionWindow(0, 1)).build();
80+
assertEquals(jobInvocation, periodic);
81+
assertEquals(jobInvocation.hashCode(), periodic.hashCode());
82+
}
7583
}

jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,71 @@ public void stop_withCallback_done() {
238238
assertEquals(message.arg1, JobService.RESULT_FAIL_RETRY);
239239
}
240240

241+
@Test
242+
public void onStartJob_jobFinishedReschedule() {
243+
// Verify that a retry request from within onStartJob will cause the retry result to be sent
244+
// to the bouncer service's handler, regardless of what value is ultimately returned from
245+
// onStartJob.
246+
JobService reschedulingService = new JobService() {
247+
@Override
248+
public boolean onStartJob(JobParameters job) {
249+
// Reschedules job.
250+
jobFinished(job, true /* retry this job */);
251+
return false;
252+
}
253+
254+
@Override
255+
public boolean onStopJob(JobParameters job) {
256+
return false;
257+
}
258+
};
259+
260+
Job jobSpec = TestUtil.getBuilderWithNoopValidator()
261+
.setTag("tag")
262+
.setService(reschedulingService.getClass())
263+
.setTrigger(Trigger.NOW)
264+
.build();
265+
Handler mock = mock(Handler.class);
266+
Message message = new Message();
267+
message.setTarget(mock);
268+
reschedulingService.start(jobSpec, message);
269+
270+
verify(mock).sendMessage(message);
271+
assertEquals(message.arg1, JobService.RESULT_FAIL_RETRY);
272+
}
273+
274+
@Test
275+
public void onStartJob_jobFinishedNotReschedule() {
276+
// Verify that a termination request from within onStartJob will cause the result to be sent
277+
// to the bouncer service's handler, regardless of what value is ultimately returned from
278+
// onStartJob.
279+
JobService reschedulingService = new JobService() {
280+
@Override
281+
public boolean onStartJob(JobParameters job) {
282+
jobFinished(job, false /* don't retry this job */);
283+
return false;
284+
}
285+
286+
@Override
287+
public boolean onStopJob(JobParameters job) {
288+
return false;
289+
}
290+
};
291+
292+
Job jobSpec = TestUtil.getBuilderWithNoopValidator()
293+
.setTag("tag")
294+
.setService(reschedulingService.getClass())
295+
.setTrigger(Trigger.NOW)
296+
.build();
297+
Handler mock = mock(Handler.class);
298+
Message message = new Message();
299+
message.setTarget(mock);
300+
reschedulingService.start(jobSpec, message);
301+
302+
verify(mock).sendMessage(message);
303+
assertEquals(message.arg1, JobService.RESULT_SUCCESS);
304+
}
305+
241306
public static class ExampleJobService extends JobService {
242307
@Override
243308
public boolean onStartJob(JobParameters job) {

0 commit comments

Comments
 (0)