diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index c3fc4d1603b0a..e4306e5e2bda6 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -246,6 +246,28 @@ public interface AppStandbyInternal { @ProcessState int getBroadcastResponseFgThresholdState(); + /** + * Returns the duration within which any broadcasts occurred will be treated as one broadcast + * session. + */ + long getBroadcastSessionsDurationMs(); + + /** + * Returns the duration within which any broadcasts occurred (with a corresponding response + * event) will be treated as one broadcast session. This similar to + * {@link #getBroadcastSessionsDurationMs()}, except that this duration will be used to group + * only broadcasts that have a corresponding response event into sessions. + */ + long getBroadcastSessionsWithResponseDurationMs(); + + /** + * Returns {@code true} if the response event should be attributed to all the broadcast + * sessions that occurred within the broadcast response window and {@code false} if the + * response event should be attributed to only the earliest broadcast session within the + * broadcast response window. + */ + boolean shouldNoteResponseEventForAllBroadcastSessions(); + /** * Return the last known value corresponding to the {@code key} from * {@link android.provider.DeviceConfig#NAMESPACE_APP_STANDBY} in AppStandbyController. 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 1891e06a94207..e57724909306a 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -376,6 +376,32 @@ public class AppStandbyController volatile int mBroadcastResponseFgThresholdState = ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE; + /** + * Duration (in millis) for the window within which any broadcasts occurred will be + * treated as one broadcast session. + */ + volatile long mBroadcastSessionsDurationMs = + ConstantsObserver.DEFAULT_BROADCAST_SESSIONS_DURATION_MS; + + /** + * Duration (in millis) for the window within which any broadcasts occurred ((with a + * corresponding response event) will be treated as one broadcast session. This similar to + * {@link #mBroadcastSessionsDurationMs}, except that this duration will be used to group only + * broadcasts that have a corresponding response event into sessions. + */ + volatile long mBroadcastSessionsWithResponseDurationMs = + ConstantsObserver.DEFAULT_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS; + + /** + * Denotes whether the response event should be attributed to all broadcast sessions or not. + * If this is {@code true}, then the response event should be attributed to all the broadcast + * sessions that occurred within the broadcast response window. Otherwise, the + * response event should be attributed to only the earliest broadcast session within the + * broadcast response window. + */ + volatile boolean mNoteResponseEventForAllBroadcastSessions = + ConstantsObserver.DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS; + /** * Map of last known values of keys in {@link DeviceConfig#NAMESPACE_APP_STANDBY}. * @@ -1868,6 +1894,21 @@ public class AppStandbyController return mBroadcastResponseFgThresholdState; } + @Override + public long getBroadcastSessionsDurationMs() { + return mBroadcastSessionsDurationMs; + } + + @Override + public long getBroadcastSessionsWithResponseDurationMs() { + return mBroadcastSessionsWithResponseDurationMs; + } + + @Override + public boolean shouldNoteResponseEventForAllBroadcastSessions() { + return mNoteResponseEventForAllBroadcastSessions; + } + @Override @Nullable public String getAppStandbyConstant(@NonNull String key) { @@ -2202,6 +2243,18 @@ public class AppStandbyController pw.print(ActivityManager.procStateToString(mBroadcastResponseFgThresholdState)); pw.println(); + pw.print(" mBroadcastSessionsDurationMs="); + TimeUtils.formatDuration(mBroadcastSessionsDurationMs, pw); + pw.println(); + + pw.print(" mBroadcastSessionsWithResponseDurationMs="); + TimeUtils.formatDuration(mBroadcastSessionsWithResponseDurationMs, pw); + pw.println(); + + pw.print(" mNoteResponseEventForAllBroadcastSessions="); + pw.print(mNoteResponseEventForAllBroadcastSessions); + pw.println(); + pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); pw.print(" mAllowRestrictedBucket="); @@ -2672,6 +2725,13 @@ public class AppStandbyController "broadcast_response_window_timeout_ms"; private static final String KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE = "broadcast_response_fg_threshold_state"; + private static final String KEY_BROADCAST_SESSIONS_DURATION_MS = + "broadcast_sessions_duration_ms"; + private static final String KEY_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS = + "broadcast_sessions_with_response_duration_ms"; + private static final String KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS = + "note_response_event_for_all_broadcast_sessions"; + public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS = COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = @@ -2705,6 +2765,12 @@ public class AppStandbyController 2 * ONE_MINUTE; public static final int DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP; + public static final long DEFAULT_BROADCAST_SESSIONS_DURATION_MS = + 2 * ONE_MINUTE; + public static final long DEFAULT_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS = + 2 * ONE_MINUTE; + public static final boolean DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS = + true; ConstantsObserver(Handler handler) { super(handler); @@ -2832,6 +2898,21 @@ public class AppStandbyController KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE, DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE); break; + case KEY_BROADCAST_SESSIONS_DURATION_MS: + mBroadcastSessionsDurationMs = properties.getLong( + KEY_BROADCAST_SESSIONS_DURATION_MS, + DEFAULT_BROADCAST_SESSIONS_DURATION_MS); + break; + case KEY_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS: + mBroadcastSessionsWithResponseDurationMs = properties.getLong( + KEY_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS, + DEFAULT_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS); + break; + case KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS: + mNoteResponseEventForAllBroadcastSessions = properties.getBoolean( + KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS, + DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS); + break; default: if (!timeThresholdsUpdated && (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD) diff --git a/services/usage/java/com/android/server/usage/BroadcastEvent.java b/services/usage/java/com/android/server/usage/BroadcastEvent.java index ceb79c107d30b..e56a541078e69 100644 --- a/services/usage/java/com/android/server/usage/BroadcastEvent.java +++ b/services/usage/java/com/android/server/usage/BroadcastEvent.java @@ -19,6 +19,7 @@ package com.android.server.usage; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.util.LongArrayQueue; import java.util.Objects; @@ -30,6 +31,7 @@ class BroadcastEvent { private String mTargetPackage; private int mTargetUserId; private long mIdForResponseEvent; + private final LongArrayQueue mTimestampsMs; BroadcastEvent(int sourceUid, @NonNull String targetPackage, @UserIdInt int targetUserId, long idForResponseEvent) { @@ -37,6 +39,7 @@ class BroadcastEvent { mTargetPackage = targetPackage; mTargetUserId = targetUserId; mIdForResponseEvent = idForResponseEvent; + mTimestampsMs = new LongArrayQueue(); } public int getSourceUid() { @@ -55,6 +58,14 @@ class BroadcastEvent { return mIdForResponseEvent; } + public LongArrayQueue getTimestampsMs() { + return mTimestampsMs; + } + + public void addTimestampMs(long timestampMs) { + mTimestampsMs.addLast(timestampMs); + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java index 76d2fe7917fc5..7e3990d959152 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java @@ -24,8 +24,10 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager.ProcessState; import android.app.usage.BroadcastResponseStats; +import android.os.SystemClock; import android.os.UserHandle; -import android.util.LongSparseArray; +import android.util.ArraySet; +import android.util.LongArrayQueue; import android.util.Slog; import android.util.SparseArray; @@ -89,14 +91,14 @@ class BroadcastResponseStatsTracker { return; } synchronized (mLock) { - final LongSparseArray broadcastEvents = + final ArraySet broadcastEvents = getOrCreateBroadcastEventsLocked(targetPackage, targetUser); - final BroadcastEvent broadcastEvent = new BroadcastEvent( + final BroadcastEvent broadcastEvent = getOrCreateBroadcastEvent(broadcastEvents, sourceUid, targetPackage, targetUser.getIdentifier(), idForResponseEvent); - broadcastEvents.append(timestampMs, broadcastEvent); - final BroadcastResponseStats responseStats = - getOrCreateBroadcastResponseStats(broadcastEvent); - responseStats.incrementBroadcastsDispatchedCount(1); + broadcastEvent.addTimestampMs(timestampMs); + + // Delete any old broadcast event related data so that we don't keep accumulating them. + recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent); } } @@ -120,47 +122,90 @@ class BroadcastResponseStatsTracker { @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs) { mLogger.logNotificationEvent(event, packageName, user, timestampMs); synchronized (mLock) { - final LongSparseArray broadcastEvents = + final ArraySet broadcastEvents = getBroadcastEventsLocked(packageName, user); if (broadcastEvents == null) { return; } - // TODO (206518114): Add LongSparseArray.removeAtRange() + final long broadcastResponseWindowDurationMs = + mAppStandby.getBroadcastResponseWindowDurationMs(); + final long broadcastsSessionWithResponseDurationMs = + mAppStandby.getBroadcastSessionsWithResponseDurationMs(); + final boolean recordAllBroadcastsSessionsWithinResponseWindow = + mAppStandby.shouldNoteResponseEventForAllBroadcastSessions(); for (int i = broadcastEvents.size() - 1; i >= 0; --i) { - final long dispatchTimestampMs = broadcastEvents.keyAt(i); - final long elapsedDurationMs = timestampMs - dispatchTimestampMs; - if (elapsedDurationMs <= 0) { - continue; - } - if (dispatchTimestampMs >= timestampMs) { - continue; - } - if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) { - final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i); - final BroadcastResponseStats responseStats = - getBroadcastResponseStats(broadcastEvent); - if (responseStats == null) { - continue; - } - switch (event) { - case NOTIFICATION_EVENT_TYPE_POSTED: - responseStats.incrementNotificationsPostedCount(1); - break; - case NOTIFICATION_EVENT_TYPE_UPDATED: - responseStats.incrementNotificationsUpdatedCount(1); - break; - case NOTIFICATION_EVENT_TYPE_CANCELLED: - responseStats.incrementNotificationsCancelledCount(1); - break; - default: - Slog.wtf(TAG, "Unknown event: " + event); + final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i); + recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent); + + final LongArrayQueue dispatchTimestampsMs = broadcastEvent.getTimestampsMs(); + long broadcastsSessionEndTimestampMs = 0; + // We only need to look at the broadcast events that occurred before + // this notification related event. + while (dispatchTimestampsMs.size() > 0 + && dispatchTimestampsMs.peekFirst() < timestampMs) { + final long dispatchTimestampMs = dispatchTimestampsMs.peekFirst(); + final long elapsedDurationMs = timestampMs - dispatchTimestampMs; + // Only increment the counts if the broadcast was sent not too long ago, as + // decided by 'broadcastResponseWindowDurationMs' and is part of a new session. + // That is, it occurred 'broadcastsSessionWithResponseDurationMs' after the + // previously handled broadcast event which is represented by + // 'broadcastsSessionEndTimestampMs'. + if (elapsedDurationMs <= broadcastResponseWindowDurationMs + && dispatchTimestampMs >= broadcastsSessionEndTimestampMs) { + if (broadcastsSessionEndTimestampMs != 0 + && !recordAllBroadcastsSessionsWithinResponseWindow) { + break; + } + final BroadcastResponseStats responseStats = + getOrCreateBroadcastResponseStats(broadcastEvent); + responseStats.incrementBroadcastsDispatchedCount(1); + broadcastsSessionEndTimestampMs = dispatchTimestampMs + + broadcastsSessionWithResponseDurationMs; + switch (event) { + case NOTIFICATION_EVENT_TYPE_POSTED: + responseStats.incrementNotificationsPostedCount(1); + break; + case NOTIFICATION_EVENT_TYPE_UPDATED: + responseStats.incrementNotificationsUpdatedCount(1); + break; + case NOTIFICATION_EVENT_TYPE_CANCELLED: + responseStats.incrementNotificationsCancelledCount(1); + break; + default: + Slog.wtf(TAG, "Unknown event: " + event); + } } + dispatchTimestampsMs.removeFirst(); + } + if (dispatchTimestampsMs.size() == 0) { + broadcastEvents.removeAt(i); } - broadcastEvents.removeAt(i); } } } + @GuardedBy("mLock") + private void recordAndPruneOldBroadcastDispatchTimestamps(BroadcastEvent broadcastEvent) { + final LongArrayQueue timestampsMs = broadcastEvent.getTimestampsMs(); + final long broadcastResponseWindowDurationMs = + mAppStandby.getBroadcastResponseWindowDurationMs(); + final long broadcastsSessionDurationMs = + mAppStandby.getBroadcastSessionsDurationMs(); + final long nowElapsedMs = SystemClock.elapsedRealtime(); + long broadcastsSessionEndTimestampMs = 0; + while (timestampsMs.size() > 0 + && timestampsMs.peekFirst() < (nowElapsedMs - broadcastResponseWindowDurationMs)) { + final long eventTimestampMs = timestampsMs.peekFirst(); + if (eventTimestampMs >= broadcastsSessionEndTimestampMs) { + final BroadcastResponseStats responseStats = + getOrCreateBroadcastResponseStats(broadcastEvent); + responseStats.incrementBroadcastsDispatchedCount(1); + broadcastsSessionEndTimestampMs = eventTimestampMs + broadcastsSessionDurationMs; + } + timestampsMs.removeFirst(); + } + } + @NonNull List queryBroadcastResponseStats(int callingUid, @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId) { final List broadcastResponseStatsList = new ArrayList<>(); @@ -247,7 +292,7 @@ class BroadcastResponseStatsTracker { @GuardedBy("mLock") @Nullable - private LongSparseArray getBroadcastEventsLocked( + private ArraySet getBroadcastEventsLocked( @NonNull String packageName, UserHandle user) { final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get( user.getIdentifier()); @@ -259,7 +304,7 @@ class BroadcastResponseStatsTracker { @GuardedBy("mLock") @NonNull - private LongSparseArray getOrCreateBroadcastEventsLocked( + private ArraySet getOrCreateBroadcastEventsLocked( @NonNull String packageName, UserHandle user) { UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(user.getIdentifier()); if (userBroadcastEvents == null) { @@ -269,16 +314,6 @@ class BroadcastResponseStatsTracker { return userBroadcastEvents.getOrCreateBroadcastEvents(packageName); } - @GuardedBy("mLock") - @Nullable - private BroadcastResponseStats getBroadcastResponseStats( - @NonNull BroadcastEvent broadcastEvent) { - final int sourceUid = broadcastEvent.getSourceUid(); - final SparseArray responseStatsForUid = - mUserResponseStats.get(sourceUid); - return getBroadcastResponseStats(responseStatsForUid, broadcastEvent); - } - @GuardedBy("mLock") @Nullable private BroadcastResponseStats getBroadcastResponseStats( @@ -315,6 +350,20 @@ class BroadcastResponseStatsTracker { return userResponseStats.getOrCreateBroadcastResponseStats(broadcastEvent); } + private static BroadcastEvent getOrCreateBroadcastEvent( + ArraySet broadcastEvents, + int sourceUid, String targetPackage, int targetUserId, long idForResponseEvent) { + final BroadcastEvent broadcastEvent = new BroadcastEvent( + sourceUid, targetPackage, targetUserId, idForResponseEvent); + final int index = broadcastEvents.indexOf(broadcastEvent); + if (index >= 0) { + return broadcastEvents.valueAt(index); + } else { + broadcastEvents.add(broadcastEvent); + return broadcastEvent; + } + } + void dump(@NonNull IndentingPrintWriter ipw) { ipw.println("Broadcast response stats:"); ipw.increaseIndent(); diff --git a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java index 0ec59c3e022f5..fcef01058cbce 100644 --- a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java +++ b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java @@ -19,7 +19,8 @@ package com.android.server.usage; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; -import android.util.LongSparseArray; +import android.util.ArraySet; +import android.util.LongArrayQueue; import android.util.TimeUtils; import com.android.internal.util.IndentingPrintWriter; @@ -30,17 +31,17 @@ class UserBroadcastEvents { * Here targetPackage refers to the package receiving the broadcast and BroadcastEvent objects * corresponding to each broadcast it is receiving. */ - private ArrayMap> mBroadcastEvents = new ArrayMap(); + private ArrayMap> mBroadcastEvents = new ArrayMap(); - @Nullable LongSparseArray getBroadcastEvents(@NonNull String packageName) { + @Nullable ArraySet getBroadcastEvents(@NonNull String packageName) { return mBroadcastEvents.get(packageName); } - @NonNull LongSparseArray getOrCreateBroadcastEvents( + @NonNull ArraySet getOrCreateBroadcastEvents( @NonNull String packageName) { - LongSparseArray broadcastEvents = mBroadcastEvents.get(packageName); + ArraySet broadcastEvents = mBroadcastEvents.get(packageName); if (broadcastEvents == null) { - broadcastEvents = new LongSparseArray<>(); + broadcastEvents = new ArraySet<>(); mBroadcastEvents.put(packageName, broadcastEvents); } return broadcastEvents; @@ -56,7 +57,7 @@ class UserBroadcastEvents { void clear(int uid) { for (int i = mBroadcastEvents.size() - 1; i >= 0; --i) { - final LongSparseArray broadcastEvents = mBroadcastEvents.valueAt(i); + final ArraySet broadcastEvents = mBroadcastEvents.valueAt(i); for (int j = broadcastEvents.size() - 1; j >= 0; --j) { if (broadcastEvents.valueAt(j).getSourceUid() == uid) { broadcastEvents.removeAt(j); @@ -68,18 +69,26 @@ class UserBroadcastEvents { void dump(@NonNull IndentingPrintWriter ipw) { for (int i = 0; i < mBroadcastEvents.size(); ++i) { final String packageName = mBroadcastEvents.keyAt(i); - final LongSparseArray broadcastEvents = mBroadcastEvents.valueAt(i); + final ArraySet broadcastEvents = mBroadcastEvents.valueAt(i); ipw.println(packageName + ":"); ipw.increaseIndent(); if (broadcastEvents.size() == 0) { ipw.println(""); } else { for (int j = 0; j < broadcastEvents.size(); ++j) { - final long timestampMs = broadcastEvents.keyAt(j); final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(j); - TimeUtils.formatDuration(timestampMs, ipw); - ipw.print(": "); ipw.println(broadcastEvent); + ipw.increaseIndent(); + final LongArrayQueue timestampsMs = broadcastEvent.getTimestampsMs(); + for (int timestampIdx = 0; timestampIdx < timestampsMs.size(); ++timestampIdx) { + if (timestampIdx > 0) { + ipw.print(','); + } + final long timestampMs = timestampsMs.get(timestampIdx); + TimeUtils.formatDuration(timestampMs, ipw); + } + ipw.println(); + ipw.decreaseIndent(); } } ipw.decreaseIndent();