From 6d4947c2265b54f87ccb1e02452c5f0e88bb8b3b Mon Sep 17 00:00:00 2001 From: Varun Shah Date: Mon, 7 Nov 2022 07:25:53 +0000 Subject: [PATCH] Add a new API in JobInfo.Builder for Data Transfer jobs. Also add a new API in JobScheduler which allows apps to check if they have the RUN_LONG_JOBS permission. Bug: 255371817 Test: atest JobInfoTest Change-Id: I433977e8bbf041f53bcb9a63f0a992433d06fb40 --- .../java/android/app/JobSchedulerImpl.java | 15 +++- .../java/android/app/job/IJobScheduler.aidl | 1 + .../java/android/app/job/JobInfo.java | 84 ++++++++++++++++++- .../java/android/app/job/JobScheduler.java | 8 ++ .../job/JobSchedulerFrameworkInitializer.java | 4 +- .../server/job/JobSchedulerService.java | 29 +++++-- core/api/current.txt | 1 + 7 files changed, 130 insertions(+), 12 deletions(-) diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index c6d4a892e6268..652c49a28d595 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -16,11 +16,13 @@ package android.app; +import android.annotation.NonNull; import android.app.job.IJobScheduler; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; +import android.content.Context; import android.os.RemoteException; import java.util.List; @@ -36,8 +38,10 @@ import java.util.List; */ public class JobSchedulerImpl extends JobScheduler { IJobScheduler mBinder; + private final Context mContext; - public JobSchedulerImpl(IJobScheduler binder) { + public JobSchedulerImpl(@NonNull Context context, IJobScheduler binder) { + mContext = context; mBinder = binder; } @@ -102,6 +106,15 @@ public class JobSchedulerImpl extends JobScheduler { } } + @Override + public boolean canRunLongJobs() { + try { + return mBinder.canRunLongJobs(mContext.getOpPackageName()); + } catch (RemoteException e) { + return false; + } + } + @Override public boolean hasRunLongJobsPermission(String packageName, int userId) { try { diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index 452fb0487d0bd..d2be32e219c99 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl @@ -33,6 +33,7 @@ interface IJobScheduler { void cancelAll(); ParceledListSlice getAllPendingJobs(); JobInfo getPendingJob(int jobId); + boolean canRunLongJobs(String packageName); boolean hasRunLongJobsPermission(String packageName, int userId); List getStartedJobs(); ParceledListSlice getAllJobSnapshots(); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 4c849fe831de3..9caf99e5b827f 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -395,6 +395,13 @@ public class JobInfo implements Parcelable { */ public static final int FLAG_EXPEDITED = 1 << 4; + /** + * Whether it's a data transfer job or not. + * + * @hide + */ + public static final int FLAG_DATA_TRANSFER = 1 << 5; + /** * @hide */ @@ -722,6 +729,14 @@ public class JobInfo implements Parcelable { return (flags & FLAG_EXPEDITED) != 0; } + /** + * @see JobInfo.Builder#setDataTransfer(boolean) + * @hide + */ + public boolean isDataTransfer() { + return (flags & FLAG_DATA_TRANSFER) != 0; + } + /** * @see JobInfo.Builder#setImportantWhileForeground(boolean) */ @@ -1815,6 +1830,52 @@ public class JobInfo implements Parcelable { return this; } + /** + * Indicates that this job will be used to transfer data to or from a remote server. The + * system could attempt to run a data transfer job longer than a regular job if the data + * being transferred is potentially very large and can take a long time to complete. + * + *

+ * The app must hold the {@link android.Manifest.permission#RUN_LONG_JOBS} permission to + * use this API. JobScheduler will throw a {@link SecurityException} if an app without the + * permission granted attempts to schedule a data transfer job. + * + *

+ * You must provide an estimate of the payload size via + * {@link #setEstimatedNetworkBytes(long, long)} when scheduling the job or use + * {@link JobService#updateEstimatedNetworkBytes(JobParameters, long, long)} or + * {@link JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)} + * shortly after the job starts. + * + *

+ * For user-initiated transfers that must be started immediately, call + * {@link #setExpedited(boolean) setExpedited(true)}. Otherwise, the system may defer the + * job to a more opportune time. Using {@link #setExpedited(boolean) setExpedited(true)} + * with this API will only be allowed for foreground apps and when the user has clearly + * interacted with the app. {@link #setExpedited(boolean) setExpedited(true)} will return + * {@link JobScheduler#RESULT_FAILURE} for a data transfer job if the app is in the + * background. Apps that successfully schedule data transfer jobs with + * {@link #setExpedited(boolean) setExpedited(true)} will not have quotas applied to them, + * though they may still be stopped for system health or constraint reasons. The system will + * also give a user the ability to stop a data transfer job via the Task Manager. + * + *

+ * If you want to perform more than one data transfer job, consider enqueuing multiple + * {@link JobWorkItem JobWorkItems} along with {@link #setDataTransfer(boolean)}. + * + * @see JobInfo#isDataTransfer() + * @hide + */ + @NonNull + public Builder setDataTransfer(boolean dataTransfer) { + if (dataTransfer) { + mFlags |= FLAG_DATA_TRANSFER; + } else { + mFlags &= (~FLAG_DATA_TRANSFER); + } + return this; + } + /** * Setting this to true indicates that this job is important while the scheduling app * is in the foreground or on the temporary whitelist for background restrictions. @@ -2062,8 +2123,9 @@ public class JobInfo implements Parcelable { "An expedited job must be high or max priority. Don't use expedited jobs" + " for unimportant tasks."); } - if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0 - || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) { + if (((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0 + || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY + | FLAG_DATA_TRANSFER)) != 0)) { throw new IllegalArgumentException( "An expedited job can only have network and storage-not-low constraints"); } @@ -2072,6 +2134,24 @@ public class JobInfo implements Parcelable { "Can't call addTriggerContentUri() on an expedited job"); } } + + if ((flags & FLAG_DATA_TRANSFER) != 0) { + if (backoffPolicy == BACKOFF_POLICY_LINEAR) { + throw new IllegalArgumentException( + "A data transfer job cannot have a linear backoff policy."); + } + if (hasLateConstraint) { + throw new IllegalArgumentException("A data transfer job cannot have a deadline"); + } + if ((flags & FLAG_PREFETCH) != 0) { + throw new IllegalArgumentException( + "A data transfer job cannot also be a prefetch job"); + } + if (networkRequest == null) { + throw new IllegalArgumentException( + "A data transfer job must specify a valid network type"); + } + } } /** diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 807f32d8c0cec..76f71a2b9fe27 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -249,6 +249,14 @@ public abstract class JobScheduler { */ public abstract @Nullable JobInfo getPendingJob(int jobId); + /** + * Returns {@code true} if the calling app currently holds the + * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs. + */ + public boolean canRunLongJobs() { + return false; + } + /** * Returns {@code true} if the app currently holds the * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs. diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java index 7b287d5f9d159..f56e1eea0f233 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java @@ -44,9 +44,9 @@ public class JobSchedulerFrameworkInitializer { *

If this is called from other places, it throws a {@link IllegalStateException). */ public static void registerServiceWrappers() { - SystemServiceRegistry.registerStaticService( + SystemServiceRegistry.registerContextAwareService( Context.JOB_SCHEDULER_SERVICE, JobScheduler.class, - (b) -> new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b))); + (context, b) -> new JobSchedulerImpl(context, IJobScheduler.Stub.asInterface(b))); SystemServiceRegistry.registerContextAwareService( Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class, (context, b) -> new DeviceIdleManager( 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 56f318f6415a5..07897d66f3b4a 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -28,7 +28,6 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; -import android.app.AppOpsManager; import android.app.IUidObserver; import android.app.compat.CompatChanges; import android.app.job.IJobScheduler; @@ -48,6 +47,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -305,7 +305,6 @@ public class JobSchedulerService extends com.android.server.SystemService final JobHandler mHandler; final JobSchedulerStub mJobSchedulerStub; - private AppOpsManager mAppOps; PackageManagerInternal mLocalPM; ActivityManagerInternal mActivityManagerInternal; DeviceIdleInternal mLocalDeviceIdleController; @@ -1806,8 +1805,6 @@ public class JobSchedulerService extends com.android.server.SystemService controller.onSystemServicesReady(); } - mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); - mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull( LocalServices.getService(AppStateTracker.class)); @@ -3413,6 +3410,19 @@ public class JobSchedulerService extends com.android.server.SystemService } } + @Override + public boolean canRunLongJobs(@NonNull String packageName) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId); + if (callingUid != packageUid) { + throw new SecurityException("Uid " + callingUid + + " cannot query canRunLongJobs for package " + packageName); + } + + return checkRunLongJobsPermission(packageUid, packageName); + } + @Override public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) { @@ -3423,9 +3433,14 @@ public class JobSchedulerService extends com.android.server.SystemService + " cannot query canRunLongJobs for package " + packageName); } - final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_RUN_LONG_JOBS, uid, - packageName); - return mode == AppOpsManager.MODE_ALLOWED || mode == AppOpsManager.MODE_DEFAULT; + return checkRunLongJobsPermission(uid, packageName); + } + + private boolean checkRunLongJobsPermission(int packageUid, String packageName) { + // Returns true if both the appop and permission are granted. + return PermissionChecker.checkPermissionForPreflight(getContext(), + android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN, + packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED; } /** diff --git a/core/api/current.txt b/core/api/current.txt index 75bea40731f4c..8ee3dbdc590a5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8440,6 +8440,7 @@ package android.app.job { public abstract class JobScheduler { ctor public JobScheduler(); + method public boolean canRunLongJobs(); method public abstract void cancel(int); method public abstract void cancelAll(); method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);