summaryrefslogtreecommitdiff
path: root/apex
diff options
context:
space:
mode:
Diffstat (limited to 'apex')
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java12
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig31
-rw-r--r--apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java22
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl7
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl3
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java25
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java69
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java56
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java10
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl19
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java100
-rw-r--r--apex/jobscheduler/service/aconfig/alarm.aconfig10
-rw-r--r--apex/jobscheduler/service/aconfig/app_idle.aconfig16
-rw-r--r--apex/jobscheduler/service/aconfig/device_idle.aconfig10
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig33
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java32
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java87
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java321
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java82
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java250
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java518
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java13
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java58
27 files changed, 1202 insertions, 608 deletions
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 3af36ebb08ca..e200434d9ff2 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1404,7 +1404,11 @@ public class BlobStoreManagerService extends SystemService {
if (LOGV) {
Slog.v(TAG, "Received " + intent);
}
- switch (intent.getAction()) {
+ final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ switch (action) {
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
case Intent.ACTION_PACKAGE_DATA_CLEARED:
final String packageName = intent.getData().getSchemeSpecificPart();
@@ -1431,7 +1435,11 @@ public class BlobStoreManagerService extends SystemService {
if (LOGV) {
Slog.v(TAG, "Received: " + intent);
}
- switch (intent.getAction()) {
+ final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ switch (action) {
case Intent.ACTION_USER_REMOVED:
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
USER_NULL);
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 5f5507587f72..63624d8cad4a 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -25,8 +25,33 @@ flag {
}
flag {
- name: "cleanup_empty_jobs"
+ name: "handle_abandoned_jobs"
namespace: "backstage_power"
- description: "Enables automatic cancellation of jobs due to leaked JobParameters, reducing unnecessary battery drain and improving system efficiency. This includes logging and traces for better issue diagnosis."
- bug: "349688611"
+ description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
+ bug: "372529068"
+ is_exported: true
+}
+
+flag {
+ name: "ignore_important_while_foreground"
+ namespace: "backstage_power"
+ description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
+ bug: "374175032"
+ is_exported: true
+}
+
+flag {
+ name: "get_pending_job_reasons_api"
+ is_exported: true
+ namespace: "backstage_power"
+ description: "Introduce a new getPendingJobReasons() API which returns reasons why a job may not have executed. Also deprecate the existing getPendingJobReason() API."
+ bug: "372031023"
+}
+
+flag {
+ name: "get_pending_job_reasons_history_api"
+ is_exported: true
+ namespace: "backstage_power"
+ description: "Introduce a new getPendingJobReasonsHistory() API which returns a limited historical view of getPendingJobReasons()."
+ bug: "372031023"
}
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 3cfddc6d8e2b..e9b11f46ddde 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -25,11 +25,13 @@ import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
+import android.app.job.PendingJobReasonsInfo;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.util.ArrayMap;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -173,6 +175,26 @@ public class JobSchedulerImpl extends JobScheduler {
}
@Override
+ @NonNull
+ public int[] getPendingJobReasons(int jobId) {
+ try {
+ return mBinder.getPendingJobReasons(mNamespace, jobId);
+ } catch (RemoteException e) {
+ return new int[] { PENDING_JOB_REASON_UNDEFINED };
+ }
+ }
+
+ @Override
+ @NonNull
+ public List<PendingJobReasonsInfo> getPendingJobReasonsHistory(int jobId) {
+ try {
+ return mBinder.getPendingJobReasonsHistory(mNamespace, jobId);
+ } catch (RemoteException e) {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ @Override
public boolean canRunUserInitiatedJobs() {
try {
return mBinder.canRunUserInitiatedJobs(mContext.getOpPackageName());
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index 11d17ca749b7..887f812aebca 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -87,11 +87,12 @@ interface IJobCallback {
void jobFinished(int jobId, boolean reschedule);
/*
- * Inform JobScheduler to force finish this job because the client has lost
- * the job handle. jobFinished can no longer be called from the client.
+ * Inform JobScheduler that this job may have been abandoned because the client process
+ * has lost strong references to the JobParameters object without calling jobFinished.
+ *
* @param jobId Unique integer used to identify this job
*/
- void forceJobFinished(int jobId);
+ void handleAbandonedJob(int jobId);
/*
* Inform JobScheduler of a change in the estimated transfer payload.
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index 416a2d8c0002..dc7f3d143e4c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -20,6 +20,7 @@ import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
+import android.app.job.PendingJobReasonsInfo;
import android.content.pm.ParceledListSlice;
import java.util.Map;
@@ -39,6 +40,8 @@ interface IJobScheduler {
ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace);
JobInfo getPendingJob(String namespace, int jobId);
int getPendingJobReason(String namespace, int jobId);
+ int[] getPendingJobReasons(String namespace, int jobId);
+ List<PendingJobReasonsInfo> getPendingJobReasonsHistory(String namespace, int jobId);
boolean canRunUserInitiatedJobs(String packageName);
boolean hasRunUserInitiatedJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 5f57c3973ab0..cc2d104813e4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -809,9 +809,21 @@ 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
+ * function effectively, regardless of the calling app's target SDK version.
+ * Calling this method will always return {@code false}.
+ *
* @see JobInfo.Builder#setImportantWhileForeground(boolean)
+ *
+ * @deprecated Use {@link #isExpedited()} instead.
*/
+ @FlaggedApi(Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
+ @Deprecated
public boolean isImportantWhileForeground() {
+ if (Flags.ignoreImportantWhileForeground()) {
+ return false;
+ }
return (flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
}
@@ -2124,6 +2136,13 @@ public class JobInfo implements Parcelable {
* <p>
* 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
+ * function effectively, regardless of the calling app's target SDK version.
+ * {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.
+ *
* @param importantWhileForeground whether to relax doze restrictions for this job when the
* app is in the foreground. False by default.
* @see JobInfo#isImportantWhileForeground()
@@ -2131,6 +2150,12 @@ public class JobInfo implements Parcelable {
*/
@Deprecated
public Builder setImportantWhileForeground(boolean importantWhileForeground) {
+ if (Flags.ignoreImportantWhileForeground()) {
+ Log.w(TAG, "Requested important-while-foreground flag for job" + mJobId
+ + " is ignored and takes no effect");
+ return this;
+ }
+
if (importantWhileForeground) {
mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
if (mPriority == PRIORITY_DEFAULT) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 52a761f8d486..7fef4e502c97 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -16,12 +16,17 @@
package android.app.job;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.usage.UsageStatsManager;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.pm.PackageManager;
@@ -33,6 +38,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.RemoteException;
import android.system.SystemCleaner;
import android.util.Log;
@@ -120,6 +126,15 @@ public class JobParameters implements Parcelable {
JobProtoEnums.INTERNAL_STOP_REASON_ANR; // 12.
/**
+ * The job ran for at least its minimum execution limit and the app lost the strong reference
+ * to the {@link JobParameters}. This could indicate that the job is empty because the app
+ * can no longer call {@link JobService#jobFinished(JobParameters, boolean)}.
+ * @hide
+ */
+ public static final int INTERNAL_STOP_REASON_TIMEOUT_ABANDONED =
+ JobProtoEnums.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED; // 13.
+
+ /**
* All the stop reason codes. This should be regarded as an immutable array at runtime.
*
* Note the order of these values will affect "dumpsys batterystats", and we do not want to
@@ -143,6 +158,7 @@ public class JobParameters implements Parcelable {
INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
INTERNAL_STOP_REASON_USER_UI_STOP,
INTERNAL_STOP_REASON_ANR,
+ INTERNAL_STOP_REASON_TIMEOUT_ABANDONED,
};
/**
@@ -165,6 +181,7 @@ public class JobParameters implements Parcelable {
case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
case INTERNAL_STOP_REASON_ANR: return "anr";
+ case INTERNAL_STOP_REASON_TIMEOUT_ABANDONED: return "timeout_abandoned";
default: return "unknown:" + reasonCode;
}
}
@@ -268,6 +285,25 @@ public class JobParameters implements Parcelable {
*/
public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15;
+ /**
+ * The job used up its maximum execution time and timed out. The system also detected that the
+ * app can no longer call {@link JobService#jobFinished(JobParameters, boolean)} for this job,
+ * likely because the strong reference to the job handle ({@link JobParameters}) passed to it
+ * via {@link JobService#onStartJob(JobParameters)} was lost. This can occur even if the app
+ * called {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or
+ * {@link JobScheduler#cancelInAllNamespaces()} to stop an active job while losing strong
+ * references to the job handle. In this case, the job is not necessarily abandoned. However,
+ * the system cannot distinguish such cases from truly abandoned jobs.
+ * <p>
+ * It is recommended that you use {@link JobService#jobFinished(JobParameters, boolean)} or
+ * return false from {@link JobService#onStartJob(JobParameters)} to stop an active job. This
+ * will prevent the occurrence of this stop reason and the need to handle it. The primary use
+ * case for this stop reason is to report a probable case of a job being abandoned.
+ * <p>
+ */
+ @FlaggedApi(Flags.FLAG_HANDLE_ABANDONED_JOBS)
+ public static final int STOP_REASON_TIMEOUT_ABANDONED = 16;
+
/** @hide */
@IntDef(prefix = {"STOP_REASON_"}, value = {
STOP_REASON_UNDEFINED,
@@ -286,6 +322,7 @@ public class JobParameters implements Parcelable {
STOP_REASON_USER,
STOP_REASON_SYSTEM_PROCESSING,
STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED,
+ STOP_REASON_TIMEOUT_ABANDONED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StopReason {
@@ -316,6 +353,16 @@ public class JobParameters implements Parcelable {
private JobCleanupCallback mJobCleanupCallback;
@Nullable
private Cleaner.Cleanable mCleanable;
+ /**
+ * Override handling of abandoned jobs in the system. Overriding this change
+ * will prevent the system to handle abandoned jobs and report it as a new
+ * stop reason STOP_REASON_TIMEOUT_ABANDONED.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_HANDLE_ABANDONED_JOBS = 372529068L;
/** @hide */
public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras,
@@ -360,6 +407,12 @@ public class JobParameters implements Parcelable {
}
/**
+ * Returns the reason {@link JobService#onStopJob(JobParameters)} was called on this job.
+ * <p>
+ * Apps should not rely on the stop reason for critical decision-making, as additional stop
+ * reasons may be added in subsequent Android releases. The primary intended use of this method
+ * is for logging and diagnostic purposes to gain insights into the causes of job termination.
+ * <p>
* @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
* be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
* yet been called.
@@ -638,6 +691,16 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void enableCleaner() {
+ if (!Flags.handleAbandonedJobs()
+ || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) {
+ return;
+ }
+ // JobParameters objects are passed by reference in local Binder
+ // transactions for clients running as SYSTEM. The life cycle of the
+ // JobParameters objects are no longer controlled by the client.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ return;
+ }
if (mJobCleanupCallback == null) {
initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId));
}
@@ -650,6 +713,10 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void disableCleaner() {
+ if (!Flags.handleAbandonedJobs()
+ || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) {
+ return;
+ }
if (mJobCleanupCallback != null) {
mJobCleanupCallback.disableCleaner();
if (mCleanable != null) {
@@ -763,7 +830,7 @@ public class JobParameters implements Parcelable {
return;
}
try {
- mCallback.forceJobFinished(mJobId);
+ mCallback.handleAbandonedJob(mJobId);
} catch (Exception e) {
Log.wtf(TAG, "Could not destroy running job", e);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index ad54cd397413..4fbd55a5d528 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -16,6 +16,7 @@
package android.app.job;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -238,6 +239,13 @@ public abstract class JobScheduler {
* to defer this job.
*/
public static final int PENDING_JOB_REASON_USER = 15;
+ /**
+ * The override deadline has not transpired.
+ *
+ * @see JobInfo.Builder#setOverrideDeadline(long)
+ */
+ @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+ public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16;
/** @hide */
@IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
@@ -259,6 +267,7 @@ public abstract class JobScheduler {
PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
PENDING_JOB_REASON_QUOTA,
PENDING_JOB_REASON_USER,
+ PENDING_JOB_REASON_CONSTRAINT_DEADLINE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PendingJobReason {
@@ -458,6 +467,10 @@ public abstract class JobScheduler {
/**
* Returns a reason why the job is pending and not currently executing. If there are multiple
* reasons why a job may be pending, this will only return one of them.
+ *
+ * @apiNote
+ * To know all the potential reasons why the job may be pending,
+ * use {@link #getPendingJobReasons(int)} instead.
*/
@PendingJobReason
public int getPendingJobReason(int jobId) {
@@ -465,6 +478,49 @@ public abstract class JobScheduler {
}
/**
+ * Returns potential reasons why the job with the given {@code jobId} may be pending
+ * and not currently executing.
+ *
+ * The returned array will include {@link PendingJobReason reasons} composed of both
+ * explicitly set constraints on the job and implicit constraints imposed by the system.
+ * The results can be used to debug why a given job may not be currently executing.
+ */
+ @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+ @NonNull
+ @PendingJobReason
+ public int[] getPendingJobReasons(int jobId) {
+ return new int[] { PENDING_JOB_REASON_UNDEFINED };
+ }
+
+ /**
+ * For the given {@code jobId}, returns a limited historical view of why the job may have
+ * been pending execution. The returned list is composed of {@link PendingJobReasonsInfo}
+ * objects, each of which include a timestamp since epoch along with an array of
+ * unsatisfied constraints represented by {@link PendingJobReason PendingJobReason constants}.
+ * <p>
+ * These constants could either be explicitly set constraints on the job or implicit
+ * constraints imposed by the system due to various reasons.
+ * The results can be used to debug why a given job may have been pending execution.
+ * <p>
+ * If the only {@link PendingJobReason} for the timestamp is
+ * {@link PendingJobReason#PENDING_JOB_REASON_UNDEFINED}, it could mean that
+ * the job was ready to be executed at that point in time.
+ * <p>
+ * Note: there is no set interval for the timestamps in the returned list since
+ * constraint changes occur based on device status and various other factors.
+ * <p>
+ * Note: the pending job reasons history is not persisted across device reboots.
+ * <p>
+ * @throws IllegalArgumentException if the {@code jobId} is invalid.
+ * @see #getPendingJobReasons(int)
+ */
+ @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API)
+ @NonNull
+ public List<PendingJobReasonsInfo> getPendingJobReasonsHistory(int jobId) {
+ throw new UnsupportedOperationException("Not implemented by " + getClass());
+ }
+
+ /**
* Returns {@code true} if the calling app currently holds the
* {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run
* user-initiated jobs.
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 5f80c52388b4..d460dcc65473 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -165,11 +165,9 @@ public abstract class JobServiceEngine {
case MSG_EXECUTE_JOB: {
final JobParameters params = (JobParameters) msg.obj;
try {
- if (Flags.cleanupEmptyJobs()) {
- params.enableCleaner();
- }
+ params.enableCleaner();
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
- if (Flags.cleanupEmptyJobs() && !workOngoing) {
+ if (!workOngoing) {
params.disableCleaner();
}
ackStartMessage(params, workOngoing);
@@ -196,9 +194,7 @@ public abstract class JobServiceEngine {
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
- if (Flags.cleanupEmptyJobs()) {
- params.disableCleaner();
- }
+ params.disableCleaner();
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
diff --git a/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl
new file mode 100644
index 000000000000..1a027020e25f
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.app.job;
+
+ parcelable PendingJobReasonsInfo;
diff --git a/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java
new file mode 100644
index 000000000000..3c96bab80794
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.job;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A simple wrapper which includes a timestamp (in millis since epoch)
+ * and an array of {@link JobScheduler.PendingJobReason reasons} at that timestamp
+ * for why a particular job may be pending.
+ */
+@FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API)
+public final class PendingJobReasonsInfo implements Parcelable {
+
+ @CurrentTimeMillisLong
+ private final long mTimestampMillis;
+
+ @NonNull
+ @JobScheduler.PendingJobReason
+ private final int[] mPendingJobReasons;
+
+ public PendingJobReasonsInfo(long timestampMillis,
+ @NonNull @JobScheduler.PendingJobReason int[] reasons) {
+ mTimestampMillis = timestampMillis;
+ mPendingJobReasons = reasons;
+ }
+
+ /**
+ * @return the time (in millis since epoch) associated with the set of pending job reasons.
+ */
+ @CurrentTimeMillisLong
+ public long getTimestampMillis() {
+ return mTimestampMillis;
+ }
+
+ /**
+ * Returns a set of {@link android.app.job.JobScheduler.PendingJobReason reasons} representing
+ * why the job may not have executed at the associated timestamp.
+ * <p>
+ * These reasons could either be explicitly set constraints on the job or implicit
+ * constraints imposed by the system due to various reasons.
+ * <p>
+ * Note: if the only {@link android.app.job.JobScheduler.PendingJobReason} present is
+ * {@link JobScheduler.PendingJobReason#PENDING_JOB_REASON_UNDEFINED}, it could mean
+ * that the job was ready to be executed at that time.
+ */
+ @NonNull
+ @JobScheduler.PendingJobReason
+ public int[] getPendingJobReasons() {
+ return mPendingJobReasons;
+ }
+
+ private PendingJobReasonsInfo(Parcel in) {
+ mTimestampMillis = in.readLong();
+ mPendingJobReasons = in.createIntArray();
+ }
+
+ @NonNull
+ public static final Creator<PendingJobReasonsInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ public PendingJobReasonsInfo createFromParcel(Parcel in) {
+ return new PendingJobReasonsInfo(in);
+ }
+
+ @Override
+ public PendingJobReasonsInfo[] newArray(int size) {
+ return new PendingJobReasonsInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mTimestampMillis);
+ dest.writeIntArray(mPendingJobReasons);
+ }
+}
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index d3068d7d37e8..a6e980726a9a 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -2,16 +2,6 @@ package: "com.android.server.alarm"
container: "system"
flag {
- name: "use_frozen_state_to_drop_listener_alarms"
- namespace: "backstage_power"
- description: "Use frozen state callback to drop listener alarms for cached apps"
- bug: "324470945"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "start_user_before_scheduled_alarms"
namespace: "multiuser"
description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
diff --git a/apex/jobscheduler/service/aconfig/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig
index c8976ca8361e..74d2a590086f 100644
--- a/apex/jobscheduler/service/aconfig/app_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig
@@ -12,3 +12,19 @@ flag {
}
}
+flag {
+ name: "screen_time_bypass"
+ namespace: "backstage_power"
+ description: "Bypass the screen time check for bucket evaluation"
+ bug: "374114769"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "adjust_default_bucket_elevation_params"
+ namespace: "backstage_power"
+ description: "Adjust the default bucket evaluation parameters"
+ bug: "379909479"
+}
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index c4d0d1850a18..426031fbeb9c 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -17,3 +17,13 @@ flag {
description: "Disable wakelocks for background apps while Light Device Idle is active"
bug: "326607666"
}
+
+flag {
+ name: "use_cpu_time_for_temp_allowlist"
+ namespace: "backstage_power"
+ description: "Use CPU time for temporary allowlists"
+ bug: "376561328"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e5389b4f96fb..86ed06bf4e3d 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -63,7 +63,7 @@ flag {
name: "remove_user_during_user_switch"
namespace: "backstage_power"
description: "Remove started user if user will be stopped due to user switch"
- bug: "321598070"
+ bug: "337077643"
}
flag {
@@ -75,3 +75,34 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enforce_quota_policy_to_fgs_jobs"
+ namespace: "backstage_power"
+ description: "Applies the normal quota policy to FGS jobs"
+ bug: "341201311"
+}
+
+flag {
+ name: "adjust_quota_default_constants"
+ namespace: "backstage_power"
+ description: "Adjust quota default parameters"
+ bug: "347058927"
+}
+
+flag {
+ name: "enforce_quota_policy_to_top_started_jobs"
+ namespace: "backstage_power"
+ description: "Apply the quota policy to jobs started when the app was in TOP state"
+ bug: "374323858"
+}
+
+flag {
+ name: "enforce_schedule_limit_to_proxy_jobs"
+ namespace: "backstage_power"
+ description: "Limit the schedule calls towards the persisted proxy jobs"
+ bug: "377912323"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3e650da2e66f..41fd4a29cfd1 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -620,8 +620,8 @@ public class DeviceIdleController extends SystemService
* the network and acquire wakelocks. Times are in milliseconds.
*/
@GuardedBy("this")
- private final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes
- = new SparseArray<>();
+ @VisibleForTesting
+ final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes = new SparseArray<>();
private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal;
@@ -1941,7 +1941,8 @@ public class DeviceIdleController extends SystemService
private static final int MSG_REPORT_IDLE_ON_LIGHT = 3;
private static final int MSG_REPORT_IDLE_OFF = 4;
private static final int MSG_REPORT_ACTIVE = 5;
- private static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
+ @VisibleForTesting
+ static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
@VisibleForTesting
static final int MSG_REPORT_STATIONARY_STATUS = 7;
private static final int MSG_FINISH_IDLE_OP = 8;
@@ -2511,6 +2512,11 @@ public class DeviceIdleController extends SystemService
return SystemClock.elapsedRealtime();
}
+ /** Returns the current elapsed realtime in milliseconds. */
+ long getUptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
LocationManager getLocationManager() {
if (mLocationManager == null) {
mLocationManager = mContext.getSystemService(LocationManager.class);
@@ -3264,7 +3270,8 @@ public class DeviceIdleController extends SystemService
void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int uid,
long duration, @TempAllowListType int tempAllowListType, boolean sync,
@ReasonCode int reasonCode, @Nullable String reason) {
- final long timeNow = SystemClock.elapsedRealtime();
+ final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis()
+ : mInjector.getElapsedRealtime();
boolean informWhitelistChanged = false;
int appId = UserHandle.getAppId(uid);
synchronized (this) {
@@ -3350,7 +3357,8 @@ public class DeviceIdleController extends SystemService
}
void checkTempAppWhitelistTimeout(int uid) {
- final long timeNow = SystemClock.elapsedRealtime();
+ final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis()
+ : mInjector.getElapsedRealtime();
final int appId = UserHandle.getAppId(uid);
if (DEBUG) {
Slog.d(TAG, "checkTempAppWhitelistTimeout: uid=" + uid + ", timeNow=" + timeNow);
@@ -5219,6 +5227,17 @@ public class DeviceIdleController extends SystemService
}
}
+ pw.println(" Flags:");
+ pw.print(" ");
+ pw.print(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST);
+ pw.print("=");
+ pw.println(Flags.useCpuTimeForTempAllowlist());
+ pw.print(" ");
+ pw.print(Flags.FLAG_REMOVE_IDLE_LOCATION);
+ pw.print("=");
+ pw.println(Flags.removeIdleLocation());
+ pw.println();
+
synchronized (this) {
mConstants.dump(pw);
@@ -5449,7 +5468,8 @@ public class DeviceIdleController extends SystemService
pw.println(" Temp whitelist schedule:");
prefix = " ";
}
- final long timeNow = SystemClock.elapsedRealtime();
+ final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis()
+ : mInjector.getElapsedRealtime();
for (int i = 0; i < size; i++) {
pw.print(prefix);
pw.print("UID=");
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 033da2df9bf6..60ba3b896a28 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -282,7 +282,6 @@ public class AlarmManagerService extends SystemService {
private final Injector mInjector;
int mBroadcastRefCount = 0;
- boolean mUseFrozenStateToDropListenerAlarms;
MetricsHelper mMetricsHelper;
PowerManager.WakeLock mWakeLock;
SparseIntArray mAlarmsPerUid = new SparseIntArray();
@@ -1784,40 +1783,37 @@ public class AlarmManagerService extends SystemService {
mMetricsHelper = new MetricsHelper(getContext(), mLock);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
&& UserManager.supportsMultipleUsers();
if (mStartUserBeforeScheduledAlarms) {
mUserWakeupStore = new UserWakeupStore();
mUserWakeupStore.init();
}
- if (mUseFrozenStateToDropListenerAlarms) {
- final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
- final int size = frozenStates.length;
- if (uids.length != size) {
- Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
- + " uids.length: " + uids.length + " frozenStates.length: " + size);
- // Cannot process received data in any meaningful way.
- return;
- }
- final IntArray affectedUids = new IntArray();
- for (int i = 0; i < size; i++) {
- if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
- continue;
- }
- if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
- uids[i])) {
- continue;
- }
- affectedUids.add(uids[i]);
+ final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
+ final int size = frozenStates.length;
+ if (uids.length != size) {
+ Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
+ + " uids.length: " + uids.length + " frozenStates.length: " + size);
+ // Cannot process received data in any meaningful way.
+ return;
+ }
+ final IntArray affectedUids = new IntArray();
+ for (int i = 0; i < size; i++) {
+ if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
+ continue;
}
- if (affectedUids.size() > 0) {
- removeExactListenerAlarms(affectedUids.toArray());
+ if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
+ uids[i])) {
+ continue;
}
- };
- final ActivityManager am = getContext().getSystemService(ActivityManager.class);
- am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
- }
+ affectedUids.add(uids[i]);
+ }
+ if (affectedUids.size() > 0) {
+ removeExactListenerAlarms(affectedUids.toArray());
+ }
+ };
+ final ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
mListenerDeathRecipient = new IBinder.DeathRecipient() {
@Override
@@ -2994,13 +2990,10 @@ public class AlarmManagerService extends SystemService {
pw.println("Feature Flags:");
pw.increaseIndent();
- pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
- mUseFrozenStateToDropListenerAlarms);
- pw.println();
pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
Flags.startUserBeforeScheduledAlarms());
- pw.decreaseIndent();
pw.println();
+ pw.decreaseIndent();
pw.println();
pw.println("App Standby Parole: " + mAppStandbyParole);
@@ -5146,38 +5139,6 @@ public class AlarmManagerService extends SystemService {
removeForStoppedLocked(uid);
}
}
-
- @Override
- public void handleUidCachedChanged(int uid, boolean cached) {
- if (mUseFrozenStateToDropListenerAlarms) {
- // Use ActivityManager#UidFrozenStateChangedCallback instead.
- return;
- }
- if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) {
- return;
- }
- // Apps can quickly get frozen after being cached, breaking the exactness guarantee on
- // listener alarms. So going forward, the contract of exact listener alarms explicitly
- // states that they will be removed as soon as the app goes out of lifecycle. We still
- // allow a short grace period for quick shuffling of proc-states that may happen
- // unexpectedly when switching between different lifecycles and is generally hard for
- // apps to avoid.
-
- final long delay;
- synchronized (mLock) {
- delay = mConstants.CACHED_LISTENER_REMOVAL_DELAY;
- }
- final Integer uidObj = uid;
-
- if (cached && !mHandler.hasEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED,
- uidObj)) {
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj),
- delay);
- } else {
- mHandler.removeEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj);
- }
- }
};
private final BroadcastStats getStatsLocked(PendingIntent pi) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 5255e7227709..5dfb3754e8fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1550,7 +1550,7 @@ class JobConcurrencyManager {
mActivePkgStats.add(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
packageStats);
- mService.resetPendingJobReasonCache(jobStatus);
+ mService.resetPendingJobReasonsCache(jobStatus);
}
if (mService.getPendingJobQueue().remove(jobStatus)) {
mService.mJobPackageTracker.noteNonpending(jobStatus);
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 97c6e25eeb75..4335cae65a3c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
@@ -44,6 +45,7 @@ import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
+import android.app.job.PendingJobReasonsInfo;
import android.app.job.UserVisibleJobSummary;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -460,10 +462,10 @@ public class JobSchedulerService extends com.android.server.SystemService
private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
/**
- * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason.
+ * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reasons.
*/
- @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
- private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache =
+ @GuardedBy("mPendingJobReasonsCache") // Use its own lock to avoid blocking JS processing
+ private final SparseArrayMap<String, SparseArray<int[]>> mPendingJobReasonsCache =
new SparseArrayMap<>();
/**
@@ -572,6 +574,7 @@ public class JobSchedulerService extends com.android.server.SystemService
case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO:
+ case Constants.KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF:
mConstants.updateBackoffConstantsLocked();
break;
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
@@ -678,6 +681,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO =
"system_stop_to_failure_ratio";
+ private static final String KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+ "abandoned_job_timeouts_before_aggressive_backoff";
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 KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
@@ -749,6 +754,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+ private static final int DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = 3;
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_CONN_USE_CELL_SIGNAL_STRENGTH = true;
@@ -844,7 +850,12 @@ public class JobSchedulerService extends com.android.server.SystemService
* incremental failure in the backoff policy calculation.
*/
int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO;
-
+ /**
+ * Number of consecutive timeouts by abandoned jobs before we change to aggressive backoff
+ * policy.
+ */
+ int ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+ DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
/**
* The fraction of a job's running window that must pass before we
* consider running it when the network is congested.
@@ -1077,6 +1088,10 @@ public class JobSchedulerService extends com.android.server.SystemService
SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_SYSTEM_STOP_TO_FAILURE_RATIO,
DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO);
+ ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF);
}
// TODO(141645789): move into ConnectivityController.CcConfig
@@ -1286,6 +1301,8 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println();
+ pw.print(KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
@@ -1702,8 +1719,9 @@ public class JobSchedulerService extends com.android.server.SystemService
int userId, @Nullable String namespace, String tag) {
// Rate limit excessive schedule() calls.
final String servicePkg = job.getService().getPackageName();
- if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
- // Only limit schedule calls for persisted jobs scheduled by the app itself.
+ if (job.isPersisted() && (Flags.enforceScheduleLimitToProxyJobs()
+ || (packageName == null || packageName.equals(servicePkg)))) {
+ // limit excessive schedule calls for persisted jobs.
final String pkg = packageName == null ? servicePkg : packageName;
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
@@ -1965,7 +1983,12 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getNumAppliedFlexibleConstraints(),
jobStatus.getNumDroppedFlexibleConstraints(),
jobStatus.getFilteredTraceTag(),
- jobStatus.getFilteredDebugTags());
+ jobStatus.getFilteredDebugTags(),
+ jobStatus.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ jobStatus.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(
+ jobStatus.getNumAbandonedFailures(), jobStatus.getSourceUid()));
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2021,139 +2044,129 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- @JobScheduler.PendingJobReason
- private int getPendingJobReason(int uid, String namespace, int jobId) {
- int reason;
+ @NonNull
+ private int[] getPendingJobReasons(int uid, String namespace, int jobId) {
+ int[] reasons;
// Some apps may attempt to query this frequently, so cache the reason under a separate lock
// so that the rest of JS processing isn't negatively impacted.
- synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
- if (jobIdToReason != null) {
- reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
- if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
- return reason;
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace);
+ if (jobIdToReasons != null) {
+ reasons = jobIdToReasons.get(jobId);
+ if (reasons != null) {
+ return reasons;
}
}
}
synchronized (mLock) {
- reason = getPendingJobReasonLocked(uid, namespace, jobId);
+ reasons = getPendingJobReasonsLocked(uid, namespace, jobId);
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReason("
- + uid + "," + namespace + "," + jobId + ")=" + reason);
+ Slog.v(TAG, "getPendingJobReasons("
+ + uid + "," + namespace + "," + jobId + ")=" + Arrays.toString(reasons));
}
}
- synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
- if (jobIdToReason == null) {
- jobIdToReason = new SparseIntArray();
- mPendingJobReasonCache.add(uid, namespace, jobIdToReason);
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace);
+ if (jobIdToReasons == null) {
+ jobIdToReasons = new SparseArray<>();
+ mPendingJobReasonsCache.add(uid, namespace, jobIdToReasons);
}
- jobIdToReason.put(jobId, reason);
+ jobIdToReasons.put(jobId, reasons);
}
- return reason;
+ return reasons;
}
@VisibleForTesting
@JobScheduler.PendingJobReason
int getPendingJobReason(JobStatus job) {
- return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId());
+ // keep original method to enable unit testing with flags
+ return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId())[0];
+ }
+
+ @VisibleForTesting
+ @NonNull
+ int[] getPendingJobReasons(JobStatus job) {
+ return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId());
}
- @JobScheduler.PendingJobReason
@GuardedBy("mLock")
- private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
+ @NonNull
+ private int[] getPendingJobReasonsLocked(int uid, String namespace, int jobId) {
// Very similar code to isReadyToBeExecutedLocked.
-
- JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+ final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (job == null) {
// Job doesn't exist.
- return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID };
}
-
if (isCurrentlyRunningLocked(job)) {
- return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_EXECUTING };
}
+ final String debugPrefix = "getPendingJobReasonsLocked: " + job.toShortString();
final boolean jobReady = job.isReady();
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " ready=" + jobReady);
+ Slog.v(TAG, debugPrefix + " ready=" + jobReady);
}
-
- if (!jobReady) {
- return job.getPendingJobReason();
+ final JobRestriction restriction = checkIfRestricted(job);
+ if (DEBUG) {
+ Slog.v(TAG, debugPrefix + " restriction=" + restriction);
+ }
+ if (!jobReady || restriction != null) {
+ return job.getPendingJobReasons(restriction);
}
final boolean userStarted = areUsersStartedLocked(job);
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " userStarted=" + userStarted);
+ Slog.v(TAG, debugPrefix + " userStarted=" + userStarted);
}
if (!userStarted) {
- return JobScheduler.PENDING_JOB_REASON_USER;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_USER };
}
final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " backingUp=" + backingUp);
+ Slog.v(TAG, debugPrefix + " backingUp=" + backingUp);
}
-
if (backingUp) {
// TODO: Should we make a special reason for this?
- return JobScheduler.PENDING_JOB_REASON_APP;
- }
-
- JobRestriction restriction = checkIfRestricted(job);
- if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " restriction=" + restriction);
- }
- if (restriction != null) {
- return restriction.getPendingReason();
+ return new int[] { JobScheduler.PENDING_JOB_REASON_APP };
}
- // The following can be a little more expensive (especially jobActive, since we need to
- // go through the array of all potentially active jobs), so we are doing them
- // later... but still before checking with the package manager!
+ // The following can be a little more expensive, so we are doing it later,
+ // but still before checking with the package manager!
final boolean jobPending = mPendingJobQueue.contains(job);
-
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " pending=" + jobPending);
+ Slog.v(TAG, debugPrefix + " pending=" + jobPending);
}
-
if (jobPending) {
- // We haven't started the job for some reason. Presumably, there are too many jobs
- // running.
- return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
- }
-
- final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
-
- if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " active=" + jobActive);
- }
- if (jobActive) {
- return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ // We haven't started the job - presumably, there are too many jobs running.
+ return new int[] { JobScheduler.PENDING_JOB_REASON_DEVICE_STATE };
}
// Validate that the defined package+service is still present & viable.
final boolean componentUsable = isComponentUsable(job);
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " componentUsable=" + componentUsable);
+ Slog.v(TAG, debugPrefix + " componentUsable=" + componentUsable);
}
if (!componentUsable) {
- return JobScheduler.PENDING_JOB_REASON_APP;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_APP };
}
- return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_UNDEFINED };
+ }
+
+ @NonNull
+ private List<PendingJobReasonsInfo> getPendingJobReasonsHistory(
+ int uid, String namespace, int jobId) {
+ synchronized (mLock) {
+ final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+ if (job == null) {
+ // Job doesn't exist.
+ throw new IllegalArgumentException("Invalid job id");
+ }
+
+ return job.getPendingJobReasonsHistory();
+ }
}
private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) {
@@ -2195,15 +2208,16 @@ public class JobSchedulerService extends com.android.server.SystemService
// The app process will be killed soon. There's no point keeping its jobs in
// the pending queue to try and start them.
if (mPendingJobQueue.remove(job)) {
- synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> jobIdToReason = mPendingJobReasonsCache.get(
job.getUid(), job.getNamespace());
if (jobIdToReason == null) {
- jobIdToReason = new SparseIntArray();
- mPendingJobReasonCache.add(job.getUid(), job.getNamespace(),
+ jobIdToReason = new SparseArray<>();
+ mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(),
jobIdToReason);
}
- jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER);
+ jobIdToReason.put(job.getJobId(),
+ new int[] { JobScheduler.PENDING_JOB_REASON_USER });
}
}
}
@@ -2229,8 +2243,8 @@ public class JobSchedulerService extends com.android.server.SystemService
synchronized (mLock) {
mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
}
- synchronized (mPendingJobReasonCache) {
- mPendingJobReasonCache.clear();
+ synchronized (mPendingJobReasonsCache) {
+ mPendingJobReasonsCache.clear();
}
}
@@ -2415,7 +2429,12 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getNumAppliedFlexibleConstraints(),
cancelled.getNumDroppedFlexibleConstraints(),
cancelled.getFilteredTraceTag(),
- cancelled.getFilteredDebugTags());
+ cancelled.getFilteredDebugTags(),
+ cancelled.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ cancelled.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(
+ cancelled.getNumAbandonedFailures(), cancelled.getSourceUid()));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -2875,7 +2894,7 @@ public class JobSchedulerService extends com.android.server.SystemService
final boolean update = lastJob != null;
mJobs.add(jobStatus);
// Clear potentially cached INVALID_JOB_ID reason.
- resetPendingJobReasonCache(jobStatus);
+ resetPendingJobReasonsCache(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
@@ -2897,9 +2916,9 @@ public class JobSchedulerService extends com.android.server.SystemService
// Deal with any remaining work items in the old job.
jobStatus.stopTrackingJobLocked(incomingJob);
- synchronized (mPendingJobReasonCache) {
- SparseIntArray reasonCache =
- mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> reasonCache =
+ mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace());
if (reasonCache != null) {
reasonCache.delete(jobStatus.getJobId());
}
@@ -2927,11 +2946,11 @@ public class JobSchedulerService extends com.android.server.SystemService
return removed;
}
- /** Remove the pending job reason for this job from the cache. */
- void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
- synchronized (mPendingJobReasonCache) {
- final SparseIntArray reasons =
- mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
+ /** Remove the pending job reasons for this job from the cache. */
+ void resetPendingJobReasonsCache(@NonNull JobStatus jobStatus) {
+ synchronized (mPendingJobReasonsCache) {
+ final SparseArray<int[]> reasons =
+ mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace());
if (reasons != null) {
reasons.delete(jobStatus.getJobId());
}
@@ -3005,7 +3024,9 @@ public class JobSchedulerService extends com.android.server.SystemService
final long initialBackoffMillis = job.getInitialBackoffMillis();
int numFailures = failureToReschedule.getNumFailures();
+ int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures();
int numSystemStops = failureToReschedule.getNumSystemStops();
+ final int uid = failureToReschedule.getSourceUid();
// We should back off slowly if JobScheduler keeps stopping the job,
// but back off immediately if the issue appeared to be the app's fault
// or the user stopped the job somehow.
@@ -3014,9 +3035,20 @@ public class JobSchedulerService extends com.android.server.SystemService
|| internalStopReason == JobParameters.INTERNAL_STOP_REASON_ANR
|| stopReason == JobParameters.STOP_REASON_USER) {
numFailures++;
+ } else if (android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid)
+ && internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) {
+ numAbandonedFailures++;
+ numFailures++;
} else {
numSystemStops++;
}
+
+ int backoffPolicy = job.getBackoffPolicy();
+ if (shouldUseAggressiveBackoff(numAbandonedFailures, uid)) {
+ backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
+ }
+
final int backoffAttempts =
numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO;
final long earliestRuntimeMs;
@@ -3025,7 +3057,7 @@ public class JobSchedulerService extends com.android.server.SystemService
earliestRuntimeMs = JobStatus.NO_EARLIEST_RUNTIME;
} else {
long delayMillis;
- switch (job.getBackoffPolicy()) {
+ switch (backoffPolicy) {
case JobInfo.BACKOFF_POLICY_LINEAR: {
long backoff = initialBackoffMillis;
if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
@@ -3054,7 +3086,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
JobStatus newJob = new JobStatus(failureToReschedule,
earliestRuntimeMs,
- JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
+ JobStatus.NO_LATEST_RUNTIME, numFailures, numAbandonedFailures, numSystemStops,
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(),
failureToReschedule.getCumulativeExecutionTimeMs());
if (stopReason == JobParameters.STOP_REASON_USER) {
@@ -3077,6 +3109,21 @@ public class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * Returns {@code true} if the given number of abandoned failures indicates that JobScheduler
+ * should use an aggressive backoff policy.
+ *
+ * @param numAbandonedFailures The number of abandoned failures.
+ * @return {@code true} if the given number of abandoned failures indicates that JobScheduler
+ * should use an aggressive backoff policy.
+ */
+ public boolean shouldUseAggressiveBackoff(int numAbandonedFailures, int uid) {
+ return android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid)
+ && numAbandonedFailures
+ > mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
+ }
+
+ /**
* Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
* does not cause a job's period to be larger than requested (eg: if the requested period is
* shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
@@ -3155,6 +3202,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return new JobStatus(periodicToReschedule,
elapsedNow + period - flex, elapsedNow + period,
0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numAbandonedFailures */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime(),
0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3171,6 +3219,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return new JobStatus(periodicToReschedule,
newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numAbandonedFailures */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime(),
0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3179,6 +3228,12 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ if (android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())) {
+ jobTimedOut |= (debugStopReason
+ == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ }
// If madeActive = 0, the job never actually started.
if (!jobTimedOut && jobStatus.madeActive > 0) {
final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
@@ -3260,9 +3315,14 @@ public class JobSchedulerService extends com.android.server.SystemService
// we stop it.
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null;
+ final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())
+ && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
if (rescheduledJob != null
&& !rescheduledJob.shouldTreatAsUserInitiatedJob()
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
+ || isStopReasonAbandoned
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
rescheduledJob.disallowRunInBatterySaverAndDoze();
}
@@ -3313,18 +3373,18 @@ public class JobSchedulerService extends com.android.server.SystemService
public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
if (changedJobs == null) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- synchronized (mPendingJobReasonCache) {
- mPendingJobReasonCache.clear();
+ synchronized (mPendingJobReasonsCache) {
+ mPendingJobReasonsCache.clear();
}
} else if (changedJobs.size() > 0) {
synchronized (mLock) {
mChangedJobList.addAll(changedJobs);
}
mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
- synchronized (mPendingJobReasonCache) {
+ synchronized (mPendingJobReasonsCache) {
for (int i = changedJobs.size() - 1; i >= 0; --i) {
final JobStatus job = changedJobs.valueAt(i);
- resetPendingJobReasonCache(job);
+ resetPendingJobReasonsCache(job);
}
}
}
@@ -3893,23 +3953,21 @@ public class JobSchedulerService extends com.android.server.SystemService
// Update the pending reason for any jobs that aren't going to be run.
final int numRunnableJobs = runnableJobs.size();
if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
- synchronized (mPendingJobReasonCache) {
+ synchronized (mPendingJobReasonsCache) {
for (int i = 0; i < numRunnableJobs; ++i) {
final JobStatus job = runnableJobs.get(i);
if (jobsToRun.contains(job)) {
- // We're running this job. Skip updating the pending reason.
- continue;
+ continue; // we're running this job - skip updating the pending reason.
}
- SparseIntArray reasons =
- mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+ SparseArray<int[]> reasons =
+ mPendingJobReasonsCache.get(job.getUid(), job.getNamespace());
if (reasons == null) {
- reasons = new SparseIntArray();
- mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
+ reasons = new SparseArray<>();
+ mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), reasons);
}
- // We're force batching these jobs, so consider it an optimization
- // policy reason.
- reasons.put(job.getJobId(),
- JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ // we're force batching these jobs - note it as optimization.
+ reasons.put(job.getJobId(), new int[]
+ { JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION });
}
}
}
@@ -5123,11 +5181,28 @@ public class JobSchedulerService extends com.android.server.SystemService
@Override
public int getPendingJobReason(String namespace, int jobId) throws RemoteException {
+ return getPendingJobReasons(validateNamespace(namespace), jobId)[0];
+ }
+
+ @Override
+ public int[] getPendingJobReasons(String namespace, int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return JobSchedulerService.this.getPendingJobReasons(
+ uid, validateNamespace(namespace), jobId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ @Override
+ public List<PendingJobReasonsInfo> getPendingJobReasonsHistory(String namespace, int jobId)
+ throws RemoteException {
+ final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.getPendingJobReason(
+ return JobSchedulerService.this.getPendingJobReasonsHistory(
uid, validateNamespace(namespace), jobId);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -5861,8 +5936,14 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
Flags.doNotForceRushExecutionAtBoot());
pw.println();
- pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION,
- android.app.job.Flags.backupJobsExemption());
+ pw.print(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND,
+ android.app.job.Flags.ignoreImportantWhileForeground());
+ pw.println();
+ pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API,
+ android.app.job.Flags.getPendingJobReasonsApi());
+ pw.println();
+ pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API,
+ android.app.job.Flags.getPendingJobReasonsHistoryApi());
pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index ac240ccff017..42c8250a6185 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -433,8 +433,14 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
- case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION:
- pw.println(android.app.job.Flags.backupJobsExemption());
+ case android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND:
+ pw.println(android.app.job.Flags.ignoreImportantWhileForeground());
+ break;
+ case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API:
+ pw.println(android.app.job.Flags.getPendingJobReasonsApi());
+ break;
+ case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API:
+ pw.println(android.app.job.Flags.getPendingJobReasonsHistoryApi());
break;
default:
pw.println("Unknown flag: " + flagName);
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 ee246d84997f..ebfda527001d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -16,6 +16,8 @@
package com.android.server.job;
+import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS;
+
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.safelyScaleBytesToKBForHistogram;
@@ -129,8 +131,8 @@ public final class JobServiceContext implements ServiceConnection {
private static final String[] VERB_STRINGS = {
"VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
};
- private static final String TRACE_JOB_FORCE_FINISHED_PREFIX = "forceJobFinished:";
- private static final String TRACE_JOB_FORCE_FINISHED_DELIMITER = "#";
+ private static final String TRACE_ABANDONED_JOB = "abandonedJob:";
+ private static final String TRACE_ABANDONED_JOB_DELIMITER = "#";
// States that a job occupies while interacting with the client.
static final int VERB_BINDING = 0;
@@ -294,8 +296,8 @@ public final class JobServiceContext implements ServiceConnection {
}
@Override
- public void forceJobFinished(int jobId) {
- doForceJobFinished(this, jobId);
+ public void handleAbandonedJob(int jobId) {
+ doHandleAbandonedJob(this, jobId);
}
@Override
@@ -546,7 +548,12 @@ public final class JobServiceContext implements ServiceConnection {
job.getNumAppliedFlexibleConstraints(),
job.getNumDroppedFlexibleConstraints(),
job.getFilteredTraceTag(),
- job.getFilteredDebugTags());
+ job.getFilteredDebugTags(),
+ job.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ job.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(
+ job.getNumAbandonedFailures(), job.getSourceUid()));
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -618,6 +625,26 @@ public final class JobServiceContext implements ServiceConnection {
return mRunningJob;
}
+ @VisibleForTesting
+ void setRunningJobLockedForTest(JobStatus job) {
+ mRunningJob = job;
+ }
+
+ @VisibleForTesting
+ void setJobParamsLockedForTest(JobParameters params) {
+ mParams = params;
+ }
+
+ @VisibleForTesting
+ void setRunningCallbackLockedForTest(JobCallback callback) {
+ mRunningCallback = callback;
+ }
+
+ @VisibleForTesting
+ void setPendingStopReasonLockedForTest(int stopReason) {
+ mPendingStopReason = stopReason;
+ }
+
@JobConcurrencyManager.WorkType
int getRunningJobWorkType() {
return mRunningJobWorkType;
@@ -773,7 +800,7 @@ public final class JobServiceContext implements ServiceConnection {
* This method just adds traces to evaluate jobs that leak jobparameters at the client.
* It does not stop the job.
*/
- void doForceJobFinished(JobCallback cb, int jobId) {
+ void doHandleAbandonedJob(JobCallback cb, int jobId) {
final long ident = Binder.clearCallingIdentity();
try {
final JobStatus executing;
@@ -786,10 +813,11 @@ public final class JobServiceContext implements ServiceConnection {
executing = getRunningJobLocked();
}
if (executing != null && jobId == executing.getJobId()) {
+ executing.setAbandoned(true);
final StringBuilder stateSuffix = new StringBuilder();
- stateSuffix.append(TRACE_JOB_FORCE_FINISHED_PREFIX);
+ stateSuffix.append(TRACE_ABANDONED_JOB);
stateSuffix.append(executing.getBatteryName());
- stateSuffix.append(TRACE_JOB_FORCE_FINISHED_DELIMITER);
+ stateSuffix.append(TRACE_ABANDONED_JOB_DELIMITER);
stateSuffix.append(executing.getJobId());
Trace.instant(Trace.TRACE_TAG_POWER, stateSuffix.toString());
}
@@ -1364,8 +1392,9 @@ public final class JobServiceContext implements ServiceConnection {
}
/** Process MSG_TIMEOUT here. */
+ @VisibleForTesting
@GuardedBy("mLock")
- private void handleOpTimeoutLocked() {
+ void handleOpTimeoutLocked() {
switch (mVerb) {
case VERB_BINDING:
// The system may have been too busy. Don't drop the job or trigger an ANR.
@@ -1427,9 +1456,28 @@ public final class JobServiceContext implements ServiceConnection {
// Not an error - client ran out of time.
Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
+ " Sending onStop: " + getRunningJobNameLocked());
- mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
- JobParameters.INTERNAL_STOP_REASON_TIMEOUT, "client timed out");
- sendStopMessageLocked("timeout while executing");
+
+ final JobStatus executing = getRunningJobLocked();
+ int stopReason = JobParameters.STOP_REASON_TIMEOUT;
+ int internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ final StringBuilder stopMessage = new StringBuilder("timeout while executing");
+ final StringBuilder debugStopReason = new StringBuilder("client timed out");
+
+ if (android.app.job.Flags.handleAbandonedJobs()
+ && executing != null
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, executing.getSourceUid())
+ && executing.isAbandoned()) {
+ final String abandonedMessage = " and maybe abandoned";
+ stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED;
+ internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED;
+ stopMessage.append(abandonedMessage);
+ debugStopReason.append(abandonedMessage);
+ }
+
+ mParams.setStopReason(stopReason,
+ internalStopReason, debugStopReason.toString());
+ sendStopMessageLocked(stopMessage.toString());
} else if (nowElapsed >= earliestStopTimeElapsed) {
// We've given the app the minimum execution time. See if we should stop it or
// let it continue running
@@ -1479,8 +1527,9 @@ public final class JobServiceContext implements ServiceConnection {
* Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
* VERB_STOPPING.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private void sendStopMessageLocked(@Nullable String reason) {
+ void sendStopMessageLocked(@Nullable String reason) {
removeOpTimeOutLocked();
if (mVerb != VERB_EXECUTING) {
Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
@@ -1642,7 +1691,12 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getNumAppliedFlexibleConstraints(),
completedJob.getNumDroppedFlexibleConstraints(),
completedJob.getFilteredTraceTag(),
- completedJob.getFilteredDebugTags());
+ completedJob.getFilteredDebugTags(),
+ completedJob.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ completedJob.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(
+ completedJob.getNumAbandonedFailures(), completedJob.getSourceUid()));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d8934d8f83b8..dfb36818c818 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -269,7 +269,9 @@ public final class JobStore {
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
JobStatus newJob = new JobStatus(job,
elapsedRuntimes.first, elapsedRuntimes.second,
- 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(),
+ 0 /* numFailures */, 0 /* numAbandonedFailures */,
+ 0 /* numSystemStops */, job.getLastSuccessfulRunTime(),
+ job.getLastFailedRunTime(),
job.getCumulativeExecutionTimeMs());
newJob.prepareLocked();
toAdd.add(newJob);
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 d5c9ae615486..abec170f3b7d 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
@@ -210,7 +210,9 @@ public final class DeviceIdleJobsController extends StateController {
}
private boolean updateTaskStateLocked(JobStatus task, final long nowElapsed) {
- final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
+ final boolean allowInIdle =
+ (!android.app.job.Flags.ignoreImportantWhileForeground()
+ && ((task.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0))
&& (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
final boolean whitelisted = isWhitelistedLocked(task);
final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
@@ -219,7 +221,8 @@ public final class DeviceIdleJobsController extends StateController {
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ if (!android.app.job.Flags.ignoreImportantWhileForeground()
+ && (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
mAllowInIdleJobs.add(jobStatus);
}
updateTaskStateLocked(jobStatus, sElapsedRealtimeClock.millis());
@@ -227,7 +230,8 @@ public final class DeviceIdleJobsController extends StateController {
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
- if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ if (!android.app.job.Flags.ignoreImportantWhileForeground()
+ && (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
mAllowInIdleJobs.remove(jobStatus);
}
}
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 e3af1d894762..5a33aa0ab7eb 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
@@ -32,6 +32,7 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
+import android.app.job.PendingJobReasonsInfo;
import android.app.job.UserVisibleJobSummary;
import android.content.ClipData;
import android.content.ComponentName;
@@ -39,6 +40,7 @@ import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.text.format.DateFormat;
@@ -64,6 +66,7 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobServerProtoEnums;
import com.android.server.job.JobStatusDumpProto;
import com.android.server.job.JobStatusShortInfoProto;
+import com.android.server.job.restrictions.JobRestriction;
import dalvik.annotation.optimization.NeverCompile;
@@ -72,6 +75,7 @@ import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.function.Predicate;
@@ -312,6 +316,12 @@ public final class JobStatus {
private final int numFailures;
/**
+ * How many times this job has stopped due to {@link
+ * JobParameters#STOP_REASON_TIMEOUT_ABANDONED}.
+ */
+ private final int mNumAbandonedFailures;
+
+ /**
* The number of times JobScheduler has forced this job to stop due to reasons mostly outside
* of the app's control.
*/
@@ -515,6 +525,10 @@ public final class JobStatus {
private final long[] mConstraintUpdatedTimesElapsed = new long[NUM_CONSTRAINT_CHANGE_HISTORY];
private final int[] mConstraintStatusHistory = new int[NUM_CONSTRAINT_CHANGE_HISTORY];
+ private final List<PendingJobReasonsInfo> mPendingJobReasonsHistory = new ArrayList<>();
+ private static final int PENDING_JOB_HISTORY_RETURN_LIMIT = 10;
+ private static final int PENDING_JOB_HISTORY_TRIM_THRESHOLD = 25;
+
/**
* For use only by ContentObserverController: state it is maintaining about content URIs
* being observed.
@@ -576,6 +590,13 @@ public final class JobStatus {
private String mSystemTraceTag;
/**
+ * Job maybe abandoned by not calling
+ * {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} while
+ * the strong reference to {@link android.app.job.JobParameters} is lost
+ */
+ private boolean mIsAbandoned;
+
+ /**
* Core constructor for JobStatus instances. All other ctors funnel down to this one.
*
* @param job The actual requested parameters for the job
@@ -590,6 +611,8 @@ public final class JobStatus {
* @param tag A string associated with the job for debugging/logging purposes.
* @param numFailures Count of how many times this job has requested a reschedule because
* its work was not yet finished.
+ * @param mNumAbandonedFailures Count of how many times this job has requested a reschedule
+ * because it was abandoned.
* @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to
* factors mostly out of the app's control.
* @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
@@ -602,7 +625,7 @@ public final class JobStatus {
*/
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
- int numFailures, int numSystemStops,
+ int numFailures, int mNumAbandonedFailures, int numSystemStops,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
int internalFlags,
@@ -662,6 +685,7 @@ public final class JobStatus {
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
+ this.mNumAbandonedFailures = mNumAbandonedFailures;
mNumSystemStops = numSystemStops;
int requiredConstraints = job.getConstraintFlags();
@@ -725,6 +749,8 @@ public final class JobStatus {
updateNetworkBytesLocked();
updateMediaBackupExemptionStatus();
+
+ mIsAbandoned = false;
}
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -733,7 +759,8 @@ public final class JobStatus {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
- jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
+ jobStatus.getSourceTag(), jobStatus.getNumFailures(),
+ jobStatus.getNumAbandonedFailures(), jobStatus.getNumSystemStops(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
jobStatus.getCumulativeExecutionTimeMs(),
@@ -770,6 +797,7 @@ public final class JobStatus {
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket, namespace,
sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
+ /* mNumAbandonedFailures */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
innerFlags, dynamicConstraints);
@@ -789,13 +817,15 @@ public final class JobStatus {
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling,
long newEarliestRuntimeElapsedMillis,
- long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
+ long newLatestRuntimeElapsedMillis, int numFailures,
+ int mNumAbandonedFailures, int numSystemStops,
long lastSuccessfulRunTime, long lastFailedRunTime,
long cumulativeExecutionTimeMs) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
- rescheduling.getSourceTag(), numFailures, numSystemStops,
+ rescheduling.getSourceTag(), numFailures,
+ mNumAbandonedFailures, numSystemStops,
newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
@@ -834,7 +864,8 @@ public final class JobStatus {
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
- standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
+ standbyBucket, namespace, tag, /* numFailures */ 0,
+ /* mNumAbandonedFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/* cumulativeExecutionTime */ 0,
@@ -1061,6 +1092,16 @@ public final class JobStatus {
return job.getTraceTag();
}
+ /** Returns if the job maybe abandoned */
+ public boolean isAbandoned() {
+ return mIsAbandoned;
+ }
+
+ /** Set the job maybe abandoned state*/
+ public void setAbandoned(boolean abandoned) {
+ mIsAbandoned = abandoned;
+ }
+
/** Returns a trace tag using debug information provided by job scheduler service. */
@NonNull
public String computeSystemTraceTag() {
@@ -1119,6 +1160,13 @@ public final class JobStatus {
}
/**
+ * Returns the number of times the job stopped previously for STOP_REASON_TIMEOUT_ABANDONED.
+ */
+ public int getNumAbandonedFailures() {
+ return mNumAbandonedFailures;
+ }
+
+ /**
* Returns the number of times the system stopped a previous execution of this job for reasons
* that were likely outside the app's control.
*/
@@ -1973,6 +2021,16 @@ public final class JobStatus {
mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
}
+ final int unsatisfiedConstraints = ~satisfiedConstraints
+ & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+ populatePendingJobReasonsHistoryMap(isReady, nowElapsed, unsatisfiedConstraints);
+ final int historySize = mPendingJobReasonsHistory.size();
+ if (historySize >= PENDING_JOB_HISTORY_TRIM_THRESHOLD) {
+ // Ensure trimming doesn't occur too often - max history we currently return is 10
+ mPendingJobReasonsHistory.subList(0, historySize - PENDING_JOB_HISTORY_RETURN_LIMIT)
+ .clear();
+ }
+
return true;
}
@@ -2047,15 +2105,10 @@ public final class JobStatus {
}
}
- /**
- * If {@link #isReady()} returns false, this will return a single reason why the job isn't
- * ready. If {@link #isReady()} returns true, this will return
- * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
- */
- @JobScheduler.PendingJobReason
- public int getPendingJobReason() {
- final int unsatisfiedConstraints = ~satisfiedConstraints
- & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+ @NonNull
+ public ArrayList<Integer> constraintsToPendingJobReasons(int unsatisfiedConstraints) {
+ final ArrayList<Integer> reasons = new ArrayList<>();
+
if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
// The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
// the app is background restricted, or because we're restricting background work
@@ -2065,78 +2118,169 @@ public final class JobStatus {
// (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
// battery saver state.
if (mIsUserBgRestricted) {
- return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION);
+ } else {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE);
}
- return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
}
+ if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+ if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE)) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE);
+ }
+ }
+
if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
// The developer requested this constraint, so it makes sense to return the
// explicit constraint reason.
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW);
+ } else {
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
}
- // Hard-coding right now since the current dynamic constraint sets don't overlap
- // TODO: return based on active dynamic constraint sets when they start overlapping
- return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
}
if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
// The developer requested this constraint, so it makes sense to return the
// explicit constraint reason.
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING);
+ } else {
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
+ }
}
- // Hard-coding right now since the current dynamic constraint sets don't overlap
- // TODO: return based on active dynamic constraint sets when they start overlapping
- return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
- }
- if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
- }
- if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
- }
- if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
- }
- if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
}
if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
// The developer requested this constraint, so it makes sense to return the
// explicit constraint reason.
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE);
+ } else {
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
+ }
}
- // Hard-coding right now since the current dynamic constraint sets don't overlap
- // TODO: return based on active dynamic constraint sets when they start overlapping
- return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+
+ if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY);
+ }
+ if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER);
+ }
+ if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
}
if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH);
}
if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW);
}
if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY);
}
if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_QUOTA;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_QUOTA);
+ }
+ if (android.app.job.Flags.getPendingJobReasonsApi()) {
+ if ((CONSTRAINT_DEADLINE & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEADLINE);
+ }
+ }
+
+ return reasons;
+ }
+
+ /**
+ * This will return all potential reasons why the job is pending.
+ */
+ @NonNull
+ public int[] getPendingJobReasons(@Nullable JobRestriction restriction) {
+ final int unsatisfiedConstraints = ~satisfiedConstraints
+ & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+ final ArrayList<Integer> reasons = constraintsToPendingJobReasons(unsatisfiedConstraints);
+
+ if (restriction != null) {
+ // Currently only ThermalStatusRestriction extends the JobRestriction class and
+ // returns PENDING_JOB_REASON_DEVICE_STATE if the job is restricted because of thermal.
+ @JobScheduler.PendingJobReason final int reason = restriction.getPendingReason();
+ if (!reasons.contains(reason)) {
+ reasons.addLast(reason);
+ }
}
- if (getEffectiveStandbyBucket() == NEVER_INDEX) {
- Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
- // The user hasn't officially launched this app.
- return JobScheduler.PENDING_JOB_REASON_USER;
+ if (reasons.isEmpty()) {
+ if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+ Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+ // The user hasn't officially launched this app.
+ reasons.add(JobScheduler.PENDING_JOB_REASON_USER);
+ } else if (serviceProcessName != null) {
+ reasons.add(JobScheduler.PENDING_JOB_REASON_APP);
+ } else {
+ reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+ }
}
- if (serviceProcessName != null) {
- return JobScheduler.PENDING_JOB_REASON_APP;
+
+ final int[] reasonsArr = new int[reasons.size()];
+ for (int i = 0; i < reasonsArr.length; i++) {
+ reasonsArr[i] = reasons.get(i);
}
+ return reasonsArr;
+ }
+
+ private void populatePendingJobReasonsHistoryMap(boolean isReady,
+ long constraintTimestamp, int unsatisfiedConstraints) {
+ final long constraintTimestampEpoch = // system_boot_time + constraint_satisfied_time
+ (System.currentTimeMillis() - SystemClock.elapsedRealtime()) + constraintTimestamp;
- if (!isReady()) {
- Slog.wtf(TAG, "Unknown reason job isn't ready");
+ if (isReady) {
+ // Job is ready to execute. At this point, if the job doesn't execute, it might be
+ // because of the app itself; if not, note it as undefined (documented in javadoc).
+ mPendingJobReasonsHistory.addLast(
+ new PendingJobReasonsInfo(
+ constraintTimestampEpoch,
+ new int[] { serviceProcessName != null
+ ? JobScheduler.PENDING_JOB_REASON_APP
+ : JobScheduler.PENDING_JOB_REASON_UNDEFINED }));
+ return;
+ }
+
+ final ArrayList<Integer> reasons = constraintsToPendingJobReasons(unsatisfiedConstraints);
+ if (reasons.isEmpty()) {
+ // If the job is not waiting on any constraints to be met, note it as undefined.
+ reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+ }
+
+ final int[] reasonsArr = new int[reasons.size()];
+ for (int i = 0; i < reasonsArr.length; i++) {
+ reasonsArr[i] = reasons.get(i);
}
- return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ mPendingJobReasonsHistory.addLast(
+ new PendingJobReasonsInfo(constraintTimestampEpoch, reasonsArr));
+ }
+
+ /**
+ * Returns the last {@link #PENDING_JOB_HISTORY_RETURN_LIMIT} constraint changes.
+ */
+ @NonNull
+ public List<PendingJobReasonsInfo> getPendingJobReasonsHistory() {
+ final List<PendingJobReasonsInfo> returnList =
+ new ArrayList<>(PENDING_JOB_HISTORY_RETURN_LIMIT);
+ final int historySize = mPendingJobReasonsHistory.size();
+ if (historySize != 0) {
+ returnList.addAll(
+ mPendingJobReasonsHistory.subList(
+ Math.max(0, historySize - PENDING_JOB_HISTORY_RETURN_LIMIT),
+ historySize));
+ }
+
+ return returnList;
}
/** @return whether or not the @param constraint is satisfied */
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 a1c72fb4c06c..637c726a9bd1 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,10 +36,14 @@ 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;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -99,10 +103,10 @@ import java.util.function.Predicate;
* the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
* not be allowed to run more than 20 jobs within the past 10 minutes.
*
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
+ * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run
+ * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those
+ * states. However, jobs that are started while the app is in the TOP state do not count towards any
+ * quota and are not restricted regardless of the app's state change.
*
* Jobs will not be throttled when the device is charging. The device is considered to be charging
* once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
@@ -121,6 +125,9 @@ public final class QuotaController extends StateController {
private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
+ private static final String TRACE_QUOTA_STATE_CHANGED_TAG = "QuotaStateChanged:";
+ private static final String TRACE_QUOTA_STATE_CHANGED_DELIMITER = "#";
+
private static final int SYSTEM_APP_CHECK_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;
@@ -129,6 +136,27 @@ public final class QuotaController extends StateController {
return (int) (val ^ (val >>> 32));
}
+ /**
+ * When enabled this change id overrides the default quota policy enforcement to the jobs
+ * running in the foreground process state.
+ */
+ // TODO: b/379681266 - Might need some refactoring for a better app-compat strategy.
+ @VisibleForTesting
+ @ChangeId
+ @Disabled // Disabled by default
+ @Overridable // The change can be overridden in user build
+ static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS = 341201311L;
+
+ /**
+ * When enabled this change id overrides the default quota policy enforcement policy
+ * the jobs started when app was in the TOP state.
+ */
+ @VisibleForTesting
+ @ChangeId
+ @Disabled // Disabled by default
+ @Overridable // The change can be overridden in user build.
+ static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L;
+
@VisibleForTesting
static class ExecutionStats {
/**
@@ -394,13 +422,13 @@ public final class QuotaController extends StateController {
* minutes to run its jobs.
*/
private final long[] mBucketPeriodsMs = new long[]{
- QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS,
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS,
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS,
QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS,
0, // NEVER
QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS
};
/** The maximum period any bucket can have. */
@@ -454,7 +482,7 @@ public final class QuotaController extends StateController {
*/
private final long[] mEJLimitsMs = new long[]{
QcConstants.DEFAULT_EJ_LIMIT_ACTIVE_MS,
- QcConstants.DEFAULT_EJ_LIMIT_WORKING_MS,
+ QcConstants.DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS,
QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS,
QcConstants.DEFAULT_EJ_LIMIT_RARE_MS,
0, // NEVER
@@ -476,7 +504,8 @@ public final class QuotaController extends StateController {
/**
* Length of time used to split an app's top time into chunks.
*/
- private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+ private long mEJTopAppTimeChunkSizeMs =
+ QcConstants.DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
/**
* How much EJ quota to give back to an app based on the number of top app time chunks it had.
@@ -486,7 +515,7 @@ public final class QuotaController extends StateController {
/**
* How much EJ quota to give back to an app based on each non-top user interaction.
*/
- private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS;
+ private long mEJRewardInteractionMs = QcConstants.DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS;
/**
* How much EJ quota to give back to an app based on each notification seen event.
@@ -498,14 +527,6 @@ public final class QuotaController extends StateController {
private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
- private long mQuotaBumpAdditionalDurationMs =
- QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS;
- private int mQuotaBumpAdditionalJobCount = QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT;
- private int mQuotaBumpAdditionalSessionCount =
- QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT;
- private long mQuotaBumpWindowSizeMs = QcConstants.DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS;
- private int mQuotaBumpLimit = QcConstants.DEFAULT_QUOTA_BUMP_LIMIT;
-
/**
* List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission
* granted for each user.
@@ -567,12 +588,19 @@ public final class QuotaController extends StateController {
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ if (Flags.enforceQuotaPolicyToFgsJobs()) {
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_BOUND_TOP, null);
+ }
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, null);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
+
+ processQuotaConstantsAdjustment();
}
@Override
@@ -619,7 +647,9 @@ public final class QuotaController extends StateController {
}
final int uid = jobStatus.getSourceUid();
- if (mTopAppCache.get(uid)) {
+ if ((!Flags.enforceQuotaPolicyToTopStartedJobs()
+ || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ uid)) && mTopAppCache.get(uid)) {
if (DEBUG) {
Slog.d(TAG, jobStatus.toShortString() + " is top started job");
}
@@ -656,7 +686,11 @@ public final class QuotaController extends StateController {
timer.stopTrackingJob(jobStatus);
}
}
- mTopStartedJobs.remove(jobStatus);
+ if (!Flags.enforceQuotaPolicyToTopStartedJobs()
+ || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ jobStatus.getSourceUid())) {
+ mTopStartedJobs.remove(jobStatus);
+ }
}
@Override
@@ -767,7 +801,13 @@ 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) {
- return mTopStartedJobs.contains(jobStatus);
+ if (!Flags.enforceQuotaPolicyToTopStartedJobs()
+ || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ jobStatus.getSourceUid())) {
+ return mTopStartedJobs.contains(jobStatus);
+ }
+
+ return false;
}
/** Returns the maximum amount of time this job could run for. */
@@ -791,7 +831,9 @@ public final class QuotaController extends StateController {
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid());
final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH
- || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
+ || (!android.app.job.Flags.ignoreImportantWhileForeground()
+ && (jobStatus.getFlags()
+ & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0);
if (isInPrivilegedState && isJobImportant) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
@@ -1090,7 +1132,7 @@ public final class QuotaController extends StateController {
// essentially run until they reach the maximum limit.
if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
return calculateTimeUntilQuotaConsumedLocked(
- events, startMaxElapsed, maxExecutionTimeRemainingMs, false);
+ events, startMaxElapsed, maxExecutionTimeRemainingMs);
}
// Need to check both max time and period time in case one is less than the other.
@@ -1099,9 +1141,9 @@ public final class QuotaController extends StateController {
// bucket value.
return Math.min(
calculateTimeUntilQuotaConsumedLocked(
- events, startMaxElapsed, maxExecutionTimeRemainingMs, false),
+ events, startMaxElapsed, maxExecutionTimeRemainingMs),
calculateTimeUntilQuotaConsumedLocked(
- events, startWindowElapsed, allowedTimeRemainingMs, true));
+ events, startWindowElapsed, allowedTimeRemainingMs));
}
/**
@@ -1111,36 +1153,12 @@ public final class QuotaController extends StateController {
* @param deadSpaceMs How much time can be allowed to count towards the quota
*/
private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimedEvent> sessions,
- final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) {
+ final long windowStartElapsed, long deadSpaceMs) {
long timeUntilQuotaConsumedMs = 0;
long start = windowStartElapsed;
- int numQuotaBumps = 0;
- final long quotaBumpWindowStartElapsed =
- sElapsedRealtimeClock.millis() - mQuotaBumpWindowSizeMs;
final int numSessions = sessions.size();
- if (allowQuotaBumps) {
- for (int i = numSessions - 1; i >= 0; --i) {
- TimedEvent event = sessions.get(i);
-
- if (event instanceof QuotaBump) {
- if (event.getEndTimeElapsed() >= quotaBumpWindowStartElapsed
- && numQuotaBumps++ < mQuotaBumpLimit) {
- deadSpaceMs += mQuotaBumpAdditionalDurationMs;
- } else {
- break;
- }
- }
- }
- }
for (int i = 0; i < numSessions; ++i) {
- TimedEvent event = sessions.get(i);
-
- if (event instanceof QuotaBump) {
- continue;
- }
-
- TimingSession session = (TimingSession) event;
-
+ TimingSession session = (TimingSession) sessions.get(i);
if (session.endTimeElapsed < windowStartElapsed) {
// Outside of window. Ignore.
continue;
@@ -1325,41 +1343,15 @@ public final class QuotaController extends StateController {
final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
int sessionCountInWindow = 0;
- int numQuotaBumps = 0;
- final long quotaBumpWindowStartElapsed = nowElapsed - mQuotaBumpWindowSizeMs;
// The minimum time between the start time and the beginning of the events that were
// looked at --> how much time the stats will be valid for.
long emptyTimeMs = Long.MAX_VALUE;
// Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
// the most recent ones.
final int loopStart = events.size() - 1;
- // Process QuotaBumps first to ensure the limits are properly adjusted.
- for (int i = loopStart; i >= 0; --i) {
- TimedEvent event = events.get(i);
-
- if (event.getEndTimeElapsed() < quotaBumpWindowStartElapsed
- || numQuotaBumps >= mQuotaBumpLimit) {
- break;
- }
-
- if (event instanceof QuotaBump) {
- stats.allowedTimePerPeriodMs += mQuotaBumpAdditionalDurationMs;
- stats.jobCountLimit += mQuotaBumpAdditionalJobCount;
- stats.sessionCountLimit += mQuotaBumpAdditionalSessionCount;
- emptyTimeMs = Math.min(emptyTimeMs,
- event.getEndTimeElapsed() - quotaBumpWindowStartElapsed);
- numQuotaBumps++;
- }
- }
TimingSession lastSeenTimingSession = null;
for (int i = loopStart; i >= 0; --i) {
- TimedEvent event = events.get(i);
-
- if (event instanceof QuotaBump) {
- continue;
- }
-
- TimingSession session = (TimingSession) event;
+ TimingSession session = (TimingSession) events.get(i);
// Window management.
if (startWindowElapsed < session.endTimeElapsed) {
@@ -1464,6 +1456,13 @@ public final class QuotaController extends StateController {
}
}
+ void processQuotaConstantsAdjustment() {
+ if (Flags.adjustQuotaDefaultConstants()) {
+ mQcConstants.adjustDefaultBucketWindowSizes();
+ mQcConstants.adjustDefaultEjLimits();
+ }
+ }
+
@VisibleForTesting
void incrementJobCountLocked(final int userId, @NonNull final String packageName, int count) {
final long now = sElapsedRealtimeClock.millis();
@@ -2053,28 +2052,6 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
- static final class QuotaBump implements TimedEvent {
- // Event timestamp in elapsed realtime timebase.
- public final long eventTimeElapsed;
-
- QuotaBump(long eventElapsed) {
- this.eventTimeElapsed = eventElapsed;
- }
-
- @Override
- public long getEndTimeElapsed() {
- return eventTimeElapsed;
- }
-
- @Override
- public void dump(IndentingPrintWriter pw) {
- pw.print("Quota bump @ ");
- pw.print(eventTimeElapsed);
- pw.println();
- }
- }
-
- @VisibleForTesting
static final class ShrinkableDebits {
/** The amount of quota remaining. Can be negative if limit changes. */
private long mDebitTally;
@@ -2523,21 +2500,6 @@ public final class QuotaController extends StateController {
updateStandbyBucket(userId, packageName, bucketIndex);
});
}
-
- @Override
- public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) {
- synchronized (mLock) {
- List<TimedEvent> events = mTimingEvents.get(userId, packageName);
- if (events == null || events.size() == 0) {
- // If the app hasn't run any jobs, there's no point giving it a quota bump.
- return;
- }
- events.add(new QuotaBump(sElapsedRealtimeClock.millis()));
- invalidateAllExecutionStatsLocked(userId, packageName);
- }
- // Update jobs out of band.
- mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
- }
}
@VisibleForTesting
@@ -2706,6 +2668,16 @@ public final class QuotaController extends StateController {
}
}
+ @VisibleForTesting
+ int getProcessStateQuotaFreeThreshold(int uid) {
+ if (Flags.enforceQuotaPolicyToFgsJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) {
+ return ActivityManager.PROCESS_STATE_BOUND_TOP;
+ }
+
+ return ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
private class QcHandler extends Handler {
QcHandler(Looper looper) {
@@ -2727,11 +2699,12 @@ public final class QuotaController extends StateController {
if (timeRemainingMs <= 50) {
// Less than 50 milliseconds left. Start process of shutting down jobs.
if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
- JobSchedulerService.TRACE_TRACK_NAME,
- pkg + "#" + MSG_REACHED_TIME_QUOTA);
- }
+ final StringBuilder traceMsg = new StringBuilder();
+ traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+ .append(pkg)
+ .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+ .append(MSG_REACHED_TIME_QUOTA);
+ Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
mStateChangedListener.onControllerStateChanged(
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(),
@@ -2760,11 +2733,12 @@ public final class QuotaController extends StateController {
pkg.userId, pkg.packageName);
if (timeRemainingMs <= 0) {
if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
- JobSchedulerService.TRACE_TRACK_NAME,
- pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA);
- }
+ final StringBuilder traceMsg = new StringBuilder();
+ traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+ .append(pkg)
+ .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+ .append(MSG_REACHED_EJ_TIME_QUOTA);
+ Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
mStateChangedListener.onControllerStateChanged(
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(),
@@ -2789,11 +2763,12 @@ public final class QuotaController extends StateController {
Slog.d(TAG, pkg + " has reached its count quota.");
}
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
- JobSchedulerService.TRACE_TRACK_NAME,
- pkg + "#" + MSG_REACHED_COUNT_QUOTA);
- }
+ final StringBuilder traceMsg = new StringBuilder();
+ traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+ .append(pkg)
+ .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+ .append(MSG_REACHED_COUNT_QUOTA);
+ Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
mStateChangedListener.onControllerStateChanged(
maybeUpdateConstraintForPkgLocked(
@@ -2832,15 +2807,15 @@ public final class QuotaController extends StateController {
mTopAppCache.put(uid, true);
mTopAppGraceCache.delete(uid);
if (mForegroundUids.get(uid)) {
- // Went from FGS to TOP. We don't need to reprocess timers or
- // jobs.
+ // Went from a process state with quota free to TOP. We don't
+ // need to reprocess timers or jobs.
break;
}
mForegroundUids.put(uid, true);
isQuotaFree = true;
} else {
final boolean reprocess;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold(uid)) {
reprocess = !mForegroundUids.get(uid);
mForegroundUids.put(uid, true);
isQuotaFree = true;
@@ -3008,7 +2983,6 @@ public final class QuotaController extends StateController {
mQcConstants.mRateLimitingConstantsUpdated = false;
mQcConstants.mExecutionPeriodConstantsUpdated = false;
mQcConstants.mEJLimitConstantsUpdated = false;
- mQcConstants.mQuotaBumpConstantsUpdated = false;
}
@Override
@@ -3035,7 +3009,6 @@ public final class QuotaController extends StateController {
private boolean mRateLimitingConstantsUpdated = false;
private boolean mExecutionPeriodConstantsUpdated = false;
private boolean mEJLimitConstantsUpdated = false;
- private boolean mQuotaBumpConstantsUpdated = false;
/** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
private static final String QC_CONSTANT_PREFIX = "qc_";
@@ -3183,21 +3156,6 @@ public final class QuotaController extends StateController {
@VisibleForTesting
static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
- @VisibleForTesting
- static final String KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS =
- QC_CONSTANT_PREFIX + "quota_bump_additional_duration_ms";
- @VisibleForTesting
- static final String KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT =
- QC_CONSTANT_PREFIX + "quota_bump_additional_job_count";
- @VisibleForTesting
- static final String KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT =
- QC_CONSTANT_PREFIX + "quota_bump_additional_session_count";
- @VisibleForTesting
- static final String KEY_QUOTA_BUMP_WINDOW_SIZE_MS =
- QC_CONSTANT_PREFIX + "quota_bump_window_size_ms";
- @VisibleForTesting
- static final String KEY_QUOTA_BUMP_LIMIT =
- QC_CONSTANT_PREFIX + "quota_bump_limit";
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
10 * 60 * 1000L; // 10 minutes
@@ -3213,14 +3171,28 @@ public final class QuotaController extends StateController {
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
- private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS =
+ // Legacy default window size for EXEMPTED bucket
+ 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
- private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
+ // Legacy default window size for ACTIVE bucket
+ 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
- private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
+ // Legacy default window size for WORKING bucket
+ private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS =
2 * 60 * 60 * 1000L; // 2 hours
- private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
+ // Legacy default window size for FREQUENT bucket
+ private static final long DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS =
8 * 60 * 60 * 1000L; // 8 hours
+
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS =
+ 20 * 60 * 1000L; // 20 minutes.
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS =
+ 30 * 60 * 1000L; // 30 minutes.
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS =
+ 4 * 60 * 60 * 1000L; // 4 hours
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS =
+ 12 * 60 * 60 * 1000L; // 12 hours
+
private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS =
@@ -3234,9 +3206,9 @@ public final class QuotaController extends StateController {
75; // 75/window = 450/hr = 1/session
private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED;
private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
- (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
+ (int) (60.0 * DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
- (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
+ (int) (25.0 * DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
(int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10;
@@ -3257,24 +3229,24 @@ public final class QuotaController extends StateController {
// TODO(267949143): set a different limit for headless system apps
private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 60 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS;
- private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
+ private static final long DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
+ private static final long DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS = 15 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS;
private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS = 15 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS = 30 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS;
- private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS =
+ 30 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS =
+ 5 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS;
- private static final long DEFAULT_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS = 5 * SECOND_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0;
private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS;
- private static final long DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 1 * MINUTE_IN_MILLIS;
- private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 2;
- private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 1;
- private static final long DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS = 8 * HOUR_IN_MILLIS;
- private static final int DEFAULT_QUOTA_BUMP_LIMIT = 8;
/**
* How much time each app in the exempted bucket will have to run jobs within their standby
@@ -3321,28 +3293,28 @@ public final class QuotaController extends StateController {
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS;
+ public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
+ public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
+ public long WINDOW_SIZE_WORKING_MS = DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
+ public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
@@ -3503,7 +3475,7 @@ public final class QuotaController extends StateController {
* standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring
* in any rewards or free EJs).
*/
- public long EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_WORKING_MS;
+ public long EJ_LIMIT_WORKING_MS = DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS;
/**
* The total expedited job session limit of the particular standby bucket. Apps in this
@@ -3547,7 +3519,7 @@ public final class QuotaController extends StateController {
/**
* Length of time used to split an app's top time into chunks.
*/
- public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+ public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
/**
* How much EJ quota to give back to an app based on the number of top app time chunks it
@@ -3558,7 +3530,7 @@ public final class QuotaController extends StateController {
/**
* How much EJ quota to give back to an app based on each non-top user interaction.
*/
- public long EJ_REWARD_INTERACTION_MS = DEFAULT_EJ_REWARD_INTERACTION_MS;
+ public long EJ_REWARD_INTERACTION_MS = DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS;
/**
* How much EJ quota to give back to an app based on each notification seen event.
@@ -3576,32 +3548,51 @@ public final class QuotaController extends StateController {
*/
public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
- /**
- * How much additional session duration to give an app for each accepted quota bump.
- */
- public long QUOTA_BUMP_ADDITIONAL_DURATION_MS = DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_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;
- /**
- * How many additional regular jobs to give an app for each accepted quota bump.
- */
- public int QUOTA_BUMP_ADDITIONAL_JOB_COUNT = DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT;
+ mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS));
+ mBucketPeriodsMs[ACTIVE_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[ACTIVE_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
+ mBucketPeriodsMs[WORKING_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[WORKING_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
+ mBucketPeriodsMs[FREQUENT_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[FREQUENT_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
+ }
- /**
- * How many additional sessions to give an app for each accepted quota bump.
- */
- public int QUOTA_BUMP_ADDITIONAL_SESSION_COUNT =
- DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT;
+ 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;
- /**
- * The rolling window size within which to accept and apply quota bump events.
- */
- public long QUOTA_BUMP_WINDOW_SIZE_MS = DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS;
+ // The limit must be in the range [15 minutes, active limit].
+ mEJLimitsMs[WORKING_INDEX] = Math.max(15 * MINUTE_IN_MILLIS,
+ Math.min(mEJLimitsMs[ACTIVE_INDEX], EJ_LIMIT_WORKING_MS));
- /**
- * The maximum number of quota bumps to accept and apply within the
- * {@link #QUOTA_BUMP_WINDOW_SIZE_MS window}.
- */
- public int QUOTA_BUMP_LIMIT = DEFAULT_QUOTA_BUMP_LIMIT;
+ // Limit interaction reward to be in the range [5 seconds, 15 minutes] per event.
+ mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS,
+ Math.max(5 * SECOND_IN_MILLIS, EJ_REWARD_INTERACTION_MS));
+
+ // Limit chunking to be in the range [1 millisecond, 15 minutes] per event.
+ long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS,
+ Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS));
+ mEJTopAppTimeChunkSizeMs = newChunkSizeMs;
+ if (mEJTopAppTimeChunkSizeMs < mEJRewardTopAppMs) {
+ // Not making chunk sizes and top rewards to be the upper/lower
+ // limits of the other to allow trying different policies. Just log
+ // the discrepancy.
+ Slog.w(TAG, "EJ top app time chunk less than reward: "
+ + mEJTopAppTimeChunkSizeMs + " vs " + mEJRewardTopAppMs);
+ }
+ }
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
@@ -3639,14 +3630,6 @@ public final class QuotaController extends StateController {
updateEJLimitConstantsLocked();
break;
- case KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS:
- case KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT:
- case KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT:
- case KEY_QUOTA_BUMP_WINDOW_SIZE_MS:
- case KEY_QUOTA_BUMP_LIMIT:
- updateQuotaBumpConstantsLocked();
- break;
-
case KEY_MAX_JOB_COUNT_EXEMPTED:
MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED);
int newExemptedMaxJobCount =
@@ -3779,7 +3762,9 @@ 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, DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS);
+ properties.getLong(key, Flags.adjustQuotaDefaultConstants()
+ ? 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.
long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS,
Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS));
@@ -3815,7 +3800,9 @@ 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, DEFAULT_EJ_REWARD_INTERACTION_MS);
+ properties.getLong(key, Flags.adjustQuotaDefaultConstants()
+ ? 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
// event.
mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS,
@@ -3889,14 +3876,23 @@ public final class QuotaController extends StateController {
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,
- DEFAULT_WINDOW_SIZE_EXEMPTED_MS);
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS);
WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
- DEFAULT_WINDOW_SIZE_ACTIVE_MS);
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS);
WINDOW_SIZE_WORKING_MS =
- properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
+ properties.getLong(KEY_WINDOW_SIZE_WORKING_MS,
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS);
WINDOW_SIZE_FREQUENT_MS =
properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS,
- DEFAULT_WINDOW_SIZE_FREQUENT_MS);
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS);
WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS,
DEFAULT_WINDOW_SIZE_RARE_MS);
WINDOW_SIZE_RESTRICTED_MS =
@@ -4067,7 +4063,9 @@ 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, DEFAULT_EJ_LIMIT_WORKING_MS);
+ KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS :
+ DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS);
EJ_LIMIT_FREQUENT_MS = properties.getLong(
KEY_EJ_LIMIT_FREQUENT_MS, DEFAULT_EJ_LIMIT_FREQUENT_MS);
EJ_LIMIT_RARE_MS = properties.getLong(
@@ -4145,65 +4143,6 @@ public final class QuotaController extends StateController {
}
}
- private void updateQuotaBumpConstantsLocked() {
- if (mQuotaBumpConstantsUpdated) {
- return;
- }
- mQuotaBumpConstantsUpdated = true;
-
- // Query the values as an atomic set.
- final DeviceConfig.Properties properties = DeviceConfig.getProperties(
- DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
- KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT,
- KEY_QUOTA_BUMP_WINDOW_SIZE_MS, KEY_QUOTA_BUMP_LIMIT);
- QUOTA_BUMP_ADDITIONAL_DURATION_MS = properties.getLong(
- KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
- DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS);
- QUOTA_BUMP_ADDITIONAL_JOB_COUNT = properties.getInt(
- KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT);
- QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = properties.getInt(
- KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT,
- DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT);
- QUOTA_BUMP_WINDOW_SIZE_MS = properties.getLong(
- KEY_QUOTA_BUMP_WINDOW_SIZE_MS, DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS);
- QUOTA_BUMP_LIMIT = properties.getInt(
- KEY_QUOTA_BUMP_LIMIT, DEFAULT_QUOTA_BUMP_LIMIT);
-
- // The window must be in the range [1 hour, 24 hours].
- long newWindowSizeMs = Math.max(HOUR_IN_MILLIS,
- Math.min(MAX_PERIOD_MS, QUOTA_BUMP_WINDOW_SIZE_MS));
- if (mQuotaBumpWindowSizeMs != newWindowSizeMs) {
- mQuotaBumpWindowSizeMs = newWindowSizeMs;
- mShouldReevaluateConstraints = true;
- }
- // The limit must be nonnegative.
- int newLimit = Math.max(0, QUOTA_BUMP_LIMIT);
- if (mQuotaBumpLimit != newLimit) {
- mQuotaBumpLimit = newLimit;
- mShouldReevaluateConstraints = true;
- }
- // The job count must be nonnegative.
- int newJobAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_JOB_COUNT);
- if (mQuotaBumpAdditionalJobCount != newJobAddition) {
- mQuotaBumpAdditionalJobCount = newJobAddition;
- mShouldReevaluateConstraints = true;
- }
- // The session count must be nonnegative.
- int newSessionAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_SESSION_COUNT);
- if (mQuotaBumpAdditionalSessionCount != newSessionAddition) {
- mQuotaBumpAdditionalSessionCount = newSessionAddition;
- mShouldReevaluateConstraints = true;
- }
- // The additional duration must be in the range [0, 10 minutes].
- long newAdditionalDuration = Math.max(0,
- Math.min(10 * MINUTE_IN_MILLIS, QUOTA_BUMP_ADDITIONAL_DURATION_MS));
- if (mQuotaBumpAdditionalDurationMs != newAdditionalDuration) {
- mQuotaBumpAdditionalDurationMs = newAdditionalDuration;
- mShouldReevaluateConstraints = true;
- }
- }
-
private void dump(IndentingPrintWriter pw) {
pw.println();
pw.println("QuotaController:");
@@ -4266,15 +4205,6 @@ public final class QuotaController extends StateController {
EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println();
pw.print(KEY_EJ_GRACE_PERIOD_TOP_APP_MS, EJ_GRACE_PERIOD_TOP_APP_MS).println();
- pw.print(KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
- QUOTA_BUMP_ADDITIONAL_DURATION_MS).println();
- pw.print(KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT,
- QUOTA_BUMP_ADDITIONAL_JOB_COUNT).println();
- pw.print(KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT,
- QUOTA_BUMP_ADDITIONAL_SESSION_COUNT).println();
- pw.print(KEY_QUOTA_BUMP_WINDOW_SIZE_MS, QUOTA_BUMP_WINDOW_SIZE_MS).println();
- pw.print(KEY_QUOTA_BUMP_LIMIT, QUOTA_BUMP_LIMIT).println();
-
pw.decreaseIndent();
}
@@ -4492,37 +4422,21 @@ public final class QuotaController extends StateController {
return mQcConstants;
}
- @VisibleForTesting
- long getQuotaBumpAdditionDurationMs() {
- return mQuotaBumpAdditionalDurationMs;
- }
-
- @VisibleForTesting
- int getQuotaBumpAdditionJobCount() {
- return mQuotaBumpAdditionalJobCount;
- }
-
- @VisibleForTesting
- int getQuotaBumpAdditionSessionCount() {
- return mQuotaBumpAdditionalSessionCount;
- }
-
- @VisibleForTesting
- int getQuotaBumpLimit() {
- return mQuotaBumpLimit;
- }
-
- @VisibleForTesting
- long getQuotaBumpWindowSizeMs() {
- return mQuotaBumpWindowSizeMs;
- }
-
//////////////////////////// DATA DUMP //////////////////////////////
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
+ pw.println("Aconfig Flags:");
+ pw.println(" " + Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS
+ + ": " + Flags.adjustQuotaDefaultConstants());
+ pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS
+ + ": " + Flags.enforceQuotaPolicyToFgsJobs());
+ pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS
+ + ": " + Flags.enforceQuotaPolicyToTopStartedJobs());
+ pw.println();
+
pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
pw.println();
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 6265d9bb815f..a8641ae43509 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -629,14 +629,15 @@ public class AppIdleHistory {
}
/**
- * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
- * that corresponds to how long since the app was used.
+ * Returns the index in the array of elapsedTimeThresholds that corresponds to
+ * how long since the app was used.
* @param packageName
* @param userId
* @param elapsedRealtime current time
- * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
+ * @param screenTimeThresholds Array of screen times, in ascending order,
+ * first one is 0 (this will not be used any more)
* @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
- * @return The index whose values the app's used time exceeds (in both arrays) or {@code -1} to
+ * @return The index whose values the app's used time exceeds or {@code -1} to
* indicate that the app has never been used.
*/
int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
@@ -646,7 +647,7 @@ public class AppIdleHistory {
elapsedRealtime, false);
// If we don't have any state for the app, assume never used
if (appUsageHistory == null || appUsageHistory.lastUsedElapsedTime < 0
- || appUsageHistory.lastUsedScreenTime < 0) {
+ || (!Flags.screenTimeBypass() && appUsageHistory.lastUsedScreenTime < 0)) {
return -1;
}
@@ -659,7 +660,7 @@ public class AppIdleHistory {
if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
+ ", elapsed=" + elapsedDelta);
for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
- if (screenOnDelta >= screenTimeThresholds[i]
+ if ((Flags.screenTimeBypass() || screenOnDelta >= screenTimeThresholds[i])
&& elapsedDelta >= elapsedTimeThresholds[i]) {
return i;
}
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 c9d340757c6b..9871d713178e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -337,11 +337,11 @@ public class AppStandbyController
*/
long[] mAppStandbyElapsedThresholds = DEFAULT_ELAPSED_TIME_THRESHOLDS;
/** Minimum time a strong usage event should keep the bucket elevated. */
- long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_STRONG_USAGE_TIMEOUT;
+ long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT;
/** Minimum time a notification seen event should keep the bucket elevated. */
long mNotificationSeenTimeoutMillis = ConstantsObserver.DEFAULT_NOTIFICATION_TIMEOUT;
/** Minimum time a slice pinned event should keep the bucket elevated. */
- long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_SLICE_PINNED_TIMEOUT;
+ long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT;
/** The standby bucket that an app will be promoted on a notification-seen event */
int mNotificationSeenPromotedBucket =
ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET;
@@ -362,7 +362,9 @@ public class AppStandbyController
/** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
long mPredictionTimeoutMillis = DEFAULT_PREDICTION_TIMEOUT;
/** Maximum time a sync adapter associated with a CP should keep the buckets elevated. */
- long mSyncAdapterTimeoutMillis = ConstantsObserver.DEFAULT_SYNC_ADAPTER_TIMEOUT;
+ long mSyncAdapterTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT;
+ /** The bucket that an app will be promoted on a sync adapter associated with a CP */
+ int mSyncAdapaterPromotedBucket = STANDBY_BUCKET_ACTIVE;
/**
* Maximum time an exempted sync should keep the buckets elevated, when sync is scheduled in
* non-doze
@@ -751,7 +753,7 @@ public class AppStandbyController
userId);
synchronized (mAppIdleLock) {
reportNoninteractiveUsageCrossUserLocked(packageName, userId,
- STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_SYNC_ADAPTER,
+ mSyncAdapaterPromotedBucket, REASON_SUB_USAGE_SYNC_ADAPTER,
elapsedRealtime, mSyncAdapterTimeoutMillis, linkedProfiles);
}
}
@@ -2446,6 +2448,8 @@ public class AppStandbyController
pw.println("Flags: ");
pw.println(" " + Flags.FLAG_AVOID_IDLE_CHECK
+ ": " + Flags.avoidIdleCheck());
+ pw.println(" " + Flags.FLAG_ADJUST_DEFAULT_BUCKET_ELEVATION_PARAMS
+ + ": " + Flags.adjustDefaultBucketElevationParams());
pw.println();
synchronized (mCarrierPrivilegedLock) {
@@ -2481,6 +2485,9 @@ public class AppStandbyController
pw.print(" mSyncAdapterTimeoutMillis=");
TimeUtils.formatDuration(mSyncAdapterTimeoutMillis, pw);
pw.println();
+ pw.print(" mSyncAdapaterPromotedBucket=");
+ pw.print(standbyBucketToString(mSyncAdapaterPromotedBucket));
+ pw.println();
pw.print(" mSystemInteractionTimeoutMillis=");
TimeUtils.formatDuration(mSystemInteractionTimeoutMillis, pw);
pw.println();
@@ -3059,12 +3066,18 @@ public class AppStandbyController
public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
- public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
+ public static final long DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR;
+
+ public static final long DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT =
+ COMPRESS_TIME ? ONE_MINUTE : 5 * ONE_MINUTE;
public static final long DEFAULT_NOTIFICATION_TIMEOUT =
COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
- public static final long DEFAULT_SLICE_PINNED_TIMEOUT =
+ public static final long DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT =
COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
+
+ public static final long DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT =
+ COMPRESS_TIME ? 12 * ONE_MINUTE : 2 * ONE_HOUR;
public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET =
STANDBY_BUCKET_WORKING_SET;
public static final boolean DEFAULT_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS = false;
@@ -3073,8 +3086,11 @@ public class AppStandbyController
COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR;
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE;
- public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT =
+ public static final long DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE;
+
+ public static final long DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT =
+ COMPRESS_TIME ? ONE_MINUTE : 2 * ONE_HOUR;
public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_NON_DOZE_TIMEOUT =
COMPRESS_TIME ? (ONE_MINUTE / 2) : 10 * ONE_MINUTE;
public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_DOZE_TIMEOUT =
@@ -3117,6 +3133,9 @@ public class AppStandbyController
cr.registerContentObserver(Global.getUriFor(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED),
false, this);
mInjector.registerDeviceConfigPropertiesChangedListener(this);
+
+ processDefaultConstants();
+
// Load all the constants.
// postOneTimeCheckIdleStates() doesn't need to be called on boot.
processProperties(mInjector.getDeviceConfigProperties());
@@ -3135,6 +3154,17 @@ public class AppStandbyController
postOneTimeCheckIdleStates();
}
+ private void processDefaultConstants() {
+ if (!Flags.adjustDefaultBucketElevationParams()) {
+ return;
+ }
+
+ mSlicePinnedTimeoutMillis = DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT;
+ mSyncAdapterTimeoutMillis = DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT;
+ mSyncAdapaterPromotedBucket = STANDBY_BUCKET_WORKING_SET;
+ mStrongUsageTimeoutMillis = DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT;
+ }
+
private void processProperties(DeviceConfig.Properties properties) {
boolean timeThresholdsUpdated = false;
synchronized (mAppIdleLock) {
@@ -3182,11 +3212,16 @@ public class AppStandbyController
case KEY_SLICE_PINNED_HOLD_DURATION:
mSlicePinnedTimeoutMillis = properties.getLong(
KEY_SLICE_PINNED_HOLD_DURATION,
- DEFAULT_SLICE_PINNED_TIMEOUT);
+ Flags.adjustDefaultBucketElevationParams()
+ ? DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT
+ : DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT);
break;
case KEY_STRONG_USAGE_HOLD_DURATION:
mStrongUsageTimeoutMillis = properties.getLong(
- KEY_STRONG_USAGE_HOLD_DURATION, DEFAULT_STRONG_USAGE_TIMEOUT);
+ KEY_STRONG_USAGE_HOLD_DURATION,
+ Flags.adjustDefaultBucketElevationParams()
+ ? DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT
+ : DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT);
break;
case KEY_PREDICTION_TIMEOUT:
mPredictionTimeoutMillis = properties.getLong(
@@ -3203,7 +3238,10 @@ public class AppStandbyController
break;
case KEY_SYNC_ADAPTER_HOLD_DURATION:
mSyncAdapterTimeoutMillis = properties.getLong(
- KEY_SYNC_ADAPTER_HOLD_DURATION, DEFAULT_SYNC_ADAPTER_TIMEOUT);
+ KEY_SYNC_ADAPTER_HOLD_DURATION,
+ Flags.adjustDefaultBucketElevationParams()
+ ? DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT
+ : DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT);
break;
case KEY_EXEMPTED_SYNC_SCHEDULED_DOZE_HOLD_DURATION:
mExemptedSyncScheduledDozeTimeoutMillis = properties.getLong(