Loading apex/jobscheduler/service/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ java_library { ], libs: [ "app-compat-annotations", "framework", "services.core", ], Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +138 −1 Original line number Diff line number Diff line Loading @@ -37,12 +37,15 @@ import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; Loading @@ -54,6 +57,7 @@ import android.net.Uri; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; Loading @@ -67,6 +71,7 @@ import android.os.UserManagerInternal; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; Loading @@ -85,6 +90,7 @@ import com.android.server.AppStateTracker; import com.android.server.DeviceIdleInternal; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.compat.PlatformCompat; import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; import com.android.server.job.controllers.BackgroundJobsController; Loading @@ -102,6 +108,9 @@ import com.android.server.job.restrictions.JobRestriction; import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import com.android.server.utils.quota.Categorizer; import com.android.server.utils.quota.Category; import com.android.server.utils.quota.CountQuotaTracker; import libcore.util.EmptyArray; Loading Loading @@ -145,6 +154,16 @@ public class JobSchedulerService extends com.android.server.SystemService /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; /** * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app * calls the APIs too frequently. */ @ChangeId // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up. @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L; @VisibleForTesting public static Clock sSystemClock = Clock.systemUTC(); Loading Loading @@ -237,6 +256,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ private final List<JobRestriction> mJobRestrictions; private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_TAG = ".schedule()"; private final PlatformCompat mPlatformCompat; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. Loading Loading @@ -275,6 +298,11 @@ public class JobSchedulerService extends com.android.server.SystemService */ final SparseIntArray mBackingUpUids = new SparseIntArray(); /** * Cache of debuggable app status. */ final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>(); /** * Named indices into standby bucket arrays, for clarity in referring to * specific buckets' bookkeeping. Loading Loading @@ -315,6 +343,10 @@ public class JobSchedulerService extends com.android.server.SystemService final StateController sc = mControllers.get(controller); sc.onConstantsUpdatedLocked(); } mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS); mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. Loading Loading @@ -466,6 +498,11 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats"; private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas"; private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count"; private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms"; private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION = "aq_schedule_throw_exception"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; Loading @@ -484,6 +521,10 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; private static final boolean DEFAULT_ENABLE_API_QUOTAS = true; private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 500; private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS; private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true; /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things Loading Loading @@ -618,6 +659,24 @@ public class JobSchedulerService extends com.android.server.SystemService */ public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; /** * Whether to enable quota limits on APIs. */ public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS; /** * The maximum number of schedule() calls an app can make in a set amount of time. */ public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT; /** * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. */ public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS; /** * Whether to throw an exception when an app hits its schedule quota limit. */ public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION = DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION; private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { Loading Loading @@ -678,6 +737,16 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_CONN_CONGESTION_DELAY_FRAC); CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS); API_QUOTA_SCHEDULE_COUNT = Math.max(250, mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT)); API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis( KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS); API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean( KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION); } void dump(IndentingPrintWriter pw) { Loading Loading @@ -716,6 +785,12 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); pw.printPair(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, API_QUOTA_SCHEDULE_THROW_EXCEPTION).println(); pw.decreaseIndent(); } Loading Loading @@ -746,6 +821,12 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION, API_QUOTA_SCHEDULE_THROW_EXCEPTION); } } Loading Loading @@ -847,6 +928,7 @@ public class JobSchedulerService extends com.android.server.SystemService for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } mDebuggableApps.remove(pkgName); } } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { Loading Loading @@ -972,6 +1054,45 @@ public class JobSchedulerService extends com.android.server.SystemService public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { final String pkg = packageName == null ? job.getService().getPackageName() : packageName; if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG)) { Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times"); // TODO(b/145551233): attempt to restrict app if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION && mPlatformCompat.isChangeEnabledByPackageName( CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) { final boolean isDebuggable; synchronized (mLock) { if (!mDebuggableApps.containsKey(packageName)) { try { final ApplicationInfo appInfo = AppGlobals.getPackageManager() .getApplicationInfo(pkg, 0, userId); if (appInfo != null) { mDebuggableApps.put(packageName, (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); } else { return JobScheduler.RESULT_FAILURE; } } catch (RemoteException e) { throw new RuntimeException(e); } } isDebuggable = mDebuggableApps.get(packageName); } if (isDebuggable) { // Only throw the exception for debuggable apps. throw new IllegalStateException( "schedule()/enqueue() called more than " + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY) + " times in the past " + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY) + "ms"); } } return JobScheduler.RESULT_FAILURE; } mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG); try { if (ActivityManager.getService().isAppStartModeDisabled(uId, job.getService().getPackageName())) { Loading Loading @@ -1296,6 +1417,12 @@ public class JobSchedulerService extends com.android.server.SystemService // Set up the app standby bucketing tracker mStandbyTracker = new StandbyTracker(); mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mPlatformCompat = (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER); mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); appStandby.addListener(mStandbyTracker); Loading Loading @@ -2745,7 +2872,7 @@ public class JobSchedulerService extends com.android.server.SystemService return new ParceledListSlice<>(snapshots); } } }; } // Shell command infrastructure: run the given job immediately int executeRunCommand(String pkgName, int userId, int jobId, boolean force) { Loading Loading @@ -2968,6 +3095,10 @@ public class JobSchedulerService extends com.android.server.SystemService return 0; } void resetScheduleQuota() { mQuotaTracker.clear(); } void triggerDockState(boolean idleState) { final Intent dockIntent; if (idleState) { Loading Loading @@ -3030,6 +3161,9 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); mQuotaTracker.dump(pw); pw.println(); pw.println("Started users: " + Arrays.toString(mStartedUsers)); pw.print("Registered "); pw.print(mJobs.size()); Loading Loading @@ -3217,6 +3351,9 @@ public class JobSchedulerService extends com.android.server.SystemService for (int u : mStartedUsers) { proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u); } mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER); if (mJobs.size() > 0) { final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs(); sortJobs(jobs); Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +14 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,8 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return getJobState(pw); case "heartbeat": return doHeartbeat(pw); case "reset-schedule-quota": return resetScheduleQuota(pw); case "trigger-dock-state": return triggerDockState(pw); default: Loading Loading @@ -344,6 +346,18 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return -1; } private int resetScheduleQuota(PrintWriter pw) throws Exception { checkPermission("reset schedule quota"); final long ident = Binder.clearCallingIdentity(); try { mInternal.resetScheduleQuota(); } finally { Binder.restoreCallingIdentity(ident); } return 0; } private int triggerDockState(PrintWriter pw) throws Exception { checkPermission("trigger wireless charging dock state"); Loading core/proto/android/server/jobscheduler.proto +15 −5 Original line number Diff line number Diff line Loading @@ -32,8 +32,8 @@ import "frameworks/base/core/proto/android/server/appstatetracker.proto"; import "frameworks/base/core/proto/android/server/job/enums.proto"; import "frameworks/base/core/proto/android/server/statlogger.proto"; import "frameworks/base/core/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/util/quotatracker.proto"; // Next tag: 21 message JobSchedulerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -160,10 +160,13 @@ message JobSchedulerServiceDumpProto { optional JobConcurrencyManagerProto concurrency_manager = 20; optional JobStorePersistStatsProto persist_stats = 21; optional .android.util.quota.CountQuotaTrackerProto quota_tracker = 22; // Next tag: 23 } // A com.android.server.job.JobSchedulerService.Constants object. // Next tag: 29 message ConstantsProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -246,6 +249,14 @@ message ConstantsProto { // Whether to use heartbeats or rolling window for quota management. True // will use heartbeats, false will use a rolling window. reserved 23; // use_heartbeats // Whether to enable quota limits on APIs. optional bool enable_api_quotas = 31; // The maximum number of schedule() calls an app can make in a set amount of time. optional int32 api_quota_schedule_count = 32; // The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. optional int64 api_quota_schedule_window_ms = 33; // Whether or not to throw an exception when an app hits its schedule quota limit. optional bool api_quota_schedule_throw_exception = 34; message QuotaController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -331,7 +342,7 @@ message ConstantsProto { // In this time after screen turns on, we increase job concurrency. optional int32 screen_off_job_concurrency_increase_delay_ms = 28; // Next tag: 31 // Next tag: 35 } // Next tag: 4 Loading Loading @@ -651,8 +662,7 @@ message StateControllerProto { optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; // How many background jobs are currently running. Valid only if the device is_active // is true. // How many background jobs are currently running. Valid only if is_active is true. optional int32 bg_job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; Loading services/core/java/com/android/server/utils/quota/QuotaTracker.java +11 −4 Original line number Diff line number Diff line Loading @@ -172,6 +172,16 @@ abstract class QuotaTracker { // Exposed API to users. /** Remove all saved events from the tracker. */ public void clear() { synchronized (mLock) { mInQuotaAlarmListener.clearLocked(); mFreeQuota.clear(); dropEverythingLocked(); } } /** * @return true if the UPTC is within quota, false otherwise. * @throws IllegalStateException if given categorizer returns a Category that's not recognized. Loading Loading @@ -245,10 +255,7 @@ abstract class QuotaTracker { mIsEnabled = enable; if (!mIsEnabled) { mInQuotaAlarmListener.clearLocked(); mFreeQuota.clear(); dropEverythingLocked(); clear(); } } } Loading Loading
apex/jobscheduler/service/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ java_library { ], libs: [ "app-compat-annotations", "framework", "services.core", ], Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +138 −1 Original line number Diff line number Diff line Loading @@ -37,12 +37,15 @@ import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; Loading @@ -54,6 +57,7 @@ import android.net.Uri; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; Loading @@ -67,6 +71,7 @@ import android.os.UserManagerInternal; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; Loading @@ -85,6 +90,7 @@ import com.android.server.AppStateTracker; import com.android.server.DeviceIdleInternal; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.compat.PlatformCompat; import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; import com.android.server.job.controllers.BackgroundJobsController; Loading @@ -102,6 +108,9 @@ import com.android.server.job.restrictions.JobRestriction; import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import com.android.server.utils.quota.Categorizer; import com.android.server.utils.quota.Category; import com.android.server.utils.quota.CountQuotaTracker; import libcore.util.EmptyArray; Loading Loading @@ -145,6 +154,16 @@ public class JobSchedulerService extends com.android.server.SystemService /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; /** * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app * calls the APIs too frequently. */ @ChangeId // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up. @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L; @VisibleForTesting public static Clock sSystemClock = Clock.systemUTC(); Loading Loading @@ -237,6 +256,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ private final List<JobRestriction> mJobRestrictions; private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_TAG = ".schedule()"; private final PlatformCompat mPlatformCompat; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. Loading Loading @@ -275,6 +298,11 @@ public class JobSchedulerService extends com.android.server.SystemService */ final SparseIntArray mBackingUpUids = new SparseIntArray(); /** * Cache of debuggable app status. */ final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>(); /** * Named indices into standby bucket arrays, for clarity in referring to * specific buckets' bookkeeping. Loading Loading @@ -315,6 +343,10 @@ public class JobSchedulerService extends com.android.server.SystemService final StateController sc = mControllers.get(controller); sc.onConstantsUpdatedLocked(); } mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS); mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. Loading Loading @@ -466,6 +498,11 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats"; private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas"; private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count"; private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms"; private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION = "aq_schedule_throw_exception"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; Loading @@ -484,6 +521,10 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; private static final boolean DEFAULT_ENABLE_API_QUOTAS = true; private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 500; private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS; private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true; /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things Loading Loading @@ -618,6 +659,24 @@ public class JobSchedulerService extends com.android.server.SystemService */ public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; /** * Whether to enable quota limits on APIs. */ public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS; /** * The maximum number of schedule() calls an app can make in a set amount of time. */ public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT; /** * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. */ public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS; /** * Whether to throw an exception when an app hits its schedule quota limit. */ public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION = DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION; private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { Loading Loading @@ -678,6 +737,16 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_CONN_CONGESTION_DELAY_FRAC); CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS); API_QUOTA_SCHEDULE_COUNT = Math.max(250, mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT)); API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis( KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS); API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean( KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION); } void dump(IndentingPrintWriter pw) { Loading Loading @@ -716,6 +785,12 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); pw.printPair(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, API_QUOTA_SCHEDULE_THROW_EXCEPTION).println(); pw.decreaseIndent(); } Loading Loading @@ -746,6 +821,12 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION, API_QUOTA_SCHEDULE_THROW_EXCEPTION); } } Loading Loading @@ -847,6 +928,7 @@ public class JobSchedulerService extends com.android.server.SystemService for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } mDebuggableApps.remove(pkgName); } } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { Loading Loading @@ -972,6 +1054,45 @@ public class JobSchedulerService extends com.android.server.SystemService public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { final String pkg = packageName == null ? job.getService().getPackageName() : packageName; if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG)) { Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times"); // TODO(b/145551233): attempt to restrict app if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION && mPlatformCompat.isChangeEnabledByPackageName( CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) { final boolean isDebuggable; synchronized (mLock) { if (!mDebuggableApps.containsKey(packageName)) { try { final ApplicationInfo appInfo = AppGlobals.getPackageManager() .getApplicationInfo(pkg, 0, userId); if (appInfo != null) { mDebuggableApps.put(packageName, (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); } else { return JobScheduler.RESULT_FAILURE; } } catch (RemoteException e) { throw new RuntimeException(e); } } isDebuggable = mDebuggableApps.get(packageName); } if (isDebuggable) { // Only throw the exception for debuggable apps. throw new IllegalStateException( "schedule()/enqueue() called more than " + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY) + " times in the past " + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY) + "ms"); } } return JobScheduler.RESULT_FAILURE; } mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG); try { if (ActivityManager.getService().isAppStartModeDisabled(uId, job.getService().getPackageName())) { Loading Loading @@ -1296,6 +1417,12 @@ public class JobSchedulerService extends com.android.server.SystemService // Set up the app standby bucketing tracker mStandbyTracker = new StandbyTracker(); mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mPlatformCompat = (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER); mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); appStandby.addListener(mStandbyTracker); Loading Loading @@ -2745,7 +2872,7 @@ public class JobSchedulerService extends com.android.server.SystemService return new ParceledListSlice<>(snapshots); } } }; } // Shell command infrastructure: run the given job immediately int executeRunCommand(String pkgName, int userId, int jobId, boolean force) { Loading Loading @@ -2968,6 +3095,10 @@ public class JobSchedulerService extends com.android.server.SystemService return 0; } void resetScheduleQuota() { mQuotaTracker.clear(); } void triggerDockState(boolean idleState) { final Intent dockIntent; if (idleState) { Loading Loading @@ -3030,6 +3161,9 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); mQuotaTracker.dump(pw); pw.println(); pw.println("Started users: " + Arrays.toString(mStartedUsers)); pw.print("Registered "); pw.print(mJobs.size()); Loading Loading @@ -3217,6 +3351,9 @@ public class JobSchedulerService extends com.android.server.SystemService for (int u : mStartedUsers) { proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u); } mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER); if (mJobs.size() > 0) { final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs(); sortJobs(jobs); Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +14 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,8 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return getJobState(pw); case "heartbeat": return doHeartbeat(pw); case "reset-schedule-quota": return resetScheduleQuota(pw); case "trigger-dock-state": return triggerDockState(pw); default: Loading Loading @@ -344,6 +346,18 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return -1; } private int resetScheduleQuota(PrintWriter pw) throws Exception { checkPermission("reset schedule quota"); final long ident = Binder.clearCallingIdentity(); try { mInternal.resetScheduleQuota(); } finally { Binder.restoreCallingIdentity(ident); } return 0; } private int triggerDockState(PrintWriter pw) throws Exception { checkPermission("trigger wireless charging dock state"); Loading
core/proto/android/server/jobscheduler.proto +15 −5 Original line number Diff line number Diff line Loading @@ -32,8 +32,8 @@ import "frameworks/base/core/proto/android/server/appstatetracker.proto"; import "frameworks/base/core/proto/android/server/job/enums.proto"; import "frameworks/base/core/proto/android/server/statlogger.proto"; import "frameworks/base/core/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/util/quotatracker.proto"; // Next tag: 21 message JobSchedulerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -160,10 +160,13 @@ message JobSchedulerServiceDumpProto { optional JobConcurrencyManagerProto concurrency_manager = 20; optional JobStorePersistStatsProto persist_stats = 21; optional .android.util.quota.CountQuotaTrackerProto quota_tracker = 22; // Next tag: 23 } // A com.android.server.job.JobSchedulerService.Constants object. // Next tag: 29 message ConstantsProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -246,6 +249,14 @@ message ConstantsProto { // Whether to use heartbeats or rolling window for quota management. True // will use heartbeats, false will use a rolling window. reserved 23; // use_heartbeats // Whether to enable quota limits on APIs. optional bool enable_api_quotas = 31; // The maximum number of schedule() calls an app can make in a set amount of time. optional int32 api_quota_schedule_count = 32; // The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. optional int64 api_quota_schedule_window_ms = 33; // Whether or not to throw an exception when an app hits its schedule quota limit. optional bool api_quota_schedule_throw_exception = 34; message QuotaController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -331,7 +342,7 @@ message ConstantsProto { // In this time after screen turns on, we increase job concurrency. optional int32 screen_off_job_concurrency_increase_delay_ms = 28; // Next tag: 31 // Next tag: 35 } // Next tag: 4 Loading Loading @@ -651,8 +662,7 @@ message StateControllerProto { optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; // How many background jobs are currently running. Valid only if the device is_active // is true. // How many background jobs are currently running. Valid only if is_active is true. optional int32 bg_job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; Loading
services/core/java/com/android/server/utils/quota/QuotaTracker.java +11 −4 Original line number Diff line number Diff line Loading @@ -172,6 +172,16 @@ abstract class QuotaTracker { // Exposed API to users. /** Remove all saved events from the tracker. */ public void clear() { synchronized (mLock) { mInQuotaAlarmListener.clearLocked(); mFreeQuota.clear(); dropEverythingLocked(); } } /** * @return true if the UPTC is within quota, false otherwise. * @throws IllegalStateException if given categorizer returns a Category that's not recognized. Loading Loading @@ -245,10 +255,7 @@ abstract class QuotaTracker { mIsEnabled = enable; if (!mIsEnabled) { mInQuotaAlarmListener.clearLocked(); mFreeQuota.clear(); dropEverythingLocked(); clear(); } } } Loading