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
This commit is contained in:
Varun Shah
2022-11-07 07:25:53 +00:00
parent 384bb8bbe9
commit 6d4947c226
7 changed files with 130 additions and 12 deletions

View File

@@ -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 {

View File

@@ -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<JobInfo> getStartedJobs();
ParceledListSlice getAllJobSnapshots();

View File

@@ -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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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");
}
}
}
/**

View File

@@ -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.

View File

@@ -44,9 +44,9 @@ public class JobSchedulerFrameworkInitializer {
* <p>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(

View File

@@ -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;
}
/**

View File

@@ -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);