diff options
6 files changed, 1023 insertions, 21 deletions
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index fde49d210f80..5fa8856f2940 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -129,6 +129,7 @@ public class Notifier { private final WindowManagerPolicy mPolicy; private final FaceDownDetector mFaceDownDetector; private final ScreenUndimDetector mScreenUndimDetector; + private final WakefulnessSessionObserver mWakefulnessSessionObserver; private final ActivityManagerInternal mActivityManagerInternal; private final InputManagerInternal mInputManagerInternal; private final InputMethodManagerInternal mInputMethodManagerInternal; @@ -197,6 +198,7 @@ public class Notifier { mPolicy = policy; mFaceDownDetector = faceDownDetector; mScreenUndimDetector = screenUndimDetector; + mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, null); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); @@ -286,6 +288,8 @@ public class Notifier { } mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags); + + mWakefulnessSessionObserver.onWakeLockAcquired(flags); } public void onLongPartialWakeLockStart(String tag, int ownerUid, WorkSource workSource, @@ -386,6 +390,16 @@ public class Notifier { public void onWakeLockReleased(int flags, String tag, String packageName, int ownerUid, int ownerPid, WorkSource workSource, String historyTag, IWakeLockCallback callback) { + onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag, + callback, ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN); + } + + /** + * Called when a wake lock is released. + */ + public void onWakeLockReleased(int flags, String tag, String packageName, + int ownerUid, int ownerPid, WorkSource workSource, String historyTag, + IWakeLockCallback callback, int releaseReason) { if (DEBUG) { Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag + "\", packageName=" + packageName @@ -409,6 +423,8 @@ public class Notifier { } } mWakeLockLog.onWakeLockReleased(tag, ownerUid); + + mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason); } /** Shows the keyguard without requesting the device to immediately lock. */ @@ -670,6 +686,8 @@ public class Notifier { interactivity.changeStartTime = eventTime; interactivity.isChanging = true; handleEarlyInteractiveChange(groupId); + mWakefulnessSessionObserver.onWakefulnessChangeStarted(groupId, wakefulness, + changeReason, eventTime); } } @@ -680,6 +698,7 @@ public class Notifier { */ public void onGroupRemoved(int groupId) { mInteractivityByGroupId.remove(groupId); + mWakefulnessSessionObserver.removePowerGroup(groupId); } /** @@ -693,6 +712,8 @@ public class Notifier { try { mBatteryStats.noteUserActivity(uid, event); + mWakefulnessSessionObserver.notifyUserActivity( + SystemClock.uptimeMillis(), displayGroupId, event); } catch (RemoteException ex) { // Ignore } @@ -798,6 +819,8 @@ public class Notifier { if (mWakeLockLog != null) { mWakeLockLog.dump(pw); } + + mWakefulnessSessionObserver.dump(pw); } private void updatePendingBroadcastLocked() { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index eb1f7202f317..bbb59ce27f1a 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -231,7 +231,7 @@ public final class PowerManagerService extends SystemService // Default timeout in milliseconds. This is only used until the settings // provider populates the actual default value (R.integer.def_screen_off_timeout). - private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; + static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; private static final int DEFAULT_SLEEP_TIMEOUT = -1; // Screen brightness boost timeout. @@ -1417,8 +1417,9 @@ public final class PowerManagerService extends SystemService updateSettingsLocked(); if (mFeatureFlags.isEarlyScreenTimeoutDetectorEnabled()) { mScreenTimeoutOverridePolicy = new ScreenTimeoutOverridePolicy(mContext, - mMinimumScreenOffTimeoutConfig, () -> { + mMinimumScreenOffTimeoutConfig, (releaseReason) -> { Message msg = mHandler.obtainMessage(MSG_RELEASE_ALL_OVERRIDE_WAKE_LOCKS); + msg.arg1 = releaseReason; mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); }); } @@ -1827,6 +1828,12 @@ public final class PowerManagerService extends SystemService @GuardedBy("mLock") private void removeWakeLockNoUpdateLocked(WakeLock wakeLock, int index) { + removeWakeLockNoUpdateLocked(wakeLock, index, + ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN); + } + + @GuardedBy("mLock") + private void removeWakeLockNoUpdateLocked(WakeLock wakeLock, int index, int releaseReason) { mWakeLocks.remove(index); UidState state = wakeLock.mUidState; state.mNumWakeLocks--; @@ -1835,7 +1842,7 @@ public final class PowerManagerService extends SystemService mUidState.remove(state.mUid); } - notifyWakeLockReleasedLocked(wakeLock); + notifyWakeLockReleasedLocked(wakeLock, releaseReason); applyWakeLockFlagsOnReleaseLocked(wakeLock); mDirty |= DIRTY_WAKE_LOCKS; } @@ -2001,12 +2008,17 @@ public final class PowerManagerService extends SystemService @GuardedBy("mLock") private void notifyWakeLockReleasedLocked(WakeLock wakeLock) { + notifyWakeLockReleasedLocked(wakeLock, ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN); + } + + @GuardedBy("mLock") + private void notifyWakeLockReleasedLocked(WakeLock wakeLock, int releaseReason) { if (mSystemReady && wakeLock.mNotifiedAcquired) { wakeLock.mNotifiedAcquired = false; wakeLock.mAcquireTime = 0; mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, - wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback); + wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback, releaseReason); notifyWakeLockLongFinishedLocked(wakeLock); } } @@ -5345,7 +5357,7 @@ public final class PowerManagerService extends SystemService handleAttentiveTimeout(); break; case MSG_RELEASE_ALL_OVERRIDE_WAKE_LOCKS: - releaseAllOverrideWakeLocks(); + releaseAllOverrideWakeLocks(msg.arg1); break; } @@ -7269,7 +7281,7 @@ public final class PowerManagerService extends SystemService return false; } - private void releaseAllOverrideWakeLocks() { + private void releaseAllOverrideWakeLocks(int releaseReason) { synchronized (mLock) { final int size = mWakeLocks.size(); boolean change = false; @@ -7277,7 +7289,7 @@ public final class PowerManagerService extends SystemService final WakeLock wakeLock = mWakeLocks.get(i); if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) == PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK) { - removeWakeLockNoUpdateLocked(wakeLock, i); + removeWakeLockNoUpdateLocked(wakeLock, i, releaseReason); change = true; } } diff --git a/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java b/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java index adde5189115c..dcb3c398db68 100644 --- a/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java +++ b/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java @@ -23,6 +23,7 @@ import static com.android.server.power.PowerManagerService.WAKE_LOCK_SCREEN_BRIG import static com.android.server.power.PowerManagerService.WAKE_LOCK_SCREEN_DIM; import static com.android.server.power.PowerManagerService.WAKE_LOCK_SCREEN_TIMEOUT_OVERRIDE; +import android.annotation.IntDef; import android.content.Context; import android.os.PowerManager; import android.util.IndentingPrintWriter; @@ -31,6 +32,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Policy that handle the screen timeout override wake lock behavior. @@ -50,25 +53,62 @@ final class ScreenTimeoutOverridePolicy { public static final int RELEASE_REASON_NON_INTERACTIVE = 1; /** - * Release reason code: Release because user activity occurred. + * Release reason code: Release because a screen lock is acquired. */ - public static final int RELEASE_REASON_USER_ACTIVITY = 2; + public static final int RELEASE_REASON_SCREEN_LOCK = 2; + /** - * Release reason code: Release because a screen lock is acquired. + * Release reason code: Release because user activity attention occurs. + */ + public static final int RELEASE_REASON_USER_ACTIVITY_ATTENTION = 3; + + /** + * Release reason code: Release because user activity other occurs. + */ + public static final int RELEASE_REASON_USER_ACTIVITY_OTHER = 4; + + /** + * Release reason code: Release because user activity button occurs. + */ + public static final int RELEASE_REASON_USER_ACTIVITY_BUTTON = 5; + + /** + * Release reason code: Release because user activity touch occurs. + */ + public static final int RELEASE_REASON_USER_ACTIVITY_TOUCH = 6; + + /** + * Release reason code: Release because user activity accessibility occurs. + */ + public static final int RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY = 7; + + /** + * @hide */ - public static final int RELEASE_REASON_SCREEN_LOCK = 3; + @IntDef(prefix = { "RELEASE_REASON_" }, value = { + RELEASE_REASON_UNKNOWN, + RELEASE_REASON_NON_INTERACTIVE, + RELEASE_REASON_SCREEN_LOCK, + RELEASE_REASON_USER_ACTIVITY_ATTENTION, + RELEASE_REASON_USER_ACTIVITY_OTHER, + RELEASE_REASON_USER_ACTIVITY_BUTTON, + RELEASE_REASON_USER_ACTIVITY_TOUCH, + RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ReleaseReason{} // The screen timeout override config in milliseconds. private long mScreenTimeoutOverrideConfig; // The last reason that wake locks had been released by service. - private int mLastAutoReleaseReason = RELEASE_REASON_UNKNOWN; + private @ReleaseReason int mLastAutoReleaseReason = RELEASE_REASON_UNKNOWN; interface PolicyCallback { /** * Notify {@link PowerManagerService} to release all override wake locks. */ - void releaseAllScreenTimeoutOverrideWakelocks(); + void releaseAllScreenTimeoutOverrideWakelocks(@ReleaseReason int reason); } private PolicyCallback mPolicyCallback; @@ -110,11 +150,20 @@ final class ScreenTimeoutOverridePolicy { switch (event) { case PowerManager.USER_ACTIVITY_EVENT_ATTENTION: + releaseAllWakeLocks(RELEASE_REASON_USER_ACTIVITY_ATTENTION); + return; case PowerManager.USER_ACTIVITY_EVENT_OTHER: + releaseAllWakeLocks(RELEASE_REASON_USER_ACTIVITY_OTHER); + return; case PowerManager.USER_ACTIVITY_EVENT_BUTTON: + releaseAllWakeLocks(RELEASE_REASON_USER_ACTIVITY_BUTTON); + return; case PowerManager.USER_ACTIVITY_EVENT_TOUCH: + releaseAllWakeLocks(RELEASE_REASON_USER_ACTIVITY_TOUCH); + return; case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY: - releaseAllWakeLocks(RELEASE_REASON_USER_ACTIVITY); + releaseAllWakeLocks(RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY); + return; } } @@ -154,8 +203,8 @@ final class ScreenTimeoutOverridePolicy { + " (reason=" + mLastAutoReleaseReason + ")"); } - private void releaseAllWakeLocks(int reason) { - mPolicyCallback.releaseAllScreenTimeoutOverrideWakelocks(); + private void releaseAllWakeLocks(@ReleaseReason int reason) { + mPolicyCallback.releaseAllScreenTimeoutOverrideWakelocks(reason); mLastAutoReleaseReason = reason; logReleaseReason(); } diff --git a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java new file mode 100644 index 000000000000..d57cd5df41db --- /dev/null +++ b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java @@ -0,0 +1,595 @@ +/* + * Copyright 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 com.android.server.power; + +import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; +import static android.os.PowerManagerInternal.isInteractive; + +import static com.android.server.power.PowerManagerService.DEFAULT_SCREEN_OFF_TIMEOUT; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_NON_INTERACTIVE; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_SCREEN_LOCK; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_ATTENTION; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_BUTTON; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_OTHER; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_TOUCH; + +import android.annotation.IntDef; +import android.app.ActivityManager; +import android.app.SynchronousUserSwitchObserver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.IndentingPrintWriter; +import android.util.SparseArray; +import android.view.Display; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Observe the wakefulness session of the device, tracking the reason and the + * last user activity when the interactive state is off. + */ +public class WakefulnessSessionObserver { + private static final String TAG = "WakefulnessSessionObserver"; + + private static final int OFF_REASON_UNKNOWN = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__INTERACTIVE_STATE_OFF_REASON__UNKNOWN; + private static final int OFF_REASON_TIMEOUT = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__INTERACTIVE_STATE_OFF_REASON__TIMEOUT; + @VisibleForTesting + protected static final int OFF_REASON_POWER_BUTTON = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__INTERACTIVE_STATE_OFF_REASON__POWER_BUTTON; + + /** + * Interactive off reason + * {@link android.os.statsd.power.ScreenInteractiveSessionReported.InteractiveStateOffReason}. + */ + @IntDef(prefix = {"OFF_REASON_"}, value = { + OFF_REASON_UNKNOWN, + OFF_REASON_TIMEOUT, + OFF_REASON_POWER_BUTTON + }) + @Retention(RetentionPolicy.SOURCE) + private @interface OffReason {} + + private static final int OVERRIDE_OUTCOME_UNKNOWN = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__UNKNOWN; + @VisibleForTesting + protected static final int OVERRIDE_OUTCOME_TIMEOUT_SUCCESS = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__TIMEOUT_SUCCESS; + @VisibleForTesting + protected static final int OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__TIMEOUT_USER_INITIATED_REVERT; + private static final int OVERRIDE_OUTCOME_CANCEL_CLIENT_API_CALL = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_CLIENT_API_CALL; + @VisibleForTesting + protected static final int OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_USER_INTERACTION; + @VisibleForTesting + protected static final int OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_POWER_BUTTON; + private static final int OVERRIDE_OUTCOME_CANCEL_CLIENT_DISCONNECT = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_CLIENT_DISCONNECTED; + private static final int OVERRIDE_OUTCOME_CANCEL_OTHER = FrameworkStatsLog + .SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_OTHER; + + /** + * Override Outcome + * {@link android.os.statsd.power.ScreenTimeoutOverrideReported.OverrideOutcome}. + */ + @IntDef(prefix = {"OVERRIDE_OUTCOME_"}, value = { + OVERRIDE_OUTCOME_UNKNOWN, + OVERRIDE_OUTCOME_TIMEOUT_SUCCESS, + OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT, + OVERRIDE_OUTCOME_CANCEL_CLIENT_API_CALL, + OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION, + OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON, + OVERRIDE_OUTCOME_CANCEL_CLIENT_DISCONNECT, + OVERRIDE_OUTCOME_CANCEL_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + private @interface OverrideOutcome {} + + private static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER; + private static final long TIMEOUT_USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L; + private static final long SEND_OVERRIDE_TIMEOUT_LOG_THRESHOLD_MILLIS = 1000L; + + private Context mContext; + private int mScreenOffTimeoutMs; + private int mOverrideTimeoutMs = 0; + @VisibleForTesting + protected final SparseArray<WakefulnessSessionPowerGroup> mPowerGroups = new SparseArray<>(); + @VisibleForTesting + protected WakefulnessSessionFrameworkStatsLogger mWakefulnessSessionFrameworkStatsLogger; + private final Clock mClock; + private final Object mLock = new Object(); + + public WakefulnessSessionObserver(Context context, Injector injector) { + if (injector == null) { + injector = new Injector(); + } + + mContext = context; + mWakefulnessSessionFrameworkStatsLogger = injector + .getWakefulnessSessionFrameworkStatsLogger(); + mClock = injector.getClock(); + updateSettingScreenOffTimeout(context); + + try { + final UserSwitchObserver observer = new UserSwitchObserver(); + ActivityManager.getService().registerUserSwitchObserver(observer, TAG); + } catch (RemoteException e) { + // Shouldn't happen since in-process. + } + + mOverrideTimeoutMs = mContext.getResources().getInteger( + com.android.internal.R.integer.config_screenTimeoutOverride); + + mContext.getContentResolver() + .registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_OFF_TIMEOUT), + false, + new ContentObserver(new Handler(mContext.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateSettingScreenOffTimeout(mContext); + } + }, + UserHandle.USER_ALL); + + mPowerGroups.append( + Display.DEFAULT_DISPLAY_GROUP, + new WakefulnessSessionPowerGroup(Display.DEFAULT_DISPLAY_GROUP)); + } + + /** + * Track the user activity event. + * + * @param eventTime Activity time, in uptime millis. + * @param powerGroupId Power Group Id for this user activity + * @param event Activity type as defined in {@link PowerManager}. {@link + * android.hardware.display.DisplayManagerInternal.DisplayPowerRequest} + */ + public void notifyUserActivity( + long eventTime, int powerGroupId, @PowerManager.UserActivityEvent int event) { + if (!mPowerGroups.contains(powerGroupId)) { + mPowerGroups.append(powerGroupId, new WakefulnessSessionPowerGroup(powerGroupId)); + } + mPowerGroups.get(powerGroupId).notifyUserActivity(eventTime, event); + } + + /** + * Track the system wakefulness + * + * @param powerGroupId Power Group Id for this wakefulness changes + * @param wakefulness Wakefulness as defined in {@link PowerManagerInternal} + * @param changeReason Reason of the go to sleep in + * {@link PowerManager.GoToSleepReason} or {@link PowerManager.WakeReason} + * @param eventTime timestamp of the wakefulness changes + */ + public void onWakefulnessChangeStarted(int powerGroupId, int wakefulness, int changeReason, + long eventTime) { + if (!mPowerGroups.contains(powerGroupId)) { + mPowerGroups.append(powerGroupId, new WakefulnessSessionPowerGroup(powerGroupId)); + } + mPowerGroups.get(powerGroupId).onWakefulnessChangeStarted(wakefulness, changeReason, + eventTime); + } + + /** + * Track the acquired wakelocks + * + * @param flags wakelocks to be acquired {@link PowerManager} + */ + public void onWakeLockAcquired(int flags) { + int maskedFlag = flags & PowerManager.WAKE_LOCK_LEVEL_MASK; + if (maskedFlag == PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK) { + for (int idx = 0; idx < mPowerGroups.size(); idx++) { + mPowerGroups.valueAt(idx).acquireTimeoutOverrideWakeLock(); + } + } + } + + /** + * Track the released wakelocks + * + * @param flags wakelocks to be released {@link PowerManager} + * @param releaseReason the reason to release wakelock + * {@link ScreenTimeoutOverridePolicy.ReleaseReason} + */ + public void onWakeLockReleased(int flags, int releaseReason) { + int maskedFlag = flags & PowerManager.WAKE_LOCK_LEVEL_MASK; + if (maskedFlag == PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK) { + for (int idx = 0; idx < mPowerGroups.size(); idx++) { + mPowerGroups.valueAt(idx).releaseTimeoutOverrideWakeLock(releaseReason); + } + } + } + + /** + * Remove the inactive power group + * + * @param powerGroupId Power Group Id that should be removed + */ + public void removePowerGroup(int powerGroupId) { + if (mPowerGroups.contains((powerGroupId))) { + mPowerGroups.delete(powerGroupId); + } + } + + void dump(PrintWriter writer) { + writer.println(); + writer.println("Wakefulness Session Observer:"); + writer.println("default timeout: " + mScreenOffTimeoutMs); + writer.println("override timeout: " + mOverrideTimeoutMs); + IndentingPrintWriter indentingPrintWriter = new IndentingPrintWriter(writer); + indentingPrintWriter.increaseIndent(); + for (int idx = 0; idx < mPowerGroups.size(); idx++) { + mPowerGroups.valueAt(idx).dump(indentingPrintWriter); + } + writer.println(); + } + + private void updateSettingScreenOffTimeout(Context context) { + synchronized (mLock) { + mScreenOffTimeoutMs = Settings.System.getIntForUser( + context.getContentResolver(), + Settings.System.SCREEN_OFF_TIMEOUT, + DEFAULT_SCREEN_OFF_TIMEOUT, + UserHandle.USER_CURRENT); + } + } + + private int getScreenOffTimeout() { + synchronized (mLock) { + return mScreenOffTimeoutMs; + } + } + + /** Screen Session by each power group */ + @VisibleForTesting + protected class WakefulnessSessionPowerGroup { + private static final long TIMEOUT_OFF_RESET_TIMESTAMP = -1; + + private int mPowerGroupId; + private int mCurrentWakefulness; + private boolean mIsInteractive = false; + // state on start timestamp: will be used in state off to calculate the duration of state on + private long mInteractiveStateOnStartTimestamp; + @VisibleForTesting + protected long mCurrentUserActivityTimestamp; + @VisibleForTesting + protected @PowerManager.UserActivityEvent int mCurrentUserActivityEvent; + @VisibleForTesting + protected long mPrevUserActivityTimestamp; + @VisibleForTesting + protected @PowerManager.UserActivityEvent int mPrevUserActivityEvent; + // to track the Override Timeout is set (that is, on SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK) + private int mTimeoutOverrideWakeLockCounter = 0; + // The timestamp when Override Timeout is set to false + private @ScreenTimeoutOverridePolicy.ReleaseReason int mTimeoutOverrideReleaseReason; + // The timestamp when state off by timeout occurs + // will set TIMEOUT_OFF_RESET_TIMESTAMP if state on or state off by power button + private long mTimeoutOffTimestamp; + // The timestamp for the latest logTimeoutOverrideEvent calling + private long mSendOverrideTimeoutLogTimestamp; + + public WakefulnessSessionPowerGroup(int powerGroupId) { + mCurrentUserActivityEvent = DEFAULT_USER_ACTIVITY; + mCurrentUserActivityTimestamp = -1; + mPrevUserActivityEvent = DEFAULT_USER_ACTIVITY; + mPrevUserActivityTimestamp = -1; + mPowerGroupId = powerGroupId; + } + + public void notifyUserActivity(long eventTime, @PowerManager.UserActivityEvent int event) { + // only track when user activity changes + if (event == mCurrentUserActivityEvent) { + return; + } + mPrevUserActivityEvent = mCurrentUserActivityEvent; + mCurrentUserActivityEvent = event; + mPrevUserActivityTimestamp = mCurrentUserActivityTimestamp; + mCurrentUserActivityTimestamp = eventTime; + } + + public void onWakefulnessChangeStarted(int wakefulness, int changeReason, long eventTime) { + mCurrentWakefulness = wakefulness; + if (mIsInteractive == isInteractive(wakefulness)) { + return; + } + + mIsInteractive = isInteractive(wakefulness); + if (mIsInteractive) { + mInteractiveStateOnStartTimestamp = eventTime; + + // Log the outcome of screen timeout override (USER INITIATED REVERT), + // when user initiates to revert the screen off state in a short period. + if (mTimeoutOffTimestamp != TIMEOUT_OFF_RESET_TIMESTAMP) { + long offToOnDurationMs = eventTime - mTimeoutOffTimestamp; + if (offToOnDurationMs < TIMEOUT_USER_INITIATED_REVERT_THRESHOLD_MILLIS) { + mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent( + mPowerGroupId, + OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT, + mOverrideTimeoutMs, + getScreenOffTimeout()); + mSendOverrideTimeoutLogTimestamp = eventTime; + } + mTimeoutOffTimestamp = TIMEOUT_OFF_RESET_TIMESTAMP; + } + } else { + int lastUserActivity = mCurrentUserActivityEvent; + long lastUserActivityDurationMs = eventTime - mCurrentUserActivityTimestamp; + @OffReason int interactiveStateOffReason = OFF_REASON_UNKNOWN; + int reducedInteractiveStateOnDurationMs = 0; + + if (changeReason == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) { + interactiveStateOffReason = OFF_REASON_POWER_BUTTON; + + // Power Off will be triggered by USER_ACTIVITY_EVENT_BUTTON + // The metric wants to record the previous activity before EVENT_BUTTON + lastUserActivity = mPrevUserActivityEvent; + lastUserActivityDurationMs = eventTime - mPrevUserActivityTimestamp; + + if (isInOverrideTimeout() + || mTimeoutOverrideReleaseReason == RELEASE_REASON_USER_ACTIVITY_BUTTON + ) { + mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent( + mPowerGroupId, + OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON, + mOverrideTimeoutMs, + getScreenOffTimeout()); + mSendOverrideTimeoutLogTimestamp = eventTime; + mTimeoutOverrideReleaseReason = RELEASE_REASON_UNKNOWN; // reset the reason + } + } else if (changeReason == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) { + // Interactive Off reason is timeout + interactiveStateOffReason = OFF_REASON_TIMEOUT; + + lastUserActivity = mCurrentUserActivityEvent; + lastUserActivityDurationMs = eventTime - mCurrentUserActivityTimestamp; + + // Log the outcome of screen timeout override when the early screen + // timeout has been done successfully. + if (isInOverrideTimeout()) { + reducedInteractiveStateOnDurationMs = + getScreenOffTimeout() - mOverrideTimeoutMs; + + mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent( + mPowerGroupId, + OVERRIDE_OUTCOME_TIMEOUT_SUCCESS, + mOverrideTimeoutMs, + getScreenOffTimeout()); + mSendOverrideTimeoutLogTimestamp = eventTime; + + // Record a timestamp to track if the user initiates to revert from off + // state instantly + mTimeoutOffTimestamp = eventTime; + } + } + + long interactiveStateOnDurationMs = + eventTime - mInteractiveStateOnStartTimestamp; + mWakefulnessSessionFrameworkStatsLogger.logSessionEvent( + mPowerGroupId, + interactiveStateOffReason, + interactiveStateOnDurationMs, + lastUserActivity, + lastUserActivityDurationMs, + reducedInteractiveStateOnDurationMs); + } + } + + public void acquireTimeoutOverrideWakeLock() { + synchronized (mLock) { + mTimeoutOverrideWakeLockCounter++; + } + } + + public void releaseTimeoutOverrideWakeLock( + @ScreenTimeoutOverridePolicy.ReleaseReason int releaseReason) { + synchronized (mLock) { + mTimeoutOverrideWakeLockCounter--; + } + + if (!isInOverrideTimeout()) { + mTimeoutOverrideReleaseReason = releaseReason; + long now = mClock.uptimeMillis(); + + // Log the outcome of screen timeout override (USER INTERACTIVE or DISCONNECT), + // when early screen timeout be canceled. + // Note: Set the threshold to avoid sending this log repeatly after other outcomes. + long sendOverrideTimeoutLogDuration = now - mSendOverrideTimeoutLogTimestamp; + boolean sendOverrideTimeoutLogSoon = sendOverrideTimeoutLogDuration + < SEND_OVERRIDE_TIMEOUT_LOG_THRESHOLD_MILLIS; + if (!sendOverrideTimeoutLogSoon) { + @OverrideOutcome int outcome = OVERRIDE_OUTCOME_UNKNOWN; + switch (releaseReason) { + case RELEASE_REASON_USER_ACTIVITY_ATTENTION: + case RELEASE_REASON_USER_ACTIVITY_OTHER: + case RELEASE_REASON_USER_ACTIVITY_BUTTON: + case RELEASE_REASON_USER_ACTIVITY_TOUCH: + case RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY: + outcome = OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION; + break; + case RELEASE_REASON_SCREEN_LOCK: + case RELEASE_REASON_NON_INTERACTIVE: + outcome = OVERRIDE_OUTCOME_CANCEL_CLIENT_DISCONNECT; + break; + default: + outcome = OVERRIDE_OUTCOME_UNKNOWN; + } + mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent( + mPowerGroupId, + outcome, + mOverrideTimeoutMs, + getScreenOffTimeout()); + } + } + } + + @VisibleForTesting + protected boolean isInOverrideTimeout() { + synchronized (mLock) { + return (mTimeoutOverrideWakeLockCounter > 0); + } + } + + void dump(IndentingPrintWriter writer) { + final long now = mClock.uptimeMillis(); + + writer.println("Wakefulness Session Power Group powerGroupId: " + mPowerGroupId); + writer.increaseIndent(); + writer.println("current wakefulness: " + mCurrentWakefulness); + writer.println("current user activity event: " + mCurrentUserActivityEvent); + final long currentUserActivityDurationMs = now - mCurrentUserActivityTimestamp; + writer.println("current user activity duration: " + currentUserActivityDurationMs); + writer.println("previous user activity event: " + mPrevUserActivityEvent); + final long prevUserActivityDurationMs = now - mPrevUserActivityTimestamp; + writer.println("previous user activity duration: " + prevUserActivityDurationMs); + writer.println("is in override timeout: " + isInOverrideTimeout()); + writer.decreaseIndent(); + } + } + + /** Log screen session atoms */ + protected static class WakefulnessSessionFrameworkStatsLogger { + public void logSessionEvent( + int powerGroupId, + @OffReason int interactiveStateOffReason, + long interactiveStateOnDurationMs, + @PowerManager.UserActivityEvent int userActivityEvent, + long lastUserActivityEventDurationMs, + int reducedInteractiveStateOnDurationMs) { + int logUserActivityEvent = convertToLogUserActivityEvent(userActivityEvent); + FrameworkStatsLog.write( + FrameworkStatsLog.SCREEN_INTERACTIVE_SESSION_REPORTED, + powerGroupId, + interactiveStateOffReason, + interactiveStateOnDurationMs, + logUserActivityEvent, + lastUserActivityEventDurationMs, + (long) reducedInteractiveStateOnDurationMs); + } + + public void logTimeoutOverrideEvent( + int powerGroupId, + @OverrideOutcome int overrideOutcome, + int overrideTimeoutMs, + int defaultTimeoutMs) { + FrameworkStatsLog.write( + FrameworkStatsLog.SCREEN_TIMEOUT_OVERRIDE_REPORTED, + powerGroupId, + overrideOutcome, + (long) overrideTimeoutMs, + (long) defaultTimeoutMs); + } + + private static final int USER_ACTIVITY_OTHER = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__OTHER; + + private static final int USER_ACTIVITY_BUTTON = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__BUTTON; + + private static final int USER_ACTIVITY_TOUCH = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__TOUCH; + + private static final int USER_ACTIVITY_ACCESSIBILITY = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__ACCESSIBILITY; + private static final int USER_ACTIVITY_ATTENTION = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__ATTENTION; + private static final int USER_ACTIVITY_FACE_DOWN = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__FACE_DOWN; + + private static final int USER_ACTIVITY_DEVICE_STATE = FrameworkStatsLog + .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__DEVICE_STATE; + + /** + * User Activity Event + * {@link android.os.statsd.power.ScreenInteractiveSessionReported.UserActivityEvent}. + */ + @IntDef(prefix = {"USER_ACTIVITY_"}, value = { + USER_ACTIVITY_OTHER, + USER_ACTIVITY_BUTTON, + USER_ACTIVITY_TOUCH, + USER_ACTIVITY_ACCESSIBILITY, + USER_ACTIVITY_ATTENTION, + USER_ACTIVITY_FACE_DOWN, + USER_ACTIVITY_DEVICE_STATE, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface UserActivityEvent {} + + private @UserActivityEvent int convertToLogUserActivityEvent( + @PowerManager.UserActivityEvent int userActivity) { + switch (userActivity) { + case PowerManager.USER_ACTIVITY_EVENT_OTHER: + return USER_ACTIVITY_OTHER; + case PowerManager.USER_ACTIVITY_EVENT_BUTTON: + return USER_ACTIVITY_BUTTON; + case PowerManager.USER_ACTIVITY_EVENT_TOUCH: + return USER_ACTIVITY_TOUCH; + case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY: + return USER_ACTIVITY_ACCESSIBILITY; + case PowerManager.USER_ACTIVITY_EVENT_ATTENTION: + return USER_ACTIVITY_ATTENTION; + case PowerManager.USER_ACTIVITY_EVENT_FACE_DOWN: + return USER_ACTIVITY_FACE_DOWN; + case PowerManager.USER_ACTIVITY_EVENT_DEVICE_STATE: + return USER_ACTIVITY_DEVICE_STATE; + } + return USER_ACTIVITY_OTHER; + } + } + + /** To observe and do actions if users switch */ + private final class UserSwitchObserver extends SynchronousUserSwitchObserver { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + updateSettingScreenOffTimeout(mContext); + } + } + + @VisibleForTesting + interface Clock { + long uptimeMillis(); + } + + @VisibleForTesting + static class Injector { + WakefulnessSessionFrameworkStatsLogger getWakefulnessSessionFrameworkStatsLogger() { + return new WakefulnessSessionFrameworkStatsLogger(); + } + + Clock getClock() { + return SystemClock::uptimeMillis; + } + } +} diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index 67409a424edb..5c74a800841f 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -1067,7 +1067,7 @@ public class PowerManagerServiceTest { wakelockMap.remove((String) inv.getArguments()[1]); return null; }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(), - anyInt(), any(), any(), any()); + anyInt(), any(), any(), any(), anyInt()); // // TEST STARTS HERE @@ -2777,7 +2777,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().releaseWakeLock(token, 0); verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(), - anyInt(), any(), any(), same(callback)); + anyInt(), any(), any(), same(callback), anyInt()); } /** @@ -2796,8 +2796,8 @@ public class PowerManagerServiceTest { when(callback1.asBinder()).thenReturn(callbackBinder1); mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1); - verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(), - anyInt(), any(), any(), same(callback1)); + verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), + anyInt(), anyInt(), any(), any(), same(callback1)); final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class); final IBinder callbackBinder2 = Mockito.mock(Binder.class); @@ -2810,7 +2810,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().releaseWakeLock(token, 0); verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(), - anyInt(), any(), any(), same(callback2)); + anyInt(), any(), any(), same(callback2), anyInt()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java new file mode 100644 index 000000000000..698f094c8796 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java @@ -0,0 +1,323 @@ +/* + * Copyright 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 com.android.server.power; + +import static android.os.PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON; +import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT; +import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON; + +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN; +import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_TOUCH; +import static com.android.server.power.WakefulnessSessionObserver.OFF_REASON_POWER_BUTTON; +import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON; +import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION; +import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_SUCCESS; +import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.UserHandle; +import android.provider.Settings; +import android.test.mock.MockContentResolver; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.testutils.OffsettableClock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WakefulnessSessionObserverTest { + private static final int DEFAULT_SCREEN_OFF_TIMEOUT_MS = 30000; + private static final int OVERRIDE_SCREEN_OFF_TIMEOUT_MS = 15000; + private WakefulnessSessionObserver mWakefulnessSessionObserver; + private Context mContext; + private OffsettableClock mTestClock; + @Mock + private WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger + mWakefulnessSessionFrameworkStatsLogger; + private WakefulnessSessionObserver.Injector mInjector = + new WakefulnessSessionObserver.Injector() { + @Override + WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger + getWakefulnessSessionFrameworkStatsLogger() { + return mWakefulnessSessionFrameworkStatsLogger; + } + @Override + WakefulnessSessionObserver.Clock getClock() { + return mTestClock::now; + } + }; + + @Before + public void setUp() { + mTestClock = new OffsettableClock.Stopped(); + + MockitoAnnotations.initMocks(this); + mContext = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + doReturn(mContext).when(mContext).getApplicationContext(); + + final Resources res = spy(mContext.getResources()); + doReturn(OVERRIDE_SCREEN_OFF_TIMEOUT_MS).when(res).getInteger( + com.android.internal.R.integer.config_screenTimeoutOverride); + when(mContext.getResources()).thenReturn(res); + FakeSettingsProvider.clearSettingsProvider(); + MockContentResolver mockContentResolver = new MockContentResolver(); + mockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mockContentResolver); + Settings.System.putIntForUser(mockContentResolver, Settings.System.SCREEN_OFF_TIMEOUT, + DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT); + + mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, mInjector); + } + + @After + public void tearDown() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Test + public void testOnUserActivity_updateActivity() { + int firstActivity = PowerManager.USER_ACTIVITY_EVENT_BUTTON; + long firstActivityTimestamp = mTestClock.now(); + int powerGroupId = 1; + mWakefulnessSessionObserver.notifyUserActivity( + firstActivityTimestamp, powerGroupId, firstActivity); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(powerGroupId) + .mCurrentUserActivityEvent).isEqualTo(firstActivity); + + int newActivity = PowerManager.USER_ACTIVITY_EVENT_ATTENTION; + advanceTime(10L); + long newActivityTimestamp = mTestClock.now(); + mWakefulnessSessionObserver.notifyUserActivity( + newActivityTimestamp, powerGroupId, newActivity); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(powerGroupId) + .mCurrentUserActivityEvent).isEqualTo(newActivity); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(powerGroupId) + .mCurrentUserActivityTimestamp).isEqualTo(newActivityTimestamp); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(powerGroupId) + .mPrevUserActivityEvent).isEqualTo(firstActivity); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(powerGroupId) + .mPrevUserActivityTimestamp).isEqualTo(firstActivityTimestamp); + + int otherPowerGroupId = 2; + mWakefulnessSessionObserver.notifyUserActivity( + firstActivityTimestamp, otherPowerGroupId, firstActivity); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(otherPowerGroupId) + .mCurrentUserActivityEvent).isEqualTo(firstActivity); + assertThat(mWakefulnessSessionObserver.mPowerGroups.get(otherPowerGroupId) + .mCurrentUserActivityTimestamp).isEqualTo(firstActivityTimestamp); + } + + @Test + public void testOnWakeLockAcquired() { + mWakefulnessSessionObserver.onWakeLockAcquired(PowerManager.DOZE_WAKE_LOCK); + for (int idx = 0; idx < mWakefulnessSessionObserver.mPowerGroups.size(); idx++) { + assertThat(mWakefulnessSessionObserver.mPowerGroups.valueAt(idx).isInOverrideTimeout()) + .isFalse(); + } + + mWakefulnessSessionObserver.onWakeLockAcquired( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK); + for (int idx = 0; idx < mWakefulnessSessionObserver.mPowerGroups.size(); idx++) { + assertThat(mWakefulnessSessionObserver.mPowerGroups.valueAt(idx).isInOverrideTimeout()) + .isTrue(); + } + } + + @Test + public void testOnWakeLockReleased() { + mWakefulnessSessionObserver.onWakeLockReleased( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK, RELEASE_REASON_UNKNOWN); + for (int idx = 0; idx < mWakefulnessSessionObserver.mPowerGroups.size(); idx++) { + assertThat(mWakefulnessSessionObserver.mPowerGroups.valueAt(idx).isInOverrideTimeout()) + .isFalse(); + } + } + + @Test + public void testOnWakefulnessChangeStarted_onDozing_UserActivityAttention_OverrideTimeout() { + int powerGroupId = 1; + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, + PowerManagerInternal.WAKEFULNESS_AWAKE, + WAKE_REASON_POWER_BUTTON, + mTestClock.now()); + mWakefulnessSessionObserver.notifyUserActivity( + mTestClock.now(), powerGroupId, PowerManager.USER_ACTIVITY_EVENT_ATTENTION); + mWakefulnessSessionObserver.onWakeLockAcquired( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK); + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, + PowerManagerInternal.WAKEFULNESS_DOZING, + GO_TO_SLEEP_REASON_TIMEOUT, + mTestClock.now()); + + verify(mWakefulnessSessionFrameworkStatsLogger) + .logTimeoutOverrideEvent( + powerGroupId, // powerGroupId + OVERRIDE_OUTCOME_TIMEOUT_SUCCESS, // overrideOutcome + OVERRIDE_SCREEN_OFF_TIMEOUT_MS, // override timeout ms + DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default timeout ms + } + + @Test + public void testOnWakefulnessChangeStarted_onDozing_UserActivityButton() { + advanceTime(5000L); // reset current timestamp for new test case + int powerGroupId = 2; + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, + PowerManagerInternal.WAKEFULNESS_AWAKE, + WAKE_REASON_POWER_BUTTON, + mTestClock.now()); + + int userActivity = PowerManager.USER_ACTIVITY_EVENT_DEVICE_STATE; + long userActivityTime = mTestClock.now(); + mWakefulnessSessionObserver.notifyUserActivity( + userActivityTime, powerGroupId, userActivity); + long advancedTime = 10L; + advanceTime(advancedTime); + mWakefulnessSessionObserver.notifyUserActivity( + userActivityTime, powerGroupId, PowerManager.USER_ACTIVITY_EVENT_BUTTON); + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, + PowerManagerInternal.WAKEFULNESS_DOZING, + GO_TO_SLEEP_REASON_POWER_BUTTON, + mTestClock.now()); + + verify(mWakefulnessSessionFrameworkStatsLogger) + .logSessionEvent( + powerGroupId, // powerGroupId + OFF_REASON_POWER_BUTTON, // interactiveStateOffReason + advancedTime, // interactiveStateOnDurationMs + userActivity, // userActivity + advancedTime, // lastUserActivityEventDurationMs + 0); // reducedInteractiveStateOnDurationMs; + + verify(mWakefulnessSessionFrameworkStatsLogger, never()) + .logTimeoutOverrideEvent(anyInt(), anyInt(), anyInt(), anyInt()); + } + + @Test + public void testOnWakefulnessChangeStarted_onDozing_UserActivityButton_OverrideTimeout() { + int powerGroupId = 1; + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, + PowerManagerInternal.WAKEFULNESS_AWAKE, + WAKE_REASON_POWER_BUTTON, + mTestClock.now()); + mWakefulnessSessionObserver.onWakeLockAcquired( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK); + + int userActivity = PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY; + long userActivityTime = mTestClock.now(); + mWakefulnessSessionObserver.notifyUserActivity( + userActivityTime, powerGroupId, userActivity); + long advancedTime = 10L; + advanceTime(advancedTime); + mWakefulnessSessionObserver.notifyUserActivity( + userActivityTime, powerGroupId, PowerManager.USER_ACTIVITY_EVENT_BUTTON); + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, + PowerManagerInternal.WAKEFULNESS_DOZING, + GO_TO_SLEEP_REASON_POWER_BUTTON, + mTestClock.now()); + + verify(mWakefulnessSessionFrameworkStatsLogger) + .logTimeoutOverrideEvent( + powerGroupId, // powerGroupId + OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON, // overrideOutcome + OVERRIDE_SCREEN_OFF_TIMEOUT_MS, // override timeout ms + DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default timeout ms + + verify(mWakefulnessSessionFrameworkStatsLogger) + .logSessionEvent( + powerGroupId, // powerGroupId + OFF_REASON_POWER_BUTTON, // interactiveStateOffReason + advancedTime, // interactiveStateOnDurationMs + userActivity, // userActivity + advancedTime, // lastUserActivityEventDurationMs + 0); // reducedInteractiveStateOnDurationMs; + } + + @Test + public void testOnWakefulnessChangeStarted_inTimeoutOverride_onAwake_After_onDozing() { + int powerGroupId = 1; + mWakefulnessSessionObserver.onWakeLockAcquired( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK); + + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, PowerManagerInternal.WAKEFULNESS_DOZING, + GO_TO_SLEEP_REASON_TIMEOUT, mTestClock.now()); + // awake after dozing + advanceTime(10L); + mWakefulnessSessionObserver.onWakefulnessChangeStarted( + powerGroupId, PowerManagerInternal.WAKEFULNESS_AWAKE, + WAKE_REASON_POWER_BUTTON, mTestClock.now()); + + verify(mWakefulnessSessionFrameworkStatsLogger) + .logTimeoutOverrideEvent( + powerGroupId, // powerGroupId + OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT, // overrideOutcome + OVERRIDE_SCREEN_OFF_TIMEOUT_MS, // override timeout ms + DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default timeout ms + } + + @Test + public void testOnWakeLockReleased_UserActivityTouch() { + int powerGroupId = 0; + mWakefulnessSessionObserver.onWakeLockAcquired( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK); + advanceTime(5000L); + mWakefulnessSessionObserver.onWakeLockReleased( + PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK, + RELEASE_REASON_USER_ACTIVITY_TOUCH); + + verify(mWakefulnessSessionFrameworkStatsLogger) + .logTimeoutOverrideEvent( + powerGroupId, // powerGroupId + OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION, // overrideOutcome + OVERRIDE_SCREEN_OFF_TIMEOUT_MS, // override timeout ms + DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default timeout ms + } + + private void advanceTime(long timeMs) { + mTestClock.fastForward(timeMs); + } +} |