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.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.ActivityManager; 23 import android.app.usage.UsageStatsManager; 24 import android.compat.annotation.UnsupportedAppUsage; 25 import android.content.ClipData; 26 import android.content.pm.PackageManager; 27 import android.net.Network; 28 import android.net.NetworkRequest; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.os.PersistableBundle; 35 import android.os.RemoteException; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 40 /** 41 * Contains the parameters used to configure/identify your job. You do not create this object 42 * yourself, instead it is handed in to your application by the System. 43 */ 44 public class JobParameters implements Parcelable { 45 46 /** @hide */ 47 public static final int INTERNAL_STOP_REASON_CANCELED = 48 JobProtoEnums.INTERNAL_STOP_REASON_CANCELLED; // 0. 49 /** @hide */ 50 public static final int INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED = 51 JobProtoEnums.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED; // 1. 52 /** @hide */ 53 public static final int INTERNAL_STOP_REASON_PREEMPT = 54 JobProtoEnums.INTERNAL_STOP_REASON_PREEMPT; // 2. 55 /** 56 * The job ran for at least its minimum execution limit. 57 * @hide 58 */ 59 public static final int INTERNAL_STOP_REASON_TIMEOUT = 60 JobProtoEnums.INTERNAL_STOP_REASON_TIMEOUT; // 3. 61 /** @hide */ 62 public static final int INTERNAL_STOP_REASON_DEVICE_IDLE = 63 JobProtoEnums.INTERNAL_STOP_REASON_DEVICE_IDLE; // 4. 64 /** @hide */ 65 public static final int INTERNAL_STOP_REASON_DEVICE_THERMAL = 66 JobProtoEnums.INTERNAL_STOP_REASON_DEVICE_THERMAL; // 5. 67 /** 68 * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} 69 * bucket. 70 * 71 * @hide 72 */ 73 public static final int INTERNAL_STOP_REASON_RESTRICTED_BUCKET = 74 JobProtoEnums.INTERNAL_STOP_REASON_RESTRICTED_BUCKET; // 6. 75 /** 76 * The app was uninstalled. 77 * @hide 78 */ 79 public static final int INTERNAL_STOP_REASON_UNINSTALL = 80 JobProtoEnums.INTERNAL_STOP_REASON_UNINSTALL; // 7. 81 /** 82 * The app's data was cleared. 83 * @hide 84 */ 85 public static final int INTERNAL_STOP_REASON_DATA_CLEARED = 86 JobProtoEnums.INTERNAL_STOP_REASON_DATA_CLEARED; // 8. 87 /** 88 * @hide 89 */ 90 public static final int INTERNAL_STOP_REASON_RTC_UPDATED = 91 JobProtoEnums.INTERNAL_STOP_REASON_RTC_UPDATED; // 9. 92 /** 93 * The app called jobFinished() on its own. 94 * @hide 95 */ 96 public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH = 97 JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10. 98 99 /** 100 * All the stop reason codes. This should be regarded as an immutable array at runtime. 101 * 102 * Note the order of these values will affect "dumpsys batterystats", and we do not want to 103 * change the order of existing fields, so adding new fields is okay but do not remove or 104 * change existing fields. When deprecating a field, just replace that with "-1" in this array. 105 * 106 * @hide 107 */ 108 public static final int[] JOB_STOP_REASON_CODES = { 109 INTERNAL_STOP_REASON_CANCELED, 110 INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 111 INTERNAL_STOP_REASON_PREEMPT, 112 INTERNAL_STOP_REASON_TIMEOUT, 113 INTERNAL_STOP_REASON_DEVICE_IDLE, 114 INTERNAL_STOP_REASON_DEVICE_THERMAL, 115 INTERNAL_STOP_REASON_RESTRICTED_BUCKET, 116 INTERNAL_STOP_REASON_UNINSTALL, 117 INTERNAL_STOP_REASON_DATA_CLEARED, 118 INTERNAL_STOP_REASON_RTC_UPDATED, 119 INTERNAL_STOP_REASON_SUCCESSFUL_FINISH, 120 }; 121 122 /** 123 * @hide 124 */ 125 // TODO(142420609): make it @SystemApi for mainline 126 @NonNull getInternalReasonCodeDescription(int reasonCode)127 public static String getInternalReasonCodeDescription(int reasonCode) { 128 switch (reasonCode) { 129 case INTERNAL_STOP_REASON_CANCELED: return "canceled"; 130 case INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints"; 131 case INTERNAL_STOP_REASON_PREEMPT: return "preempt"; 132 case INTERNAL_STOP_REASON_TIMEOUT: return "timeout"; 133 case INTERNAL_STOP_REASON_DEVICE_IDLE: return "device_idle"; 134 case INTERNAL_STOP_REASON_DEVICE_THERMAL: return "thermal"; 135 case INTERNAL_STOP_REASON_RESTRICTED_BUCKET: return "restricted_bucket"; 136 case INTERNAL_STOP_REASON_UNINSTALL: return "uninstall"; 137 case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared"; 138 case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated"; 139 case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish"; 140 default: return "unknown:" + reasonCode; 141 } 142 } 143 144 /** @hide */ 145 @NonNull getJobStopReasonCodes()146 public static int[] getJobStopReasonCodes() { 147 return JOB_STOP_REASON_CODES; 148 } 149 150 /** 151 * There is no reason the job is stopped. This is the value returned from the JobParameters 152 * object passed to {@link JobService#onStartJob(JobParameters)}. 153 */ 154 public static final int STOP_REASON_UNDEFINED = 0; 155 /** 156 * The job was cancelled directly by the app, either by calling 157 * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a 158 * new job with the same job ID. 159 */ 160 public static final int STOP_REASON_CANCELLED_BY_APP = 1; 161 /** The job was stopped to run a higher priority job of the app. */ 162 public static final int STOP_REASON_PREEMPT = 2; 163 /** 164 * The job used up its maximum execution time and timed out. Each individual job has a maximum 165 * execution time limit, regardless of how much total quota the app has. See the note on 166 * {@link JobScheduler} for the execution time limits. 167 */ 168 public static final int STOP_REASON_TIMEOUT = 3; 169 /** 170 * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this 171 * job. 172 */ 173 public static final int STOP_REASON_DEVICE_STATE = 4; 174 /** 175 * The requested battery-not-low constraint is no longer satisfied. 176 * 177 * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean) 178 */ 179 public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; 180 /** 181 * The requested charging constraint is no longer satisfied. 182 * 183 * @see JobInfo.Builder#setRequiresCharging(boolean) 184 */ 185 public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; 186 /** 187 * The requested connectivity constraint is no longer satisfied. 188 * 189 * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) 190 * @see JobInfo.Builder#setRequiredNetworkType(int) 191 */ 192 public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; 193 /** 194 * The requested idle constraint is no longer satisfied. 195 * 196 * @see JobInfo.Builder#setRequiresDeviceIdle(boolean) 197 */ 198 public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; 199 /** 200 * The requested storage-not-low constraint is no longer satisfied. 201 * 202 * @see JobInfo.Builder#setRequiresStorageNotLow(boolean) 203 */ 204 public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; 205 /** 206 * The app has consumed all of its current quota. Each app is assigned a quota of how much 207 * it can run jobs within a certain time frame. The quota is informed, in part, by app standby 208 * buckets. Once an app has used up all of its quota, it won't be able to start jobs until 209 * quota is replenished, is changed, or is temporarily not applied. 210 * 211 * @see UsageStatsManager#getAppStandbyBucket() 212 */ 213 public static final int STOP_REASON_QUOTA = 10; 214 /** 215 * The app is restricted from running in the background. 216 * 217 * @see ActivityManager#isBackgroundRestricted() 218 * @see PackageManager#isInstantApp() 219 */ 220 public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; 221 /** 222 * The current standby bucket requires that the job stop now. 223 * 224 * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED 225 */ 226 public static final int STOP_REASON_APP_STANDBY = 12; 227 /** 228 * The user stopped the job. This can happen either through force-stop, adb shell commands, 229 * or uninstalling. 230 */ 231 public static final int STOP_REASON_USER = 13; 232 /** The system is doing some processing that requires stopping this job. */ 233 public static final int STOP_REASON_SYSTEM_PROCESSING = 14; 234 235 /** @hide */ 236 @IntDef(prefix = {"STOP_REASON_"}, value = { 237 STOP_REASON_UNDEFINED, 238 STOP_REASON_CANCELLED_BY_APP, 239 STOP_REASON_PREEMPT, 240 STOP_REASON_TIMEOUT, 241 STOP_REASON_DEVICE_STATE, 242 STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW, 243 STOP_REASON_CONSTRAINT_CHARGING, 244 STOP_REASON_CONSTRAINT_CONNECTIVITY, 245 STOP_REASON_CONSTRAINT_DEVICE_IDLE, 246 STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW, 247 STOP_REASON_QUOTA, 248 STOP_REASON_BACKGROUND_RESTRICTION, 249 STOP_REASON_APP_STANDBY, 250 STOP_REASON_USER, 251 STOP_REASON_SYSTEM_PROCESSING, 252 }) 253 @Retention(RetentionPolicy.SOURCE) 254 public @interface StopReason { 255 } 256 257 @UnsupportedAppUsage 258 private final int jobId; 259 private final PersistableBundle extras; 260 private final Bundle transientExtras; 261 private final ClipData clipData; 262 private final int clipGrantFlags; 263 @UnsupportedAppUsage 264 private final IBinder callback; 265 private final boolean overrideDeadlineExpired; 266 private final boolean mIsExpedited; 267 private final Uri[] mTriggeredContentUris; 268 private final String[] mTriggeredContentAuthorities; 269 private final Network network; 270 271 private int mStopReason = STOP_REASON_UNDEFINED; 272 private int mInternalStopReason; // Default value is REASON_CANCELED 273 private String debugStopReason; // Human readable stop reason for debugging. 274 275 /** @hide */ JobParameters(IBinder callback, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, boolean isExpedited, Uri[] triggeredContentUris, String[] triggeredContentAuthorities, Network network)276 public JobParameters(IBinder callback, int jobId, PersistableBundle extras, 277 Bundle transientExtras, ClipData clipData, int clipGrantFlags, 278 boolean overrideDeadlineExpired, boolean isExpedited, Uri[] triggeredContentUris, 279 String[] triggeredContentAuthorities, Network network) { 280 this.jobId = jobId; 281 this.extras = extras; 282 this.transientExtras = transientExtras; 283 this.clipData = clipData; 284 this.clipGrantFlags = clipGrantFlags; 285 this.callback = callback; 286 this.overrideDeadlineExpired = overrideDeadlineExpired; 287 this.mIsExpedited = isExpedited; 288 this.mTriggeredContentUris = triggeredContentUris; 289 this.mTriggeredContentAuthorities = triggeredContentAuthorities; 290 this.network = network; 291 } 292 293 /** 294 * @return The unique id of this job, specified at creation time. 295 */ getJobId()296 public int getJobId() { 297 return jobId; 298 } 299 300 /** 301 * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will 302 * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not 303 * yet been called. 304 */ 305 @StopReason getStopReason()306 public int getStopReason() { 307 return mStopReason; 308 } 309 310 /** @hide */ getInternalStopReasonCode()311 public int getInternalStopReasonCode() { 312 return mInternalStopReason; 313 } 314 315 /** 316 * Reason onStopJob() was called on this job. 317 * 318 * @hide 319 */ getDebugStopReason()320 public String getDebugStopReason() { 321 return debugStopReason; 322 } 323 324 /** 325 * @return The extras you passed in when constructing this job with 326 * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will 327 * never be null. If you did not set any extras this will be an empty bundle. 328 */ getExtras()329 public @NonNull PersistableBundle getExtras() { 330 return extras; 331 } 332 333 /** 334 * @return The transient extras you passed in when constructing this job with 335 * {@link android.app.job.JobInfo.Builder#setTransientExtras(android.os.Bundle)}. This will 336 * never be null. If you did not set any extras this will be an empty bundle. 337 */ getTransientExtras()338 public @NonNull Bundle getTransientExtras() { 339 return transientExtras; 340 } 341 342 /** 343 * @return The clip you passed in when constructing this job with 344 * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null 345 * if it was not set. 346 */ getClipData()347 public @Nullable ClipData getClipData() { 348 return clipData; 349 } 350 351 /** 352 * @return The clip grant flags you passed in when constructing this job with 353 * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be 0 354 * if it was not set. 355 */ getClipGrantFlags()356 public int getClipGrantFlags() { 357 return clipGrantFlags; 358 } 359 360 /** 361 * @return Whether this job is running as an expedited job or not. A job is guaranteed to have 362 * all expedited job guarantees for the duration of the job execution if this returns 363 * {@code true}. This will return {@code false} if the job that wasn't requested to run as a 364 * expedited job, or if it was requested to run as an expedited job but the app didn't have 365 * any remaining expedited job quota at the time of execution. 366 * 367 * @see JobInfo.Builder#setExpedited(boolean) 368 */ isExpeditedJob()369 public boolean isExpeditedJob() { 370 return mIsExpedited; 371 } 372 373 /** 374 * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this 375 * provides an easy way to tell whether the job is being executed due to the deadline 376 * expiring. Note: If the job is running because its deadline expired, it implies that its 377 * constraints will not be met. 378 */ isOverrideDeadlineExpired()379 public boolean isOverrideDeadlineExpired() { 380 return overrideDeadlineExpired; 381 } 382 383 /** 384 * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this 385 * reports which URIs have triggered the job. This will be null if either no URIs have 386 * triggered it (it went off due to a deadline or other reason), or the number of changed 387 * URIs is too large to report. Whether or not the number of URIs is too large, you can 388 * always use {@link #getTriggeredContentAuthorities()} to determine whether the job was 389 * triggered due to any content changes and the authorities they are associated with. 390 */ getTriggeredContentUris()391 public @Nullable Uri[] getTriggeredContentUris() { 392 return mTriggeredContentUris; 393 } 394 395 /** 396 * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this 397 * reports which content authorities have triggered the job. It will only be null if no 398 * authorities have triggered it -- that is, the job executed for some other reason, such 399 * as a deadline expiring. If this is non-null, you can use {@link #getTriggeredContentUris()} 400 * to retrieve the details of which URIs changed (as long as that has not exceeded the maximum 401 * number it can reported). 402 */ getTriggeredContentAuthorities()403 public @Nullable String[] getTriggeredContentAuthorities() { 404 return mTriggeredContentAuthorities; 405 } 406 407 /** 408 * Return the network that should be used to perform any network requests 409 * for this job. 410 * <p> 411 * Devices may have multiple active network connections simultaneously, or 412 * they may not have a default network route at all. To correctly handle all 413 * situations like this, your job should always use the network returned by 414 * this method instead of implicitly using the default network route. 415 * <p> 416 * Note that the system may relax the constraints you originally requested, 417 * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over 418 * a metered network when there is a surplus of metered data available. 419 * 420 * @return the network that should be used to perform any network requests 421 * for this job, or {@code null} if this job didn't set any required 422 * network type or if the job executed when there was no available network to use. 423 * @see JobInfo.Builder#setRequiredNetworkType(int) 424 * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) 425 */ getNetwork()426 public @Nullable Network getNetwork() { 427 return network; 428 } 429 430 /** 431 * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their 432 * currently running job. Calling this method when there is no more work available and all 433 * previously dequeued work has been completed will result in the system taking care of 434 * stopping the job for you -- 435 * you should not call {@link JobService#jobFinished(JobParameters, boolean)} yourself 436 * (otherwise you risk losing an upcoming JobWorkItem that is being enqueued at the same time). 437 * 438 * <p>Once you are done with the {@link JobWorkItem} returned by this method, you must call 439 * {@link #completeWork(JobWorkItem)} with it to inform the system that you are done 440 * executing the work. The job will not be finished until all dequeued work has been 441 * completed. You do not, however, have to complete each returned work item before deqeueing 442 * the next one -- you can use {@link #dequeueWork()} multiple times before completing 443 * previous work if you want to process work in parallel, and you can complete the work 444 * in whatever order you want.</p> 445 * 446 * <p>If the job runs to the end of its available time period before all work has been 447 * completed, it will stop as normal. You should return true from 448 * {@link JobService#onStopJob(JobParameters)} in order to have the job rescheduled, and by 449 * doing so any pending as well as remaining uncompleted work will be re-queued 450 * for the next time the job runs.</p> 451 * 452 * <p>This example shows how to construct a JobService that will serially dequeue and 453 * process work that is available for it:</p> 454 * 455 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java 456 * service} 457 * 458 * @return Returns a new {@link JobWorkItem} if there is one pending, otherwise null. 459 * If null is returned, the system will also stop the job if all work has also been completed. 460 * (This means that for correct operation, you must always call dequeueWork() after you have 461 * completed other work, to check either for more work or allow the system to stop the job.) 462 */ dequeueWork()463 public @Nullable JobWorkItem dequeueWork() { 464 try { 465 return getCallback().dequeueWork(getJobId()); 466 } catch (RemoteException e) { 467 throw e.rethrowFromSystemServer(); 468 } 469 } 470 471 /** 472 * Report the completion of executing a {@link JobWorkItem} previously returned by 473 * {@link #dequeueWork()}. This tells the system you are done with the 474 * work associated with that item, so it will not be returned again. Note that if this 475 * is the last work in the queue, completing it here will <em>not</em> finish the overall 476 * job -- for that to happen, you still need to call {@link #dequeueWork()} 477 * again. 478 * 479 * <p>If you are enqueueing work into a job, you must call this method for each piece 480 * of work you process. Do <em>not</em> call 481 * {@link JobService#jobFinished(JobParameters, boolean)} 482 * or else you can lose work in your queue.</p> 483 * 484 * @param work The work you have completed processing, as previously returned by 485 * {@link #dequeueWork()} 486 */ completeWork(@onNull JobWorkItem work)487 public void completeWork(@NonNull JobWorkItem work) { 488 try { 489 if (!getCallback().completeWork(getJobId(), work.getWorkId())) { 490 throw new IllegalArgumentException("Given work is not active: " + work); 491 } 492 } catch (RemoteException e) { 493 throw e.rethrowFromSystemServer(); 494 } 495 } 496 497 /** @hide */ 498 @UnsupportedAppUsage getCallback()499 public IJobCallback getCallback() { 500 return IJobCallback.Stub.asInterface(callback); 501 } 502 JobParameters(Parcel in)503 private JobParameters(Parcel in) { 504 jobId = in.readInt(); 505 extras = in.readPersistableBundle(); 506 transientExtras = in.readBundle(); 507 if (in.readInt() != 0) { 508 clipData = ClipData.CREATOR.createFromParcel(in); 509 clipGrantFlags = in.readInt(); 510 } else { 511 clipData = null; 512 clipGrantFlags = 0; 513 } 514 callback = in.readStrongBinder(); 515 overrideDeadlineExpired = in.readInt() == 1; 516 mIsExpedited = in.readBoolean(); 517 mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); 518 mTriggeredContentAuthorities = in.createStringArray(); 519 if (in.readInt() != 0) { 520 network = Network.CREATOR.createFromParcel(in); 521 } else { 522 network = null; 523 } 524 mStopReason = in.readInt(); 525 mInternalStopReason = in.readInt(); 526 debugStopReason = in.readString(); 527 } 528 529 /** @hide */ setStopReason(@topReason int reason, int internalStopReason, String debugStopReason)530 public void setStopReason(@StopReason int reason, int internalStopReason, 531 String debugStopReason) { 532 mStopReason = reason; 533 mInternalStopReason = internalStopReason; 534 this.debugStopReason = debugStopReason; 535 } 536 537 @Override describeContents()538 public int describeContents() { 539 return 0; 540 } 541 542 @Override writeToParcel(Parcel dest, int flags)543 public void writeToParcel(Parcel dest, int flags) { 544 dest.writeInt(jobId); 545 dest.writePersistableBundle(extras); 546 dest.writeBundle(transientExtras); 547 if (clipData != null) { 548 dest.writeInt(1); 549 clipData.writeToParcel(dest, flags); 550 dest.writeInt(clipGrantFlags); 551 } else { 552 dest.writeInt(0); 553 } 554 dest.writeStrongBinder(callback); 555 dest.writeInt(overrideDeadlineExpired ? 1 : 0); 556 dest.writeBoolean(mIsExpedited); 557 dest.writeTypedArray(mTriggeredContentUris, flags); 558 dest.writeStringArray(mTriggeredContentAuthorities); 559 if (network != null) { 560 dest.writeInt(1); 561 network.writeToParcel(dest, flags); 562 } else { 563 dest.writeInt(0); 564 } 565 dest.writeInt(mStopReason); 566 dest.writeInt(mInternalStopReason); 567 dest.writeString(debugStopReason); 568 } 569 570 public static final @android.annotation.NonNull Creator<JobParameters> CREATOR = new Creator<JobParameters>() { 571 @Override 572 public JobParameters createFromParcel(Parcel in) { 573 return new JobParameters(in); 574 } 575 576 @Override 577 public JobParameters[] newArray(int size) { 578 return new JobParameters[size]; 579 } 580 }; 581 } 582