@Override protected int convertNetworkType(@NonNull JobRequest.NetworkType networkType) { switch (networkType) { case NOT_ROAMING: return JobInfo.NETWORK_TYPE_NOT_ROAMING; default: return super.convertNetworkType(networkType); } } }
@Override public void cancel(int jobId) { try { getJobScheduler().cancel(jobId); } catch (Exception e) { // https://gist.github.com/vRallev/5d48a4a8e8d05067834e mCat.e(e); } TransientBundleCompat.cancel(mContext, jobId, null); }
@Override public void plantPeriodicFlexSupport(JobRequest request) { mCat.w("plantPeriodicFlexSupport called although flex is supported"); super.plantPeriodicFlexSupport(request); }
@Override public void plantOneOff(JobRequest request) { long startMs = Common.getStartMs(request); long endMs = Common.getEndMs(request, true); JobInfo jobInfo = createBuilderOneOff(createBaseBuilder(request, true), startMs, endMs).build(); int scheduleResult = schedule(jobInfo); if (scheduleResult == ERROR_BOOT_PERMISSION) { jobInfo = createBuilderOneOff(createBaseBuilder(request, false), startMs, endMs).build(); scheduleResult = schedule(jobInfo); } mCat.d("Schedule one-off jobInfo %s, %s, start %s, end %s (from now), reschedule count %d", scheduleResultToString(scheduleResult), request, JobUtil.timeToString(startMs), JobUtil.timeToString(Common.getEndMs(request, false)), Common.getRescheduleCount(request)); }
@Override public void plantPeriodic(JobRequest request) { long intervalMs = request.getIntervalMs(); long flexMs = request.getFlexMs(); JobInfo jobInfo = createBuilderPeriodic(createBaseBuilder(request, true), intervalMs, flexMs).build(); int scheduleResult = schedule(jobInfo); if (scheduleResult == ERROR_BOOT_PERMISSION) { jobInfo = createBuilderPeriodic(createBaseBuilder(request, false), intervalMs, flexMs).build(); scheduleResult = schedule(jobInfo); } mCat.d("Schedule periodic jobInfo %s, %s, interval %s, flex %s", scheduleResultToString(scheduleResult), request, JobUtil.timeToString(intervalMs), JobUtil.timeToString(flexMs)); }
@Test public void verifyAlarmIsCanceledAfterStart() throws Exception { assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); JobConfig.forceApi(JobApi.V_21); int jobId = scheduleJob(); final Intent intent = PlatformAlarmServiceExact.createIntent(context(), jobId, null); PendingIntent pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNotNull(); boolean started = TransientBundleCompat.startWithTransientBundle(context(), mJobManagerRule.getManager().getJobRequest(jobId)); assertThat(started).isTrue(); pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNull(); }
@Override public void run() { try { final int jobId = params.getJobId(); final JobProxy.Common common = new JobProxy.Common(PlatformJobService.this, CAT, jobId); // don't mark starting! final JobRequest request = common.getPendingRequest(true, false); if (request == null) { return; } if (request.isTransient()) { if (TransientBundleCompat.startWithTransientBundle(PlatformJobService.this, request)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // should only happen during testing if an API is disabled CAT.d("PendingIntent for transient bundle is not null although running on O, using compat mode, request %s", request); } return; } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { CAT.d("PendingIntent for transient job %s expired", request); return; } } common.markStarting(request); common.executeJobRequest(request, getTransientBundle(params)); } finally { // do not reschedule jobFinished(params, false); } } });
@Override public boolean isPlatformJobScheduled(JobRequest request) { List<JobInfo> pendingJobs; try { pendingJobs = getJobScheduler().getAllPendingJobs(); } catch (Exception e) { // it's possible that this throws an exception, see https://gist.github.com/vRallev/a59947dd3932d2642641 mCat.e(e); return false; } //noinspection ConstantConditions if (pendingJobs == null || pendingJobs.isEmpty()) { return false; } for (JobInfo info : pendingJobs) { if (isJobInfoScheduled(info, request)) { return true; } } return false; }
protected JobInfo.Builder createBaseBuilder(JobRequest request, boolean allowPersisting) { JobInfo.Builder builder = new JobInfo.Builder(request.getJobId(), new ComponentName(mContext, PlatformJobService.class)) .setRequiresCharging(request.requiresCharging()) .setRequiresDeviceIdle(request.requiresDeviceIdle()) .setRequiredNetworkType(convertNetworkType(request.requiredNetworkType())) .setPersisted(allowPersisting && !request.isTransient() && JobUtil.hasBootPermission(mContext)); return setTransientBundle(request, builder); }
@Test public void verifyAlarmNotCanceledForPeriodicAfterStart() throws Exception { assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); JobConfig.forceApi(JobApi.V_21); Bundle extras = new Bundle(); extras.putString("key", "value"); int jobId = new JobRequest.Builder("tag") .setPeriodic(TimeUnit.DAYS.toMillis(1)) .setTransientExtras(extras) .build() .schedule(); assertThat(mJobManagerRule.getAllPendingJobsFromScheduler()).isNotNull().isNotEmpty(); JobRequest request = mJobManagerRule.getManager().getJobRequest(jobId); assertThat(request.isTransient()).isTrue(); final Intent intent = PlatformAlarmServiceExact.createIntent(context(), jobId, null); PendingIntent pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNotNull(); boolean started = TransientBundleCompat.startWithTransientBundle(context(), request); assertThat(started).isTrue(); pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNotNull(); }
protected JobInfo.Builder setTransientBundle(JobRequest request, JobInfo.Builder builder) { if (request.isTransient()) { TransientBundleCompat.persistBundle(mContext, request); } return builder; }
@Test public void verifyAlarmIsCanceled() throws Exception { assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); JobConfig.forceApi(JobApi.V_21); int jobId = scheduleJob(); final Intent intent = PlatformAlarmServiceExact.createIntent(context(), jobId, null); PendingIntent pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNotNull(); mJobManagerRule.getManager().cancel(jobId); pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNull(); }
@SuppressWarnings("SimplifiableIfStatement") protected boolean isJobInfoScheduled(@Nullable JobInfo info, @NonNull JobRequest request) { boolean correctInfo = info != null && info.getId() == request.getJobId(); if (!correctInfo) { return false; } return !request.isTransient() || TransientBundleCompat.isScheduled(mContext, request.getJobId()); }
protected final int schedule(JobInfo jobInfo) { JobScheduler jobScheduler = getJobScheduler(); if (jobScheduler == null) { throw new JobProxyIllegalStateException("JobScheduler is null"); } try { return jobScheduler.schedule(jobInfo); } catch (IllegalArgumentException e) { mCat.e(e); String message = e.getMessage(); if (message != null && message.contains("RECEIVE_BOOT_COMPLETED")) { return ERROR_BOOT_PERMISSION; } else if (message != null && message.contains("No such service ComponentInfo")) { // this will reset the proxy and in the worst case use the AlarmManager throw new JobProxyIllegalStateException(e); } else { throw e; } } catch (NullPointerException e) { /* Attempt to invoke interface method 'int android.app.job.IJobScheduler.schedule(android.app.job.JobInfo)' on a null object reference at android.app.JobSchedulerImpl.schedule(JobSchedulerImpl.java:42) at com.evernote.android.job.v21.JobProxy21.schedule(JobProxy21.java:198) */ mCat.e(e); throw new JobProxyIllegalStateException(e); } }
public static boolean startWithTransientBundle(@NonNull Context context, @NonNull JobRequest request) { // transientExtras are not necessary in this case Intent intent = PlatformAlarmServiceExact.createIntent(context, request.getJobId(), null); PendingIntent pendingIntent = PendingIntent.getService(context, request.getJobId(), intent, PendingIntent.FLAG_NO_CREATE); if (pendingIntent == null) { return false; } try { CAT.i("Delegating transient job %s to API 14", request); pendingIntent.send(); } catch (PendingIntent.CanceledException e) { CAT.e(e); return false; } if (!request.isPeriodic()) { cancel(context, request.getJobId(), pendingIntent); } return true; }
@NonNull private JobProxy createProxy(Context context) { switch (this) { case WORK_MANAGER: return new JobProxyWorkManager(context); case V_26: return new JobProxy26(context); case V_24: return new JobProxy24(context); case V_21: return new JobProxy21(context); case V_19: return new JobProxy19(context); case V_14: return new JobProxy14(context); case GCM: return new JobProxyGcm(context); default: throw new IllegalStateException("not implemented"); } }
@Override public void plantPeriodicFlexSupport(JobRequest request) { long startMs = Common.getStartMsSupportFlex(request); long endMs = Common.getEndMsSupportFlex(request); JobInfo jobInfo = createBuilderOneOff(createBaseBuilder(request, true), startMs, endMs).build(); int scheduleResult = schedule(jobInfo); if (scheduleResult == ERROR_BOOT_PERMISSION) { jobInfo = createBuilderOneOff(createBaseBuilder(request, false), startMs, endMs).build(); scheduleResult = schedule(jobInfo); } mCat.d("Schedule periodic (flex support) jobInfo %s, %s, start %s, end %s, flex %s", scheduleResultToString(scheduleResult), request, JobUtil.timeToString(startMs), JobUtil.timeToString(endMs), JobUtil.timeToString(request.getFlexMs())); }
@Override public void cancel(int jobId) { try { getJobScheduler().cancel(jobId); } catch (Exception e) { // https://gist.github.com/vRallev/5d48a4a8e8d05067834e mCat.e(e); } TransientBundleCompat.cancel(mContext, jobId, null); }
protected JobInfo.Builder setTransientBundle(JobRequest request, JobInfo.Builder builder) { if (request.isTransient()) { TransientBundleCompat.persistBundle(mContext, request); } return builder; }
@Test public void verifyNativeImplementationIsUsedWithO() throws Exception { // ignore test if not supported assumeTrue(JobApi.V_26.isSupported(InstrumentationRegistry.getTargetContext())); JobConfig.forceApi(JobApi.V_26); int jobId = scheduleJob(); final Intent intent = PlatformAlarmServiceExact.createIntent(context(), jobId, null); PendingIntent pendingIntent = PendingIntent.getService(context(), jobId, intent, PendingIntent.FLAG_NO_CREATE); assertThat(pendingIntent).isNull(); }