summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/power/Notifier.java23
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java26
-rw-r--r--services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java67
-rw-r--r--services/core/java/com/android/server/power/WakefulnessSessionObserver.java595
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java323
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);
+ }
+}