diff options
Diffstat (limited to 'apex')
12 files changed, 467 insertions, 67 deletions
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index f6ae56f01758..5b3b876edd3a 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -32,11 +32,13 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; +import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import java.io.Closeable; import java.io.IOException; import java.util.List; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -153,6 +155,26 @@ public class BlobStoreManager { private final Context mContext; private final IBlobStoreManager mService; + // TODO: b/404309424 - Make these constants available using a test-api to avoid hardcoding + // them in tests. + /** + * The maximum allowed length for the package name, provided using + * {@link BlobStoreManager.Session#allowPackageAccess(String, byte[])}. + * + * This is the same limit that is already used for limiting the length of the package names + * at android.content.pm.parsing.FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE. + * + * @hide + */ + public static final int MAX_PACKAGE_NAME_LENGTH = 223; + /** + * The maximum allowed length for the certificate, provided using + * {@link BlobStoreManager.Session#allowPackageAccess(String, byte[])}. + * + * @hide + */ + public static final int MAX_CERTIFICATE_LENGTH = 32; + /** @hide */ public BlobStoreManager(@NonNull Context context, @NonNull IBlobStoreManager service) { mContext = context; @@ -786,6 +808,12 @@ public class BlobStoreManager { */ public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) throws IOException { + Objects.requireNonNull(packageName); + Preconditions.checkArgument(packageName.length() <= MAX_PACKAGE_NAME_LENGTH, + "packageName is longer than " + MAX_PACKAGE_NAME_LENGTH + " chars"); + Objects.requireNonNull(certificate); + Preconditions.checkArgument(certificate.length <= MAX_CERTIFICATE_LENGTH, + "certificate is longer than " + MAX_CERTIFICATE_LENGTH + " chars"); try { mSession.allowPackageAccess(packageName, certificate); } catch (ParcelableException e) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index ede29ec168c0..790d4e934317 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -16,6 +16,8 @@ package com.android.server.blob; import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; +import static android.app.blob.BlobStoreManager.MAX_CERTIFICATE_LENGTH; +import static android.app.blob.BlobStoreManager.MAX_PACKAGE_NAME_LENGTH; import static android.app.blob.XmlTags.ATTR_CREATION_TIME_MS; import static android.app.blob.XmlTags.ATTR_ID; import static android.app.blob.XmlTags.ATTR_PACKAGE; @@ -328,6 +330,11 @@ class BlobStoreSession extends IBlobStoreSession.Stub { @NonNull byte[] certificate) { assertCallerIsOwner(); Objects.requireNonNull(packageName, "packageName must not be null"); + Preconditions.checkArgument(packageName.length() <= MAX_PACKAGE_NAME_LENGTH, + "packageName is longer than " + MAX_PACKAGE_NAME_LENGTH + " chars"); + Objects.requireNonNull(certificate, "certificate must not be null"); + Preconditions.checkArgument(certificate.length <= MAX_CERTIFICATE_LENGTH, + "certificate is longer than " + MAX_CERTIFICATE_LENGTH + " chars"); synchronized (mSessionLock) { if (mState != STATE_OPENED) { throw new IllegalStateException("Not allowed to change access type in state: " diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index cc2d104813e4..d48af2cccd59 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -810,7 +810,7 @@ public class JobInfo implements Parcelable { /** * <p class="caution"><strong>Note:</strong> Beginning with - * {@link android.os.Build.VERSION_CODES#B}, this flag will be ignored and no longer + * {@link android.os.Build.VERSION_CODES#BAKLAVA}, this flag will be ignored and no longer * function effectively, regardless of the calling app's target SDK version. * Calling this method will always return {@code false}. * @@ -2137,9 +2137,9 @@ public class JobInfo implements Parcelable { * Jobs marked as important-while-foreground are given {@link #PRIORITY_HIGH} by default. * * <p class="caution"><strong>Note:</strong> Beginning with - * {@link android.os.Build.VERSION_CODES#B}, this flag will be ignored and no longer + * {@link android.os.Build.VERSION_CODES#BAKLAVA}, this flag will be ignored and no longer * function effectively, regardless of the calling app's target SDK version. - * {link #isImportantWhileForeground()} will always return {@code false}. + * {@link #isImportantWhileForeground()} will always return {@code false}. * Apps should use {link #setExpedited(boolean)} with {@code true} to indicate * that this job is important and needs to run as soon as possible. * diff --git a/apex/jobscheduler/service/aconfig/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig index 74d2a590086f..899341016148 100644 --- a/apex/jobscheduler/service/aconfig/app_idle.aconfig +++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig @@ -28,3 +28,14 @@ flag { description: "Adjust the default bucket evaluation parameters" bug: "379909479" } + +flag { + name: "persist_restore_to_rare_apps_list" + namespace: "backstage_power" + description: "Persist the list of apps which are put in the RARE bucket upon restore." + is_fixed_read_only: true + bug: "383766428" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 86ed06bf4e3d..aae5bb31273b 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -105,4 +105,36 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "include_trace_tag_in_job_name" + namespace: "backstage_power" + description: "Add the trace tag to the job name" + bug: "354795473" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "additional_quota_for_system_installer" + namespace: "backstage_power" + description: "Offer additional quota for system installer" + bug: "398264531" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "tune_quota_window_default_parameters" + namespace: "backstage_power" + description: "Tune default active/exempted bucket quota parameters" + bug: "401767691" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index f89b13dce307..44e4999ccf44 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2995,6 +2995,8 @@ public class AlarmManagerService extends SystemService { pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS, Flags.startUserBeforeScheduledAlarms()); pw.println(); + pw.print(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND, Flags.acquireWakelockBeforeSend()); + pw.println(); pw.decreaseIndent(); pw.println(); @@ -5367,6 +5369,11 @@ public class AlarmManagerService extends SystemService { // to do any wakelock or stats tracking, so we have nothing // left to do here but go on to the next thing. mSendFinishCount++; + if (Flags.acquireWakelockBeforeSend() && mBroadcastRefCount == 0) { + // No other alarms are in-flight and this dispatch failed. We will + // acquire the wakelock again before the next dispatch. + mWakeLock.release(); + } return; } } else { @@ -5404,6 +5411,11 @@ public class AlarmManagerService extends SystemService { // stats management to do. It threw before we posted the delayed // timeout message, so we're done here. mListenerFinishCount++; + if (Flags.acquireWakelockBeforeSend() && mBroadcastRefCount == 0) { + // No other alarms are in-flight and this dispatch failed. We will + // acquire the wakelock again before the next dispatch. + mWakeLock.release(); + } return; } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index ebfda527001d..010006edc995 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -809,7 +809,11 @@ public final class JobServiceContext implements ServiceConnection { if (!verifyCallerLocked(cb)) { return; } - + if (mVerb != VERB_EXECUTING) { + // Any state other than executing means the + // job is in transient or stopped state + return; + } executing = getRunningJobLocked(); } if (executing != null && jobId == executing.getJobId()) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index abec170f3b7d..d52bbc245157 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -18,6 +18,7 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.NonNull; import android.app.job.JobInfo; import android.content.BroadcastReceiver; import android.content.Context; @@ -28,6 +29,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Log; @@ -56,7 +58,10 @@ public final class DeviceIdleJobsController extends StateController { private static final boolean DEBUG = JobSchedulerService.DEBUG || Log.isLoggable(TAG, Log.DEBUG); - private static final long BACKGROUND_JOBS_DELAY = 3000; + /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ + private static final String DIJC_CONSTANT_PREFIX = "dijc_"; + private static final String KEY_BACKGROUND_JOBS_DELAY_MS = + DIJC_CONSTANT_PREFIX + "background_jobs_delay_ms"; static final int PROCESS_BACKGROUND_JOBS = 1; @@ -78,6 +83,8 @@ public final class DeviceIdleJobsController extends StateController { private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; + private long mBackgroundJobsDelay; + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -128,6 +135,9 @@ public final class DeviceIdleJobsController extends StateController { public DeviceIdleJobsController(JobSchedulerService service) { super(service); + mBackgroundJobsDelay = mContext.getResources().getInteger( + com.android.internal.R.integer.config_jobSchedulerBackgroundJobsDelay); + mHandler = new DeviceIdleJobsDelayHandler(AppSchedulingModuleThread.get().getLooper()); // Register for device idle mode changes mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -165,7 +175,7 @@ public final class DeviceIdleJobsController extends StateController { // When coming out of doze, process all foreground uids and EJs immediately, // while others will be processed after a delay of 3 seconds. mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor); - mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); + mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, mBackgroundJobsDelay); } } // Inform the job scheduler service about idle mode changes @@ -237,6 +247,26 @@ public final class DeviceIdleJobsController extends StateController { } @Override + public void processConstantLocked(@NonNull DeviceConfig.Properties properties, + @NonNull String key) { + switch (key) { + case KEY_BACKGROUND_JOBS_DELAY_MS: + mBackgroundJobsDelay = Math.max(0, properties.getLong(key, mBackgroundJobsDelay)); + break; + } + } + + @Override + public void dumpConstants(IndentingPrintWriter pw) { + pw.println(); + pw.print(DeviceIdleJobsController.class.getSimpleName()); + pw.println(":"); + pw.increaseIndent(); + pw.print(KEY_BACKGROUND_JOBS_DELAY_MS, mBackgroundJobsDelay).println(); + pw.decreaseIndent(); + } + + @Override public void dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate) { pw.println("Idle mode: " + mDeviceIdleMode); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 4b9d7364e27b..2d069f934d0d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -673,10 +673,22 @@ public final class JobStatus { this.job = job; - final String bnNamespace = namespace == null ? "" : "@" + namespace + "@"; - this.batteryName = this.sourceTag != null - ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName() - : bnNamespace + job.getService().flattenToShortString(); + StringBuilder batteryName = new StringBuilder(); + if (com.android.server.job.Flags.includeTraceTagInJobName()) { + final String filteredTraceTag = this.getFilteredTraceTag(); + if (filteredTraceTag != null) { + batteryName.append("#").append(filteredTraceTag).append("#"); + } + } + if (namespace != null) { + batteryName.append("@").append(namespace).append("@"); + } + if (sourceTag != null) { + batteryName.append(sourceTag).append(":").append(job.getService().getPackageName()); + } else { + batteryName.append(job.getService().flattenToShortString()); + } + this.batteryName = batteryName.toString(); final String componentPackage = job.getService().getPackageName(); mIsProxyJob = !this.sourcePackageName.equals(componentPackage); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 637c726a9bd1..6dd7521e4d43 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -36,7 +36,6 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; -import android.app.compat.CompatChanges; import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; @@ -54,6 +53,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -74,6 +74,7 @@ import com.android.internal.util.ArrayUtils; import com.android.server.AppSchedulingModuleThread; import com.android.server.LocalServices; import com.android.server.PowerAllowlistInternal; +import com.android.server.compat.PlatformCompat; import com.android.server.job.ConstantsProto; import com.android.server.job.Flags; import com.android.server.job.JobSchedulerService; @@ -157,6 +158,15 @@ public final class QuotaController extends StateController { @Overridable // The change can be overridden in user build. static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L; + /** + * When enabled this change id overrides the default quota parameters adjustment. + */ + @VisibleForTesting + @ChangeId + @Disabled // Disabled by default + @Overridable // The change can be overridden in user build. + static final long OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS = 378129159L; + @VisibleForTesting static class ExecutionStats { /** @@ -360,13 +370,13 @@ public final class QuotaController extends StateController { /** How much time each app will have to run jobs within their standby bucket window. */ private final long[] mAllowedTimePerPeriodMs = new long[]{ - QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS, 0, // NEVER QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, - QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS + QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS }; /** @@ -494,6 +504,9 @@ public final class QuotaController extends StateController { private long mEjLimitAdditionSpecialMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS; + private long mAllowedTimePeriodAdditionaInstallerMs = + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + /** * The period of time used to calculate expedited job sessions. Apps can only have expedited job * sessions totalling {@link #mEJLimitsMs}[bucket within this period of time (without factoring @@ -533,6 +546,8 @@ public final class QuotaController extends StateController { */ private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>(); + private final PlatformCompat mPlatformCompat; + /** An app has reached its quota. The message should contain a {@link UserPackage} object. */ @VisibleForTesting static final int MSG_REACHED_TIME_QUOTA = 0; @@ -584,6 +599,13 @@ public final class QuotaController extends StateController { PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class); pai.registerTempAllowlistChangeListener(new TempAllowlistTracker()); + mPlatformCompat = (PlatformCompat) + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + if (Flags.adjustQuotaDefaultConstants()) { + mPlatformCompat.registerListener(OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS, + (packageName) -> handleQuotaDefaultConstantsCompatChange()); + } + try { ActivityManager.getService().registerUidObserver(new QcUidObserver(), ActivityManager.UID_OBSERVER_PROCSTATE, @@ -648,8 +670,9 @@ public final class QuotaController extends StateController { final int uid = jobStatus.getSourceUid(); if ((!Flags.enforceQuotaPolicyToTopStartedJobs() - || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - uid)) && mTopAppCache.get(uid)) { + || mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, uid)) + && mTopAppCache.get(uid)) { if (DEBUG) { Slog.d(TAG, jobStatus.toShortString() + " is top started job"); } @@ -687,8 +710,8 @@ public final class QuotaController extends StateController { } } if (!Flags.enforceQuotaPolicyToTopStartedJobs() - || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - jobStatus.getSourceUid())) { + || mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { mTopStartedJobs.remove(jobStatus); } } @@ -802,8 +825,8 @@ public final class QuotaController extends StateController { /** @return true if the job was started while the app was in the TOP state. */ private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { if (!Flags.enforceQuotaPolicyToTopStartedJobs() - || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - jobStatus.getSourceUid())) { + || mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { return mTopStartedJobs.contains(jobStatus); } @@ -1095,6 +1118,19 @@ public final class QuotaController extends StateController { return baseLimitMs; } + private long getAllowedTimePerPeriodMsLocked(final int userId, @NonNull final String pkgName, + final int standbyBucket) { + final long baseLimitMs = mAllowedTimePerPeriodMs[standbyBucket]; + if (Flags.adjustQuotaDefaultConstants() + && !isCompatOverridedForQuotaConstantAdjustment() + && Flags.additionalQuotaForSystemInstaller() + && standbyBucket == EXEMPTED_INDEX + && mSystemInstallers.contains(userId, pkgName)) { + return baseLimitMs + mAllowedTimePeriodAdditionaInstallerMs; + } + return baseLimitMs; + } + /** * Returns the amount of time, in milliseconds, until the package would have reached its * duration quota, assuming it has a job counting towards its quota the entire time. This takes @@ -1112,25 +1148,26 @@ public final class QuotaController extends StateController { List<TimedEvent> events = mTimingEvents.get(userId, packageName); final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + final long allowedTimePerPeriodMs = + getAllowedTimePerPeriodMsLocked(userId, packageName, standbyBucket); if (events == null || events.size() == 0) { // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. - if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { + if (stats.windowSizeMs == allowedTimePerPeriodMs) { return mMaxExecutionTimeMs; } - return mAllowedTimePerPeriodMs[standbyBucket]; + return allowedTimePerPeriodMs; } final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; - final long allowedTimePerPeriodMs = mAllowedTimePerPeriodMs[standbyBucket]; final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs; final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. - if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { + if (stats.windowSizeMs == allowedTimePerPeriodMs) { return calculateTimeUntilQuotaConsumedLocked( events, startMaxElapsed, maxExecutionTimeRemainingMs); } @@ -1270,7 +1307,8 @@ public final class QuotaController extends StateController { appStats[standbyBucket] = stats; } if (refreshStatsIfOld) { - final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket]; + final long bucketAllowedTimeMs = + getAllowedTimePerPeriodMsLocked(userId, packageName, standbyBucket); final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; final int jobCountLimit = mMaxBucketJobCounts[standbyBucket]; final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket]; @@ -1456,10 +1494,21 @@ public final class QuotaController extends StateController { } } + void handleQuotaDefaultConstantsCompatChange() { + synchronized (mLock) { + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + mQcConstants.adjustDefaultBucketWindowSizes(isCompatEnabled); + mQcConstants.adjustDefaultEjLimits(isCompatEnabled); + mQcConstants.mShouldReevaluateConstraints = true; + onConstantsUpdatedLocked(); + } + } + void processQuotaConstantsAdjustment() { - if (Flags.adjustQuotaDefaultConstants()) { - mQcConstants.adjustDefaultBucketWindowSizes(); - mQcConstants.adjustDefaultEjLimits(); + if (Flags.adjustQuotaDefaultConstants() + && !isCompatOverridedForQuotaConstantAdjustment()) { + mQcConstants.adjustDefaultBucketWindowSizes(false); + mQcConstants.adjustDefaultEjLimits(false); } } @@ -1488,6 +1537,11 @@ public final class QuotaController extends StateController { } } + private boolean isCompatOverridedForQuotaConstantAdjustment() { + return mPlatformCompat.isChangeEnabledByPackageName( + OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS, "android", UserHandle.USER_SYSTEM); + } + private void incrementTimingSessionCountLocked(final int userId, @NonNull final String packageName) { final long now = sElapsedRealtimeClock.millis(); @@ -1845,9 +1899,10 @@ public final class QuotaController extends StateController { final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats); final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats); final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName); - + final long allowedTimePerPeriosMs = + getAllowedTimePerPeriodMsLocked(userId, packageName, standbyBucket); final boolean inRegularQuota = - stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs[standbyBucket] + stats.executionTimeInWindowMs < allowedTimePerPeriosMs && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs && isUnderJobCountQuota && isUnderTimingSessionCountQuota; @@ -2671,7 +2726,8 @@ public final class QuotaController extends StateController { @VisibleForTesting int getProcessStateQuotaFreeThreshold(int uid) { if (Flags.enforceQuotaPolicyToFgsJobs() - && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { + && !mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { return ActivityManager.PROCESS_STATE_BOUND_TOP; } @@ -3037,6 +3093,9 @@ public final class QuotaController extends StateController { static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms"; @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_addition_installer_ms"; + @VisibleForTesting static final String KEY_IN_QUOTA_BUFFER_MS = QC_CONSTANT_PREFIX + "in_quota_buffer_ms"; @VisibleForTesting @@ -3157,9 +3216,11 @@ public final class QuotaController extends StateController { static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + // Legacy default time each app will have to run jobs within EXEMPTED bucket + private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 10 * 60 * 1000L; // 10 minutes - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + // Legacy default time each app will have to run jobs within ACTIVE bucket + private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 10 * 60 * 1000L; // 10 minutes @@ -3169,14 +3230,28 @@ public final class QuotaController extends StateController { 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + 10 * 60 * 1000L; // 10 minutes + + // Current default time each app will have to run jobs within EXEMPTED bucket + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + 20 * 60 * 1000L; // 20 minutes + // Current default time each app will have to run jobs within ACTIVE bucket + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + 20 * 60 * 1000L; // 20 minutes + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + 20 * 60 * 1000L; // 20 minutes + private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds // Legacy default window size for EXEMPTED bucket + // EXEMPT apps can run jobs at any time private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // Legacy default window size for ACTIVE bucket + // ACTIVE apps can run jobs at any time private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // Legacy default window size for WORKING bucket private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours @@ -3193,6 +3268,13 @@ public final class QuotaController extends StateController { private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS = 12 * 60 * 60 * 1000L; // 12 hours + // Latest default window size for EXEMPTED bucket. + private static final long DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS = + 40 * 60 * 1000L; // 40 minutes. + // Latest default window size for ACTIVE bucket. + private static final long DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS = + 60 * 60 * 1000L; // 60 minutes. + private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = @@ -3253,12 +3335,13 @@ public final class QuotaController extends StateController { * bucket window. */ public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; /** * How much time each app in the active bucket will have to run jobs within their standby * bucket window. */ - public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; /** * How much time each app in the working set bucket will have to run jobs within their * standby bucket window. @@ -3509,6 +3592,9 @@ public final class QuotaController extends StateController { */ public long EJ_LIMIT_ADDITION_INSTALLER_MS = DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS; + public long ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + /** * The period of time used to calculate expedited job sessions. Apps can only have expedited * job sessions totalling EJ_LIMIT_<bucket>_MS within this period of time (without factoring @@ -3548,11 +3634,45 @@ public final class QuotaController extends StateController { */ public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; - void adjustDefaultBucketWindowSizes() { - WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; - WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; - WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; - WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; + void adjustDefaultBucketWindowSizes(boolean useLegacyQuotaConstants) { + if (useLegacyQuotaConstants) { + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + + WINDOW_SIZE_EXEMPTED_MS = DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS; + WINDOW_SIZE_WORKING_MS = DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS; + WINDOW_SIZE_FREQUENT_MS = DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS; + } else { + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS : + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + + WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; + WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; + WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; + } + + mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); + mAllowedTimePerPeriodMs[ACTIVE_INDEX] = Math.min(MAX_PERIOD_MS, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)); mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max( mAllowedTimePerPeriodMs[EXEMPTED_INDEX], @@ -3566,12 +3686,22 @@ public final class QuotaController extends StateController { mBucketPeriodsMs[FREQUENT_INDEX] = Math.max( mAllowedTimePerPeriodMs[FREQUENT_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); + + mAllowedTimePeriodAdditionaInstallerMs = + Math.min(mBucketPeriodsMs[EXEMPTED_INDEX] + - mAllowedTimePerPeriodMs[EXEMPTED_INDEX], + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); } - void adjustDefaultEjLimits() { - EJ_LIMIT_WORKING_MS = DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS; - EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; - EJ_REWARD_INTERACTION_MS = DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS; + void adjustDefaultEjLimits(boolean useLegacyQuotaConstants) { + EJ_LIMIT_WORKING_MS = useLegacyQuotaConstants ? DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS + : DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS; + EJ_TOP_APP_TIME_CHUNK_SIZE_MS = useLegacyQuotaConstants + ? DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS : + DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; + EJ_REWARD_INTERACTION_MS = useLegacyQuotaConstants + ? DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS + : DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS; // The limit must be in the range [15 minutes, active limit]. mEJLimitsMs[WORKING_INDEX] = Math.max(15 * MINUTE_IN_MILLIS, @@ -3596,6 +3726,8 @@ public final class QuotaController extends StateController { public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + switch (key) { case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS: case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS: @@ -3603,6 +3735,7 @@ public final class QuotaController extends StateController { case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS: case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS: case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS: case KEY_IN_QUOTA_BUFFER_MS: case KEY_MAX_EXECUTION_TIME_MS: case KEY_WINDOW_SIZE_ACTIVE_MS: @@ -3762,7 +3895,8 @@ public final class QuotaController extends StateController { case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS: // We don't need to re-evaluate execution stats or constraint status for this. EJ_TOP_APP_TIME_CHUNK_SIZE_MS = - properties.getLong(key, Flags.adjustQuotaDefaultConstants() + properties.getLong(key, + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS : DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS); // Limit chunking to be in the range [1 millisecond, 15 minutes] per event. @@ -3800,7 +3934,8 @@ public final class QuotaController extends StateController { case KEY_EJ_REWARD_INTERACTION_MS: // We don't need to re-evaluate execution stats or constraint status for this. EJ_REWARD_INTERACTION_MS = - properties.getLong(key, Flags.adjustQuotaDefaultConstants() + properties.getLong(key, + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS : DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS); // Limit interaction reward to be in the range [5 seconds, 15 minutes] per @@ -3841,13 +3976,15 @@ public final class QuotaController extends StateController { } mExecutionPeriodConstantsUpdated = true; + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, - KEY_IN_QUOTA_BUFFER_MS, + KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS, KEY_IN_QUOTA_BUFFER_MS, KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS, KEY_WINDOW_SIZE_WORKING_MS, @@ -3855,10 +3992,14 @@ public final class QuotaController extends StateController { KEY_WINDOW_SIZE_RESTRICTED_MS); ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); ALLOWED_TIME_PER_PERIOD_WORKING_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS); @@ -3871,26 +4012,37 @@ public final class QuotaController extends StateController { ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS); + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS, + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS + : DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS); MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, - Flags.adjustQuotaDefaultConstants() - ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : - DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS); + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled + && Flags.tuneQuotaWindowDefaultParameters()) + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled + ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS)); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, - Flags.adjustQuotaDefaultConstants() - ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : - DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS); + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled + && Flags.tuneQuotaWindowDefaultParameters()) + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled + ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS)); WINDOW_SIZE_WORKING_MS = properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, - Flags.adjustQuotaDefaultConstants() + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS : DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS); WINDOW_SIZE_FREQUENT_MS = properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS, - Flags.adjustQuotaDefaultConstants() + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS : DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS); WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS, @@ -3995,6 +4147,18 @@ public final class QuotaController extends StateController { mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs; mShouldReevaluateConstraints = true; } + + if (Flags.additionalQuotaForSystemInstaller()) { + // The additions must be in the range + // [0 minutes, exempted window size - active limit]. + long newAdditionInstallerMs = Math.max(0, + Math.min(mBucketPeriodsMs[EXEMPTED_INDEX] - newAllowedTimeExemptedMs, + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS)); + if (mAllowedTimePeriodAdditionaInstallerMs != newAdditionInstallerMs) { + mAllowedTimePeriodAdditionaInstallerMs = newAdditionInstallerMs; + mShouldReevaluateConstraints = true; + } + } } private void updateRateLimitingConstantsLocked() { @@ -4049,6 +4213,8 @@ public final class QuotaController extends StateController { } mEJLimitConstantsUpdated = true; + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, @@ -4063,7 +4229,7 @@ public final class QuotaController extends StateController { EJ_LIMIT_ACTIVE_MS = properties.getLong( KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); EJ_LIMIT_WORKING_MS = properties.getLong( - KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants() + KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS : DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS); EJ_LIMIT_FREQUENT_MS = properties.getLong( @@ -4159,6 +4325,8 @@ public final class QuotaController extends StateController { .println(); pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS, + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS).println(); pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println(); pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println(); pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println(); @@ -4335,6 +4503,11 @@ public final class QuotaController extends StateController { } @VisibleForTesting + long getAllowedTimePeriodAdditionInstallerMs() { + return mAllowedTimePeriodAdditionaInstallerMs; + } + + @VisibleForTesting long getEjLimitAdditionSpecialMs() { return mEjLimitAdditionSpecialMs; } @@ -4435,6 +4608,8 @@ public final class QuotaController extends StateController { + ": " + Flags.enforceQuotaPolicyToFgsJobs()); pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS + ": " + Flags.enforceQuotaPolicyToTopStartedJobs()); + pw.println(" " + Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER + + ": " + Flags.additionalQuotaForSystemInstaller()); pw.println(); pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index a8641ae43509..4acfebc536eb 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -38,6 +38,7 @@ import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager; import android.os.SystemClock; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -83,6 +84,9 @@ public class AppIdleHistory { private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); private static final long ONE_MINUTE = 60 * 1000; + // Only keep the persisted restore-to-rare apps list for 2 days. + static final long RESTORE_TO_RARE_APPS_LIST_EXPIRY = ONE_MINUTE * 60 * 24 * 2; + static final int STANDBY_BUCKET_UNKNOWN = -1; /** @@ -277,6 +281,58 @@ public class AppIdleHistory { writeScreenOnTime(); } + private File getRestoreToRareAppsListFile(int userId) { + return new File(getUserDirectory(userId), "restore_to_rare_apps_list"); + } + + public ArraySet<String> readRestoreToRareAppsList(int userId) { + File restoreToRareAppsListFile = getRestoreToRareAppsListFile(userId); + if (!restoreToRareAppsListFile.exists()) { + return null; + } + + try (BufferedReader reader = + new BufferedReader(new FileReader(restoreToRareAppsListFile))) { + final ArraySet<String> appsList = new ArraySet<>(); + final long restoreTime = Long.parseLong(reader.readLine()); + if (System.currentTimeMillis() - restoreTime > RESTORE_TO_RARE_APPS_LIST_EXPIRY) { + // the apps list should only be kept around for 2 days + reader.close(); + restoreToRareAppsListFile.delete(); + return null; + } + String pkgName; + while ((pkgName = reader.readLine()) != null) { + appsList.add(pkgName); + } + return appsList; + } catch (IOException | NumberFormatException e) { + return null; + } + } + + public void writeRestoreToRareAppsList(int userId, ArraySet<String> restoreAppsToRare) { + File fileHandle = getRestoreToRareAppsListFile(userId); + if (fileHandle.exists()) { + // don't update the persisted file - it should only be written once. + return; + } + AtomicFile restoreToRareAppsListFile = new AtomicFile(fileHandle); + FileOutputStream fos = null; + try { + fos = restoreToRareAppsListFile.startWrite(); + final StringBuilder sb = new StringBuilder(); + sb.append(System.currentTimeMillis()).append("\n"); + for (String pkgName : restoreAppsToRare) { + sb.append(pkgName).append("\n"); + } + fos.write(sb.toString().getBytes()); + restoreToRareAppsListFile.finishWrite(fos); + } catch (IOException ioe) { + restoreToRareAppsListFile.failWrite(fos); + } + } + /** * Mark the app as used and update the bucket if necessary. If there is a expiry time specified * that's in the future, then the usage event is temporary and keeps the app in the specified @@ -694,10 +750,13 @@ public class AppIdleHistory { return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0); } + private File getUserDirectory(int userId) { + return new File(new File(mStorageDir, "users"), Integer.toString(userId)); + } + @VisibleForTesting File getUserFile(int userId) { - return new File(new File(new File(mStorageDir, "users"), - Integer.toString(userId)), APP_IDLE_FILENAME); + return new File(getUserDirectory(userId), APP_IDLE_FILENAME); } void clearLastUsedTimestamps(String packageName, int userId) { diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 9871d713178e..b87b5ceebd98 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -264,6 +264,8 @@ public class AppStandbyController @GuardedBy("mCarrierPrivilegedLock") private boolean mHaveCarrierPrivilegedApps; + private final boolean mHasFeatureTelephonySubscription; + /** List of carrier-privileged apps that should be excluded from standby */ @GuardedBy("mCarrierPrivilegedLock") private List<String> mCarrierPrivilegedApps; @@ -603,6 +605,8 @@ public class AppStandbyController mContext = mInjector.getContext(); mHandler = new AppStandbyHandler(mInjector.getLooper()); mPackageManager = mContext.getPackageManager(); + mHasFeatureTelephonySubscription = mPackageManager.hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION); DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver(); IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); @@ -1515,7 +1519,7 @@ public class AppStandbyController } // Check this last, as it can be the most expensive check - if (isCarrierApp(packageName)) { + if (mHasFeatureTelephonySubscription && isCarrierApp(packageName)) { return STANDBY_BUCKET_EXEMPTED; } @@ -1702,10 +1706,18 @@ public class AppStandbyController restoreAppToRare(packageName, userId, nowElapsed, reason); } // Clear out the list of restored apps that need to have their standby buckets adjusted - // if they still haven't been installed eight hours after restore. - // Note: if the device reboots within these first 8 hours, this list will be lost since it's - // not persisted - this is the expected behavior for now and may be updated in the future. - mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), 8 * ONE_HOUR); + // if they still haven't been installed two days after initial restore. + final long delayMillis = Flags.persistRestoreToRareAppsList() + ? AppIdleHistory.RESTORE_TO_RARE_APPS_LIST_EXPIRY : 8 * ONE_HOUR; + mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), delayMillis); + + // Persist the file in case the device reboots within 2 days after the initial restore. + if (Flags.persistRestoreToRareAppsList()) { + synchronized (mAppIdleLock) { + mAppIdleHistory.writeRestoreToRareAppsList( + userId, mAppsToRestoreToRare.get(userId)); + } + } } /** Adjust the standby bucket of the given package for the user to RARE. */ @@ -2268,6 +2280,22 @@ public class AppStandbyController } else if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) { clearAppIdleForPackage(pkgName, userId); } else { + // Do a lazy read of the persisted list, if necessary. + if (Flags.persistRestoreToRareAppsList() + && mAppsToRestoreToRare.get(userId) == null) { + synchronized (mAppIdleLock) { + final ArraySet<String> restoredApps = + mAppIdleHistory.readRestoreToRareAppsList(userId); + if (restoredApps != null) { + mAppsToRestoreToRare.addAll(userId, restoredApps); + // Clear out the list of restored apps if they still haven't been + // installed in two days - at worst, we are allowing for up to + // 4 days for reinstallation (device reboots just before 2 days) + mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), + AppIdleHistory.RESTORE_TO_RARE_APPS_LIST_EXPIRY); + } + } + } // Package was just added and it's not being replaced. if (mAppsToRestoreToRare.contains(userId, pkgName)) { restoreAppToRare(pkgName, userId, mInjector.elapsedRealtime(), @@ -2450,6 +2478,8 @@ public class AppStandbyController + ": " + Flags.avoidIdleCheck()); pw.println(" " + Flags.FLAG_ADJUST_DEFAULT_BUCKET_ELEVATION_PARAMS + ": " + Flags.adjustDefaultBucketElevationParams()); + pw.println(" " + Flags.FLAG_PERSIST_RESTORE_TO_RARE_APPS_LIST + + ": " + Flags.persistRestoreToRareAppsList()); pw.println(); synchronized (mCarrierPrivilegedLock) { |