diff options
author | 2024-11-13 13:57:20 -0800 | |
---|---|---|
committer | 2024-11-13 14:01:00 -0800 | |
commit | de53d9657cd5e352984697fc130159c474e819a9 (patch) | |
tree | 79b7d2c74d20c83b9adaddfde7862240e4d3725e /apex | |
parent | 70a1e5dcf316f8ddf6d1b3b00341c7a35a8dcc58 (diff) | |
parent | 8eecb1a1afe454e3233f27b06f9e5d41b5bec011 (diff) |
Merge 24Q4 (ab/12406339) into aosp-main-future
Bug: 370570306
Merged-In: Ic2ffd5a1f4420121b958cadb726c3e805cda1434
Change-Id: If0f7008f8e699bf1fca8d20ee5e05877a61ea002
Diffstat (limited to 'apex')
11 files changed, 304 insertions, 119 deletions
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index 80db264d0f44..5f5507587f72 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -23,3 +23,10 @@ flag { description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content." bug: "318731461" } + +flag { + name: "cleanup_empty_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" +} diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl index 96494ec28204..11d17ca749b7 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl @@ -85,6 +85,14 @@ interface IJobCallback { */ @UnsupportedAppUsage 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. + * @param jobId Unique integer used to identify this job + */ + void forceJobFinished(int jobId); + /* * Inform JobScheduler of a change in the estimated transfer payload. * diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index e833bb95a302..52a761f8d486 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -34,15 +34,21 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; +import android.system.SystemCleaner; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.Cleaner; /** * Contains the parameters used to configure/identify your job. You do not create this object * yourself, instead it is handed in to your application by the System. */ public class JobParameters implements Parcelable { + private static final String TAG = "JobParameters"; /** @hide */ public static final int INTERNAL_STOP_REASON_UNKNOWN = -1; @@ -306,6 +312,10 @@ public class JobParameters implements Parcelable { private int mStopReason = STOP_REASON_UNDEFINED; private int mInternalStopReason = INTERNAL_STOP_REASON_UNKNOWN; private String debugStopReason; // Human readable stop reason for debugging. + @Nullable + private JobCleanupCallback mJobCleanupCallback; + @Nullable + private Cleaner.Cleanable mCleanable; /** @hide */ public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras, @@ -326,6 +336,8 @@ public class JobParameters implements Parcelable { this.mTriggeredContentAuthorities = triggeredContentAuthorities; this.mNetwork = network; this.mJobNamespace = namespace; + this.mJobCleanupCallback = null; + this.mCleanable = null; } /** @@ -597,6 +609,8 @@ public class JobParameters implements Parcelable { mStopReason = in.readInt(); mInternalStopReason = in.readInt(); debugStopReason = in.readString(); + mJobCleanupCallback = null; + mCleanable = null; } /** @hide */ @@ -612,6 +626,54 @@ public class JobParameters implements Parcelable { this.debugStopReason = debugStopReason; } + /** @hide */ + public void initCleaner(JobCleanupCallback jobCleanupCallback) { + mJobCleanupCallback = jobCleanupCallback; + mCleanable = SystemCleaner.cleaner().register(this, mJobCleanupCallback); + } + + /** + * Lazy initialize the cleaner and enable it + * + * @hide + */ + public void enableCleaner() { + if (mJobCleanupCallback == null) { + initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId)); + } + mJobCleanupCallback.enableCleaner(); + } + + /** + * Disable the cleaner from running and unregister it + * + * @hide + */ + public void disableCleaner() { + if (mJobCleanupCallback != null) { + mJobCleanupCallback.disableCleaner(); + if (mCleanable != null) { + mCleanable.clean(); + mCleanable = null; + } + mJobCleanupCallback = null; + } + } + + /** @hide */ + @VisibleForTesting + @Nullable + public Cleaner.Cleanable getCleanable() { + return mCleanable; + } + + /** @hide */ + @VisibleForTesting + @Nullable + public JobCleanupCallback getJobCleanupCallback() { + return mJobCleanupCallback; + } + @Override public int describeContents() { return 0; @@ -647,6 +709,67 @@ public class JobParameters implements Parcelable { dest.writeString(debugStopReason); } + /** + * JobCleanupCallback is used track JobParameters leak. If the job is started + * and jobFinish is not called at the time of garbage collection of JobParameters + * instance, it is considered a job leak. Force finish the job. + * + * @hide + */ + public static class JobCleanupCallback implements Runnable { + private final IJobCallback mCallback; + private final int mJobId; + private boolean mIsCleanerEnabled; + + public JobCleanupCallback( + IJobCallback callback, + int jobId) { + mCallback = callback; + mJobId = jobId; + mIsCleanerEnabled = false; + } + + /** + * Check if the cleaner is enabled + * + * @hide + */ + public boolean isCleanerEnabled() { + return mIsCleanerEnabled; + } + + /** + * Enable the cleaner to detect JobParameter leak + * + * @hide + */ + public void enableCleaner() { + mIsCleanerEnabled = true; + } + + /** + * Disable the cleaner from running. + * + * @hide + */ + public void disableCleaner() { + mIsCleanerEnabled = false; + } + + /** @hide */ + @Override + public void run() { + if (!isCleanerEnabled()) { + return; + } + try { + mCallback.forceJobFinished(mJobId); + } catch (Exception e) { + Log.wtf(TAG, "Could not destroy running job", e); + } + } + } + public static final @android.annotation.NonNull Creator<JobParameters> CREATOR = new Creator<JobParameters>() { @Override public JobParameters createFromParcel(Parcel in) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java index 79d87edff9b2..5f80c52388b4 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java @@ -165,7 +165,13 @@ public abstract class JobServiceEngine { case MSG_EXECUTE_JOB: { final JobParameters params = (JobParameters) msg.obj; try { + if (Flags.cleanupEmptyJobs()) { + params.enableCleaner(); + } boolean workOngoing = JobServiceEngine.this.onStartJob(params); + if (Flags.cleanupEmptyJobs() && !workOngoing) { + params.disableCleaner(); + } ackStartMessage(params, workOngoing); } catch (Exception e) { Log.e(TAG, "Error while executing job: " + params.getJobId()); @@ -190,6 +196,9 @@ public abstract class JobServiceEngine { IJobCallback callback = params.getCallback(); if (callback != null) { try { + if (Flags.cleanupEmptyJobs()) { + 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/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index e489c1ad891a..e5389b4f96fb 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -48,3 +48,30 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "create_work_chain_by_default" + namespace: "backstage_power" + description: "Create a workchain by default when acquiring a wakelock" + bug: "352676818" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +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" +} + +flag { + name: "use_correct_process_state_for_logging" + namespace: "backstage_power" + description: "Use correct process state for statsd logging" + bug: "361308212" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index d1f1ac3c1c92..033da2df9bf6 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -116,10 +116,10 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.ThreadLocalWorkSource; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.os.WorkSource; import android.provider.DeviceConfig; import android.provider.Settings; @@ -165,6 +165,7 @@ import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.SystemTimeZone; import com.android.server.SystemTimeZone.TimeZoneConfidence; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; @@ -178,9 +179,6 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.zone.ZoneOffsetTransition; -import java.time.zone.ZoneRules; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -192,7 +190,6 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; import java.util.function.Predicate; /** @@ -232,13 +229,6 @@ public class AlarmManagerService extends SystemService { private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY; - // System properties read on some device configurations to initialize time properly and - // perform DST transitions at the bootloader level. - private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset"; - private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition"; - private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset"; - - private final Intent mBackgroundIntent = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); @@ -329,7 +319,7 @@ public class AlarmManagerService extends SystemService { */ int mSystemUiUid; - static boolean isTimeTickAlarm(Alarm a) { + private static boolean isTimeTickAlarm(Alarm a) { return a.uid == Process.SYSTEM_UID && TIME_TICK_TAG.equals(a.listenerTag); } @@ -367,6 +357,7 @@ public class AlarmManagerService extends SystemService { } // TODO(b/172085676): Move inside alarm store. + @GuardedBy("mLock") private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser = new SparseArray<>(); private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray = @@ -1794,7 +1785,8 @@ public class AlarmManagerService extends SystemService { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms(); - mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms(); + mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms() + && UserManager.supportsMultipleUsers(); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore = new UserWakeupStore(); mUserWakeupStore.init(); @@ -2125,22 +2117,6 @@ public class AlarmManagerService extends SystemService { // "GMT" if the ID is unrecognized). The parameter ID is used here rather than // newZone.getId(). It will be rejected if it is invalid. timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo); - - final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis()); - SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset)); - - final ZoneRules rules = newZone.toZoneId().getRules(); - final ZoneOffsetTransition transition = rules.nextTransition(Instant.now()); - if (null != transition) { - // Get the offset between the time after the DST transition and before. - final long transitionOffset = TimeUnit.SECONDS.toMillis(( - transition.getOffsetAfter().getTotalSeconds() - - transition.getOffsetBefore().getTotalSeconds())); - // Time when the next DST transition is programmed. - final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond()); - SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition)); - SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset)); - } } // Clear the default time zone in the system server process. This forces the next call @@ -2641,6 +2617,13 @@ public class AlarmManagerService extends SystemService { mInFlightListeners.add(callback); } } + + /** @see AlarmManagerInternal#getNextAlarmTriggerTimeForUser(int) */ + @Override + public long getNextAlarmTriggerTimeForUser(@UserIdInt int userId) { + final AlarmManager.AlarmClockInfo nextAlarm = getNextAlarmClockImpl(userId); + return nextAlarm != null ? nextAlarm.getTriggerTime() : 0; + } } boolean hasUseExactAlarmInternal(String packageName, int uid) { @@ -3015,7 +2998,7 @@ public class AlarmManagerService extends SystemService { mUseFrozenStateToDropListenerAlarms); pw.println(); pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS, - mStartUserBeforeScheduledAlarms); + Flags.startUserBeforeScheduledAlarms()); pw.decreaseIndent(); pw.println(); pw.println(); @@ -3789,8 +3772,10 @@ public class AlarmManagerService extends SystemService { } mNextAlarmClockForUser.put(userId, alarmClock); if (mStartUserBeforeScheduledAlarms) { - mUserWakeupStore.addUserWakeup(userId, convertToElapsed( - mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC)); + if (shouldAddWakeupForUser(userId)) { + mUserWakeupStore.addUserWakeup(userId, convertToElapsed( + mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC)); + } } } else { if (DEBUG_ALARM_CLOCK) { @@ -3810,6 +3795,23 @@ public class AlarmManagerService extends SystemService { } /** + * Checks whether the user is of type that needs to be started before the alarm. + */ + @VisibleForTesting + boolean shouldAddWakeupForUser(@UserIdInt int userId) { + final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); + if (umInternal.getUserInfo(userId) == null || umInternal.getUserInfo(userId).isGuest()) { + // Guest user should not be started in the background. + return false; + } else { + // SYSTEM user is always running, so no need to schedule wakeup for it. + // Profiles are excluded from the wakeup list because users can explicitly stop them and + // so starting them in the background would go against the user's intent. + return userId != UserHandle.USER_SYSTEM && umInternal.getUserInfo(userId).isFull(); + } + } + + /** * Updates NEXT_ALARM_FORMATTED and sends NEXT_ALARM_CLOCK_CHANGED_INTENT for all users * for which alarm clocks have changed since the last call to this. * @@ -3953,6 +3955,9 @@ public class AlarmManagerService extends SystemService { if (!RemovedAlarm.isLoggable(reason)) { continue; } + if (isTimeTickAlarm(removed)) { + Slog.wtf(TAG, "Removed TIME_TICK alarm"); + } RingBuffer<RemovedAlarm> bufferForUid = mRemovalHistory.get(removed.uid); if (bufferForUid == null) { bufferForUid = new RingBuffer<>(RemovedAlarm.class, REMOVAL_HISTORY_SIZE_PER_UID); @@ -4447,6 +4452,11 @@ public class AlarmManagerService extends SystemService { public void run() { ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); + synchronized (mLock) { + mLastTimeChangeClockTime = mInjector.getCurrentTimeMillis(); + mLastTimeChangeRealtime = mInjector.getElapsedRealtimeMillis(); + } + while (true) { int result = mInjector.waitForAlarm(); final long nowRTC = mInjector.getCurrentTimeMillis(); @@ -4470,10 +4480,9 @@ public class AlarmManagerService extends SystemService { expectedClockTime = lastTimeChangeClockTime + (nowELAPSED - mLastTimeChangeRealtime); } - if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime - 1000) + if (nowRTC < (expectedClockTime - 1000) || nowRTC > (expectedClockTime + 1000)) { - // The change is by at least +/- 1000 ms (or this is the first change), - // let's do it! + // The change is by at least +/- 1000 ms, let's do it! if (DEBUG_BATCH) { Slog.v(TAG, "Time changed notification from kernel; rebatching"); } @@ -4521,8 +4530,9 @@ public class AlarmManagerService extends SystemService { final int[] userIds = mUserWakeupStore.getUserIdsToWakeup(nowELAPSED); for (int i = 0; i < userIds.length; i++) { - if (!mActivityManagerInternal.startUserInBackground( - userIds[i])) { + if (mActivityManagerInternal.isUserRunning(userIds[i], 0) + || !mActivityManagerInternal.startUserInBackground( + userIds[i])) { mUserWakeupStore.removeUserWakeup(userIds[i]); } } @@ -4925,7 +4935,6 @@ public class AlarmManagerService extends SystemService { sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); if (mStartUserBeforeScheduledAlarms) { - sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); sdFilter.addAction(Intent.ACTION_USER_REMOVED); } sdFilter.addAction(Intent.ACTION_UID_REMOVED); @@ -4962,14 +4971,6 @@ public class AlarmManagerService extends SystemService { mTemporaryQuotaReserve.removeForUser(userHandle); } return; - case Intent.ACTION_LOCKED_BOOT_COMPLETED: - final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (handle >= 0) { - if (mStartUserBeforeScheduledAlarms) { - mUserWakeupStore.onUserStarted(handle); - } - } - return; case Intent.ACTION_USER_REMOVED: final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (user >= 0) { diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java index 0073335a1332..020b510269f8 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java @@ -17,11 +17,9 @@ package com.android.server.alarm; import static com.android.server.alarm.AlarmManagerService.dumpAlarmList; -import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm; import android.app.AlarmManager; import android.util.IndentingPrintWriter; -import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -88,11 +86,6 @@ public class LazyAlarmStore implements AlarmStore { if (removed.alarmClock != null && mOnAlarmClockRemoved != null) { mOnAlarmClockRemoved.run(); } - if (isTimeTickAlarm(removed)) { - // This code path is not invoked when delivering alarms, only when removing - // alarms due to the caller cancelling it or getting uninstalled, etc. - Slog.wtf(TAG, "Removed TIME_TICK alarm"); - } removedAlarms.add(removed); } } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java index 7fc630c964e5..9fe197d69ce5 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java @@ -98,12 +98,7 @@ public class UserWakeupStore { */ @GuardedBy("mUserWakeupLock") private final SparseLongArray mUserStarts = new SparseLongArray(); - /** - * A list of users that are in a phase after they have been started but before alarms were - * initialized. - */ - @GuardedBy("mUserWakeupLock") - private final SparseLongArray mStartingUsers = new SparseLongArray(); + private Executor mBackgroundExecutor; private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME); @@ -118,15 +113,12 @@ public class UserWakeupStore { } /** - * Add user wakeup for the alarm. + * Add user wakeup for the alarm if needed. * @param userId Id of the user that scheduled alarm. * @param alarmTime time when alarm is expected to trigger. */ public void addUserWakeup(int userId, long alarmTime) { synchronized (mUserWakeupLock) { - // This should not be needed, but if an app in the user is scheduling an alarm clock, we - // consider the user start complete. There is a dedicated removal when user is started. - mStartingUsers.delete(userId); mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset()); } updateUserListFile(); @@ -192,23 +184,10 @@ public class UserWakeupStore { } /** - * Move user from wakeup list to starting user list. + * Remove scheduled user wakeup from the list when it is started. */ public void onUserStarting(int userId) { - synchronized (mUserWakeupLock) { - final long wakeup = getWakeupTimeForUser(userId); - if (wakeup >= 0) { - mStartingUsers.put(userId, wakeup); - mUserStarts.delete(userId); - } - } - } - - /** - * Remove userId from starting user list once start is complete. - */ - public void onUserStarted(int userId) { - if (deleteWakeupFromStartingUsers(userId)) { + if (deleteWakeupFromUserStarts(userId)) { updateUserListFile(); } } @@ -217,7 +196,7 @@ public class UserWakeupStore { * Remove userId from the store when the user is removed. */ public void onUserRemoved(int userId) { - if (deleteWakeupFromUserStarts(userId) || deleteWakeupFromStartingUsers(userId)) { + if (deleteWakeupFromUserStarts(userId)) { updateUserListFile(); } } @@ -227,29 +206,14 @@ public class UserWakeupStore { * @return true if an entry is found and the list of wakeups changes. */ private boolean deleteWakeupFromUserStarts(int userId) { - int index; synchronized (mUserWakeupLock) { - index = mUserStarts.indexOfKey(userId); + final int index = mUserStarts.indexOfKey(userId); if (index >= 0) { mUserStarts.removeAt(index); + return true; } + return false; } - return index >= 0; - } - - /** - * Remove wakeup for a given userId from mStartingUsers. - * @return true if an entry is found and the list of wakeups changes. - */ - private boolean deleteWakeupFromStartingUsers(int userId) { - int index; - synchronized (mUserWakeupLock) { - index = mStartingUsers.indexOfKey(userId); - if (index >= 0) { - mStartingUsers.removeAt(index); - } - } - return index >= 0; } /** @@ -299,9 +263,6 @@ public class UserWakeupStore { for (int i = 0; i < mUserStarts.size(); i++) { listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i))); } - for (int i = 0; i < mStartingUsers.size(); i++) { - listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i))); - } } Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second)); for (int i = 0; i < listOfUsers.size(); i++) { @@ -329,7 +290,6 @@ public class UserWakeupStore { } synchronized (mUserWakeupLock) { mUserStarts.clear(); - mStartingUsers.clear(); } try (FileInputStream fis = userWakeupFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(fis); @@ -396,14 +356,6 @@ public class UserWakeupStore { TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw); pw.println(); } - pw.println(mStartingUsers.size() + " starting users: "); - for (int i = 0; i < mStartingUsers.size(); i++) { - pw.print("UserId: "); - pw.print(mStartingUsers.keyAt(i)); - pw.print(", userStartTime: "); - TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw); - pw.println(); - } pw.decreaseIndent(); } } 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 ff73a4922977..97c6e25eeb75 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1617,10 +1617,11 @@ public class JobSchedulerService extends com.android.server.SystemService @NonNull public WorkSource deriveWorkSource(int sourceUid, @Nullable String sourcePackageName) { - if (WorkSource.isChainedBatteryAttributionEnabled(getContext())) { + if (Flags.createWorkChainByDefault() + || WorkSource.isChainedBatteryAttributionEnabled(getContext())) { WorkSource ws = new WorkSource(); ws.createWorkChain() - .addNode(sourceUid, sourcePackageName) + .addNode(sourceUid, null) .addNode(Process.SYSTEM_UID, "JobScheduler"); return ws; } else { @@ -1668,6 +1669,20 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override + public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { + if (!Flags.removeUserDuringUserSwitch() + || from == null + || !mActivityManagerInternal.isEarlyPackageKillEnabledForUserSwitch( + from.getUserIdentifier(), + to.getUserIdentifier())) { + return; + } + synchronized (mLock) { + mStartedUsers = ArrayUtils.removeInt(mStartedUsers, from.getUserIdentifier()); + } + } + + @Override public void onUserStopping(@NonNull TargetUser user) { synchronized (mLock) { mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier()); @@ -5793,9 +5808,10 @@ public class JobSchedulerService extends com.android.server.SystemService static void dumpHelp(PrintWriter pw) { pw.println("Job Scheduler (jobscheduler) dump options:"); - pw.println(" [-h] [package] ..."); + pw.println(" [-h] [package] [--proto] ..."); pw.println(" -h: print this help"); pw.println(" [package] is an optional package name to limit the output to."); + pw.println(" --proto: output dump in protocol buffer format."); } /** Sort jobs by caller UID, then by Job ID. */ 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 d65a66c83fcc..ee246d84997f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -129,6 +129,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 = "#"; // States that a job occupies while interacting with the client. static final int VERB_BINDING = 0; @@ -292,6 +294,11 @@ public final class JobServiceContext implements ServiceConnection { } @Override + public void forceJobFinished(int jobId) { + doForceJobFinished(this, jobId); + } + + @Override public void updateEstimatedNetworkBytes(int jobId, JobWorkItem item, long downloadBytes, long uploadBytes) { doUpdateEstimatedNetworkBytes(this, jobId, item, downloadBytes, uploadBytes); @@ -473,6 +480,14 @@ public final class JobServiceContext implements ServiceConnection { mInitialDownloadedBytesFromCalling = TrafficStats.getUidRxBytes(job.getUid()); mInitialUploadedBytesFromCalling = TrafficStats.getUidTxBytes(job.getUid()); + int procState = mService.getUidProcState(job.getUid()); + if (Flags.useCorrectProcessStateForLogging() + && procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { + // Try to get the latest proc state from AMS, there might be some delay + // for the proc states worse than TRANSIENT_BACKGROUND. + procState = mActivityManagerInternal.getUidProcessState(job.getUid()); + } + FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, job.isProxyJob() ? new int[]{sourceUid, job.getUid()} : new int[]{sourceUid}, // Given that the source tag is set by the calling app, it should be connected @@ -517,7 +532,7 @@ public final class JobServiceContext implements ServiceConnection { job.getEstimatedNetworkDownloadBytes(), job.getEstimatedNetworkUploadBytes(), job.getWorkCount(), - ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())), + ActivityManager.processStateAmToProto(procState), job.getNamespaceHash(), /* system_measured_source_download_bytes */ 0, /* system_measured_source_upload_bytes */ 0, @@ -754,6 +769,35 @@ 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) { + final long ident = Binder.clearCallingIdentity(); + try { + final JobStatus executing; + synchronized (mLock) { + // not the current job, presumably it has finished in some way already + if (!verifyCallerLocked(cb)) { + return; + } + + executing = getRunningJobLocked(); + } + if (executing != null && jobId == executing.getJobId()) { + final StringBuilder stateSuffix = new StringBuilder(); + stateSuffix.append(TRACE_JOB_FORCE_FINISHED_PREFIX); + stateSuffix.append(executing.getBatteryName()); + stateSuffix.append(TRACE_JOB_FORCE_FINISHED_DELIMITER); + stateSuffix.append(executing.getJobId()); + Trace.instant(Trace.TRACE_TAG_POWER, stateSuffix.toString()); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback cb, int jobId, int workId, @BytesLong long transferredBytes) { // TODO(255393346): Make sure apps call this appropriately and monitor for abuse @@ -1528,6 +1572,13 @@ public final class JobServiceContext implements ServiceConnection { mJobPackageTracker.noteInactive(completedJob, loggingInternalStopReason, loggingDebugReason); final int sourceUid = completedJob.getSourceUid(); + int procState = mService.getUidProcState(completedJob.getUid()); + if (Flags.useCorrectProcessStateForLogging() + && procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { + // Try to get the latest proc state from AMS, there might be some delay + // for the proc states worse than TRANSIENT_BACKGROUND. + procState = mActivityManagerInternal.getUidProcessState(completedJob.getUid()); + } FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, completedJob.isProxyJob() ? new int[]{sourceUid, completedJob.getUid()} : new int[]{sourceUid}, @@ -1573,7 +1624,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getEstimatedNetworkUploadBytes(), completedJob.getWorkCount(), ActivityManager - .processStateAmToProto(mService.getUidProcState(completedJob.getUid())), + .processStateAmToProto(procState), completedJob.getNamespaceHash(), TrafficStats.getUidRxBytes(completedJob.getSourceUid()) - mInitialDownloadedBytesFromSource, 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 c3fe0314636e..c9d340757c6b 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -1989,10 +1989,8 @@ public class AppStandbyController mAdminProtectedPackages.put(userId, packageNames); } } - if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) { - if (!Flags.avoidIdleCheck()) { - postCheckIdleStates(userId); - } + if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) { + postCheckIdleStates(userId); } } |