diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index f59e7a4ae6ecd..c6d4a892e6268 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -102,6 +102,15 @@ public class JobSchedulerImpl extends JobScheduler { } } + @Override + public boolean hasRunLongJobsPermission(String packageName, int userId) { + try { + return mBinder.hasRunLongJobsPermission(packageName, userId); + } catch (RemoteException e) { + return false; + } + } + @Override public List getStartedJobs() { try { diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index 3006f50e54fcd..452fb0487d0bd 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 hasRunLongJobsPermission(String packageName, int userId); List getStartedJobs(); ParceledListSlice getAllJobSnapshots(); } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 74486861956ed..807f32d8c0cec 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.UserIdInt; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.ClipData; @@ -248,6 +249,15 @@ public abstract class JobScheduler { */ public abstract @Nullable JobInfo getPendingJob(int jobId); + /** + * Returns {@code true} if the app currently holds the + * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs. + * @hide + */ + public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) { + return false; + } + /** * For internal system callers only! * Returns a list of all currently-executing jobs. 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 e0d1a30e7cede..6966ac0fca567 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -28,6 +28,7 @@ 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; @@ -304,6 +305,7 @@ public class JobSchedulerService extends com.android.server.SystemService final JobHandler mHandler; final JobSchedulerStub mJobSchedulerStub; + private AppOpsManager mAppOps; PackageManagerInternal mLocalPM; ActivityManagerInternal mActivityManagerInternal; DeviceIdleInternal mLocalDeviceIdleController; @@ -1804,6 +1806,8 @@ 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)); @@ -3409,6 +3413,21 @@ public class JobSchedulerService extends com.android.server.SystemService } } + @Override + public boolean hasRunLongJobsPermission(@NonNull String packageName, + @UserIdInt int userId) { + final int uid = mLocalPM.getPackageUid(packageName, 0, userId); + final int callingUid = Binder.getCallingUid(); + if (callingUid != uid && !UserHandle.isCore(callingUid)) { + throw new SecurityException("Uid " + callingUid + + " 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; + } + /** * "dumpsys" infrastructure */ diff --git a/core/api/current.txt b/core/api/current.txt index 8782236a8ea84..272df9a7bd5f1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -35860,6 +35860,7 @@ package android.provider { field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS"; field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; + field public static final String ACTION_MANAGE_APP_LONG_JOBS = "android.settings.MANAGE_APP_LONG_JOBS"; field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ef448f5dbd1b4..b644a0b3d07d5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -586,6 +586,22 @@ public final class Settings { public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA"; + /** + * Activity Action: Show settings to allow configuration of + * {@link Manifest.permission#RUN_LONG_JOBS} permission + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". + *

+ * Output: When a package data uri is passed as input, the activity result is set to + * {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise, + * the result is set to {@link android.app.Activity#RESULT_CANCELED}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_APP_LONG_JOBS = + "android.settings.MANAGE_APP_LONG_JOBS"; + /** * Activity Action: Show settings to allow configuration of cross-profile access for apps *