diff options
Diffstat (limited to 'apex')
16 files changed, 460 insertions, 116 deletions
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index 79aef1e6a19a..47a85498f51b 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -45,3 +45,11 @@ flag { 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 fb5ef8771c26..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; @@ -183,6 +185,16 @@ public class JobSchedulerImpl extends JobScheduler { } @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/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index 21051b520d84..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; @@ -40,6 +41,7 @@ interface IJobScheduler { 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/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index bfdd15e9b0cd..4fbd55a5d528 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -493,6 +493,34 @@ public abstract class JobScheduler { } /** + * 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/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 f079c02707e0..74d2a590086f 100644 --- a/apex/jobscheduler/service/aconfig/app_idle.aconfig +++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig @@ -21,3 +21,10 @@ flag { 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/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/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 197de37f8ac4..a5a08fb9997c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -44,6 +44,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; @@ -2099,8 +2100,12 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.v(TAG, debugPrefix + " ready=" + jobReady); } - if (!jobReady) { - return job.getPendingJobReasons(); + 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); @@ -2120,18 +2125,6 @@ public class JobSchedulerService extends com.android.server.SystemService return new int[] { JobScheduler.PENDING_JOB_REASON_APP }; } - final JobRestriction restriction = checkIfRestricted(job); - if (DEBUG) { - Slog.v(TAG, debugPrefix + " restriction=" + restriction); - } - if (restriction != null) { - // Currently this will return _DEVICE_STATE because of thermal reasons. - // TODO (b/372031023): does it make sense to move this along with the - // pendingJobReasons() call above and also get the pending reasons from - // all of the restriction controllers? - return new int[] { restriction.getPendingReason() }; - } - // 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); @@ -2155,6 +2148,20 @@ public class JobSchedulerService extends com.android.server.SystemService 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) { synchronized (mLock) { ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid); @@ -5171,6 +5178,19 @@ public class JobSchedulerService extends com.android.server.SystemService } @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.getPendingJobReasonsHistory( + uid, validateNamespace(namespace), jobId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void cancelAll() throws RemoteException { final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); @@ -5906,6 +5926,9 @@ public class JobSchedulerService extends com.android.server.SystemService 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 a4a302450849..f3bc9c747f17 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -442,6 +442,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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); break; 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 0c5be2185c2b..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; @@ -521,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. @@ -2013,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; } @@ -2087,14 +2105,10 @@ public final class JobStatus { } } - /** - * This will return all potential reasons why the job is pending. - */ @NonNull - public int[] getPendingJobReasons() { + public ArrayList<Integer> constraintsToPendingJobReasons(int unsatisfiedConstraints) { final ArrayList<Integer> reasons = new ArrayList<>(); - final int unsatisfiedConstraints = ~satisfiedConstraints - & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); + 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 @@ -2180,6 +2194,27 @@ public final class JobStatus { } } + 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 (reasons.isEmpty()) { if (getEffectiveStandbyBucket() == NEVER_INDEX) { Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); @@ -2199,6 +2234,55 @@ public final class JobStatus { 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) { + // 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); + } + 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 */ public boolean isConstraintSatisfied(int constraint) { return (satisfiedConstraints&constraint) != 0; 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 8bd3ef4f4d1a..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; @@ -132,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 { /** @@ -622,7 +647,9 @@ public final class QuotaController extends StateController { } final int uid = jobStatus.getSourceUid(); - if (!Flags.enforceQuotaPolicyToTopStartedJobs() && 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"); } @@ -659,7 +686,9 @@ public final class QuotaController extends StateController { timer.stopTrackingJob(jobStatus); } } - if (!Flags.enforceQuotaPolicyToTopStartedJobs()) { + if (!Flags.enforceQuotaPolicyToTopStartedJobs() + || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, + jobStatus.getSourceUid())) { mTopStartedJobs.remove(jobStatus); } } @@ -772,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 !Flags.enforceQuotaPolicyToTopStartedJobs() && 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. */ @@ -2634,9 +2669,13 @@ public final class QuotaController extends StateController { } @VisibleForTesting - int getProcessStateQuotaFreeThreshold() { - return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP : - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + 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 { @@ -2776,7 +2815,7 @@ public final class QuotaController extends StateController { isQuotaFree = true; } else { final boolean reprocess; - if (procState <= getProcessStateQuotaFreeThreshold()) { + if (procState <= getProcessStateQuotaFreeThreshold(uid)) { reprocess = !mForegroundUids.get(uid); mForegroundUids.put(uid, true); isQuotaFree = true; 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( |