diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index 776d913e56cb1..3cfddc6d8e2b2 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -65,8 +65,12 @@ public class JobSchedulerImpl extends JobScheduler { @NonNull @Override public JobScheduler forNamespace(@NonNull String namespace) { + namespace = sanitizeNamespace(namespace); if (namespace == null) { - throw new IllegalArgumentException("namespace cannot be null"); + throw new NullPointerException("namespace cannot be null"); + } + if (namespace.isEmpty()) { + throw new IllegalArgumentException("namespace cannot be empty"); } return new JobSchedulerImpl(this, namespace); } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index b8847add07349..d59d430e0b78f 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -270,6 +270,9 @@ public abstract class JobScheduler { * otherwise. Attempting to update a job scheduled in another namespace will not be possible * but will instead create or update the job inside the current namespace. A JobScheduler * instance dedicated to a namespace must be used to schedule or update jobs in that namespace. + * + *
Since leading and trailing whitespace can lead to hard-to-debug issues, + * they will be {@link String#trim() trimmed}. An empty String (after trimming) is not allowed. * @see #getNamespace() */ @NonNull @@ -287,6 +290,15 @@ public abstract class JobScheduler { throw new RuntimeException("Not implemented. Must override in a subclass."); } + /** @hide */ + @Nullable + public static String sanitizeNamespace(@Nullable String namespace) { + if (namespace == null) { + return null; + } + return namespace.trim().intern(); + } + /** * Schedule a job to be executed. Will replace any currently scheduled job with the same * ID with the new information in the {@link JobInfo}. If a job with the given ID is currently diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 3fe83a64b5ec8..bad4060ca775c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -4024,6 +4024,18 @@ public class JobSchedulerService extends com.android.server.SystemService return JobScheduler.RESULT_SUCCESS; } + /** Returns a sanitized namespace if valid, or throws an exception if not. */ + private String validateNamespace(@Nullable String namespace) { + namespace = JobScheduler.sanitizeNamespace(namespace); + if (namespace != null) { + if (namespace.isEmpty()) { + throw new IllegalArgumentException("namespace cannot be empty"); + } + namespace = namespace.intern(); + } + return namespace; + } + private int validateRunUserInitiatedJobsPermission(int uid, String packageName) { final int state = getRunUserInitiatedJobsPermissionState(uid, packageName); if (state == PermissionChecker.PERMISSION_HARD_DENIED) { @@ -4071,9 +4083,7 @@ public class JobSchedulerService extends com.android.server.SystemService return result; } - if (namespace != null) { - namespace = namespace.intern(); - } + namespace = validateNamespace(namespace); final long ident = Binder.clearCallingIdentity(); try { @@ -4104,9 +4114,7 @@ public class JobSchedulerService extends com.android.server.SystemService return result; } - if (namespace != null) { - namespace = namespace.intern(); - } + namespace = validateNamespace(namespace); final long ident = Binder.clearCallingIdentity(); try { @@ -4145,9 +4153,7 @@ public class JobSchedulerService extends com.android.server.SystemService return result; } - if (namespace != null) { - namespace = namespace.intern(); - } + namespace = validateNamespace(namespace); final long ident = Binder.clearCallingIdentity(); try { @@ -4184,7 +4190,8 @@ public class JobSchedulerService extends com.android.server.SystemService final long ident = Binder.clearCallingIdentity(); try { return new ParceledListSlice<>( - JobSchedulerService.this.getPendingJobsInNamespace(uid, namespace)); + JobSchedulerService.this.getPendingJobsInNamespace(uid, + validateNamespace(namespace))); } finally { Binder.restoreCallingIdentity(ident); } @@ -4196,7 +4203,8 @@ public class JobSchedulerService extends com.android.server.SystemService final long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.getPendingJob(uid, namespace, jobId); + return JobSchedulerService.this.getPendingJob( + uid, validateNamespace(namespace), jobId); } finally { Binder.restoreCallingIdentity(ident); } @@ -4208,7 +4216,8 @@ public class JobSchedulerService extends com.android.server.SystemService final long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.getPendingJobReason(uid, namespace, jobId); + return JobSchedulerService.this.getPendingJobReason( + uid, validateNamespace(namespace), jobId); } finally { Binder.restoreCallingIdentity(ident); } @@ -4238,7 +4247,7 @@ public class JobSchedulerService extends com.android.server.SystemService JobSchedulerService.this.cancelJobsForUid(uid, // Documentation says only jobs scheduled BY the app will be cancelled /* includeSourceApp */ false, - /* namespaceOnly */ true, namespace, + /* namespaceOnly */ true, validateNamespace(namespace), JobParameters.STOP_REASON_CANCELLED_BY_APP, JobParameters.INTERNAL_STOP_REASON_CANCELED, "cancelAllInNamespace() called by app, callingUid=" + uid); @@ -4253,7 +4262,7 @@ public class JobSchedulerService extends com.android.server.SystemService final long ident = Binder.clearCallingIdentity(); try { - JobSchedulerService.this.cancelJob(uid, namespace, jobId, uid, + JobSchedulerService.this.cancelJob(uid, validateNamespace(namespace), jobId, uid, JobParameters.STOP_REASON_CANCELLED_BY_APP); } finally { Binder.restoreCallingIdentity(ident);