Merge "Add permission check to setBias." into main
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 5dc994e..a92a01f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1265,6 +1265,7 @@
 
         /** @hide */
         @NonNull
+        @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
         public Builder setBias(int bias) {
             mBias = bias;
             return this;
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 592aff8..1287cb4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -202,6 +202,15 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L;
 
+    /**
+     * Throw an exception when biases are set by an unsupported client.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long THROW_ON_UNSUPPORTED_BIAS_USAGE = 300477393L;
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static Clock sSystemClock = Clock.systemUTC();
 
@@ -4331,6 +4340,24 @@
             }
         }
 
+        private JobInfo enforceBuilderApiPermissions(int uid, int pid, JobInfo job) {
+            if (job.getBias() != JobInfo.BIAS_DEFAULT
+                        && !hasPermission(uid, pid, Manifest.permission.UPDATE_DEVICE_STATS)) {
+                if (CompatChanges.isChangeEnabled(THROW_ON_UNSUPPORTED_BIAS_USAGE, uid)) {
+                    throw new SecurityException("Apps may not call setBias()");
+                } else {
+                    // We can't throw the exception. Log the issue and modify the job to remove
+                    // the invalid value.
+                    Slog.w(TAG, "Uid " + uid + " set bias on its job");
+                    return new JobInfo.Builder(job)
+                            .setBias(JobInfo.BIAS_DEFAULT)
+                            .build(false, false);
+                }
+            }
+
+            return job;
+        }
+
         private boolean canPersistJobs(int pid, int uid) {
             // Persisting jobs is tantamount to running at boot, so we permit
             // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
@@ -4512,6 +4539,8 @@
 
             namespace = validateNamespace(namespace);
 
+            job = enforceBuilderApiPermissions(uid, pid, job);
+
             final long ident = Binder.clearCallingIdentity();
             try {
                 return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
@@ -4543,6 +4572,8 @@
 
             namespace = validateNamespace(namespace);
 
+            job = enforceBuilderApiPermissions(uid, pid, job);
+
             final long ident = Binder.clearCallingIdentity();
             try {
                 return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
@@ -4582,6 +4613,8 @@
 
             namespace = validateNamespace(namespace);
 
+            job = enforceBuilderApiPermissions(callerUid, callerPid, job);
+
             final long ident = Binder.clearCallingIdentity();
             try {
                 return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,