diff options
| author | 2019-01-25 21:55:51 +0000 | |
|---|---|---|
| committer | 2019-01-25 21:55:51 +0000 | |
| commit | cea31af085a8ad79c71fe52fbce3ad358c6e6bc2 (patch) | |
| tree | eea81402ea97a6e18198e62223cb7924bb1169a6 | |
| parent | 9669b21417ea72bdc8e42be18f27bbdb10f88956 (diff) | |
| parent | 0a5d79707d95afc4a7c18a57cc2b48d098e4ac2d (diff) | |
Merge "Do not sleep if someone is paying attention"
8 files changed, 458 insertions, 6 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index eae1aa5eb750..1919fcc00a33 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -834,10 +834,10 @@ public abstract class BatteryStats implements Parcelable { * also be bumped. */ static final String[] USER_ACTIVITY_TYPES = { - "other", "button", "touch", "accessibility" + "other", "button", "touch", "accessibility", "attention" }; - public static final int NUM_USER_ACTIVITY_TYPES = 4; + public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length; public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 4ce760f2c4a6..7f4254e29aaa 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -336,6 +336,13 @@ public final class PowerManager { public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; /** + * User activity event type: {@link android.service.attention.AttentionService} taking action + * on behalf of user. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_ATTENTION = 4; + + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on * a little longer without causing a visible change such as when diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 49f2c84335c5..c05795de4751 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2211,6 +2211,10 @@ has expired, then assume the device is receiving insufficient current to charge effectively and terminate the dream. Use -1 to disable this safety feature. --> <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer> + <!-- Limit of how long the device can remain unlocked due to attention checking. --> + <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes --> + <!-- How long we should wait until we give up on receiving an attention API callback. --> + <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds --> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d5561302fdc6..f79e22d1f94e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3554,4 +3554,8 @@ <java-symbol type="bool" name="config_cbrs_supported" /> <java-symbol type="bool" name="config_awareSettingAvailable" /> + + <!-- For Attention Service --> + <java-symbol type="integer" name="config_attentionMaximumExtension" /> + <java-symbol type="integer" name="config_attentionApiTimeout" /> </resources> diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index 27edbbf4f2d5..b71a7517ab12 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -174,10 +174,11 @@ public class AttentionManagerService extends SystemService { @Override public void onSuccess(int requestCode, int result, long timestamp) { callback.onSuccess(requestCode, result, timestamp); - userState.mAttentionCheckCache = new AttentionCheckCache( - SystemClock.uptimeMillis(), result, - timestamp); - + synchronized (mLock) { + userState.mAttentionCheckCache = new AttentionCheckCache( + SystemClock.uptimeMillis(), result, + timestamp); + } StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED, result); } diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java new file mode 100644 index 000000000000..a2c8dace9510 --- /dev/null +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 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 android.attention.AttentionManagerInternal; +import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.io.PrintWriter; + +/** + * Class responsible for checking if the user is currently paying attention to the phone and + * notifying {@link PowerManagerService} that user activity should be renewed. + * + * This class also implements a limit of how long the extension should be, to avoid security + * issues where the device would never be locked. + */ +public class AttentionDetector { + + private static final String TAG = "AttentionDetector"; + private static final boolean DEBUG = false; + + /** + * Invoked whenever user attention is detected. + */ + private final Runnable mOnUserAttention; + + /** + * The maximum time, in millis, that the phone can stay unlocked because of attention events, + * triggered by any user. + */ + @VisibleForTesting + protected long mMaximumExtensionMillis; + + private final Object mLock; + + /** + * {@link android.service.attention.AttentionService} API timeout. + */ + private long mMaxAttentionApiTimeoutMillis; + + /** + * Last known user activity. + */ + private long mLastUserActivityTime; + + @VisibleForTesting + protected AttentionManagerInternal mAttentionManager; + + /** + * If we're currently waiting for an attention callback + */ + private boolean mRequested; + + /** + * Current wakefulness of the device. {@see PowerManagerInternal} + */ + private int mWakefulness; + + @VisibleForTesting + final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() { + + @Override + public void onSuccess(int requestCode, int result, long timestamp) { + Slog.v(TAG, "onSuccess: " + requestCode + ", " + result + + " - current requestCode: " + getRequestCode()); + synchronized (mLock) { + if (requestCode == getRequestCode() && mRequested) { + mRequested = false; + if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + if (DEBUG) Slog.d(TAG, "Device slept before receiving callback."); + return; + } + if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) { + mOnUserAttention.run(); + } + } + } + } + + @Override + public void onFailure(int requestCode, int error) { + Slog.i(TAG, "Failed to check attention: " + error); + synchronized (mLock) { + if (requestCode == getRequestCode()) { + mRequested = false; + } + } + } + }; + + public AttentionDetector(Runnable onUserAttention, Object lock) { + mOnUserAttention = onUserAttention; + mLock = lock; + } + + public void systemReady(Context context) { + mAttentionManager = LocalServices.getService(AttentionManagerInternal.class); + mMaximumExtensionMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionMaximumExtension); + mMaxAttentionApiTimeoutMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionApiTimeout); + } + + public long updateUserActivity(long nextScreenDimming) { + if (!isAttentionServiceSupported()) { + return nextScreenDimming; + } + + final long now = SystemClock.uptimeMillis(); + final long whenToCheck = nextScreenDimming - getAttentionTimeout(); + final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis; + if (now < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now)); + } + return nextScreenDimming; + } else if (whenToStopExtending < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Let device sleep to avoid false results and improve security " + + (whenToCheck - whenToStopExtending)); + } + return nextScreenDimming; + } else if (mRequested) { + if (DEBUG) { + Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode()); + } + return whenToCheck; + } + + // Ideally we should attribute mRequested to the result of #checkAttention, but the + // callback might arrive before #checkAttention returns (if there are cached results.) + // This means that we must assume that the request was successful, and then cancel it + // afterwards if AttentionManager couldn't deliver it. + mRequested = true; + final boolean sent = mAttentionManager.checkAttention(getRequestCode(), + getAttentionTimeout(), mCallback); + if (!sent) { + mRequested = false; + } + + Slog.v(TAG, "Checking user attention with request code: " + getRequestCode()); + return whenToCheck; + } + + /** + * Handles user activity by cancelling any pending attention requests and keeping track of when + * the activity happened. + * + * @param eventTime Activity time, in uptime millis. + * @param event Activity type as defined in {@link PowerManager}. + * @return 0 when activity was ignored, 1 when handled, -1 when invalid. + */ + public int onUserActivity(long eventTime, int event) { + switch (event) { + case PowerManager.USER_ACTIVITY_EVENT_ATTENTION: + return 0; + case PowerManager.USER_ACTIVITY_EVENT_OTHER: + case PowerManager.USER_ACTIVITY_EVENT_BUTTON: + case PowerManager.USER_ACTIVITY_EVENT_TOUCH: + case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY: + cancelCurrentRequestIfAny(); + mLastUserActivityTime = eventTime; + return 1; + default: + if (DEBUG) { + Slog.d(TAG, "Attention not reset. Unknown activity event: " + event); + } + return -1; + } + } + + public void onWakefulnessChangeStarted(int wakefulness) { + mWakefulness = wakefulness; + if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + cancelCurrentRequestIfAny(); + } + } + + private void cancelCurrentRequestIfAny() { + if (mRequested) { + mAttentionManager.cancelAttentionCheck(getRequestCode()); + mRequested = false; + } + } + + @VisibleForTesting + int getRequestCode() { + return (int) (mLastUserActivityTime % Integer.MAX_VALUE); + } + + @VisibleForTesting + long getAttentionTimeout() { + return mMaxAttentionApiTimeoutMillis; + } + + /** + * {@see AttentionManagerInternal#isAttentionServiceSupported} + */ + @VisibleForTesting + boolean isAttentionServiceSupported() { + return mAttentionManager.isAttentionServiceSupported(); + } + + public void dump(PrintWriter pw) { + pw.print("AttentionDetector:"); + pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis); + pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis); + pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime); + pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported()); + pw.print(" mRequested=" + mRequested); + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index a02787308246..3be64802c9fc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -229,6 +229,7 @@ public final class PowerManagerService extends SystemService private final BatterySaverController mBatterySaverController; private final BatterySaverStateMachine mBatterySaverStateMachine; private final BatterySavingStats mBatterySavingStats; + private final AttentionDetector mAttentionDetector; private final BinderService mBinderService; private final LocalService mLocalService; private final NativeWrapper mNativeWrapper; @@ -736,6 +737,7 @@ public final class PowerManagerService extends SystemService mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); mConstants = new Constants(mHandler); mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock); mBatterySavingStats = new BatterySavingStats(mLock); mBatterySaverPolicy = @@ -804,6 +806,7 @@ public final class PowerManagerService extends SystemService mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); + mAttentionDetector.systemReady(mContext); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); @@ -1326,6 +1329,16 @@ public final class PowerManagerService extends SystemService } } + private void onUserAttention() { + synchronized (mLock) { + if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */, + Process.SYSTEM_UID)) { + updatePowerStateLocked(); + } + } + } + private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime @@ -1346,6 +1359,7 @@ public final class PowerManagerService extends SystemService } mNotifier.onUserActivity(event, uid); + mAttentionDetector.onUserActivity(eventTime, event); if (mUserInactiveOverrideFromWindowManager) { mUserInactiveOverrideFromWindowManager = false; @@ -1593,6 +1607,7 @@ public final class PowerManagerService extends SystemService if (mNotifier != null) { mNotifier.onWakefulnessChangeStarted(wakefulness, reason); } + mAttentionDetector.onWakefulnessChangeStarted(wakefulness); } } @@ -2085,6 +2100,10 @@ public final class PowerManagerService extends SystemService nextTimeout = -1; } + if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) { + nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout); + } + if (nextProfileTimeout > 0) { nextTimeout = Math.min(nextTimeout, nextProfileTimeout); } @@ -3477,6 +3496,7 @@ public final class PowerManagerService extends SystemService mBatterySaverPolicy.dump(pw); mBatterySaverStateMachine.dump(pw); + mAttentionDetector.dump(pw); pw.println(); final int numProfiles = mProfilePowerState.size(); diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java new file mode 100644 index 000000000000..9f1cbcd7ec27 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 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.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.attention.AttentionManagerInternal; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +public class AttentionDetectorTest extends AndroidTestCase { + + private @Mock AttentionManagerInternal mAttentionManagerInternal; + private @Mock Runnable mOnUserAttention; + private TestableAttentionDetector mAttentionDetector; + private long mAttentionTimeout; + private long mNextDimming; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any())) + .thenReturn(true); + mAttentionDetector = new TestableAttentionDetector(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE); + mAttentionDetector.setAttentionServiceSupported(true); + mNextDimming = SystemClock.uptimeMillis() + 3000L; + } + + @Test + public void testOnUserActivity_checksAttention() { + long when = registerAttention(); + verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnUserActivity_doesntCheckIfNotSupported() { + mAttentionDetector.setAttentionServiceSupported(false); + long when = registerAttention(); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(mNextDimming).isEqualTo(when); + } + + @Test + public void onUserActivity_ignoresWhiteListedActivityTypes() { + for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) { + int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i); + if (result == -1) { + throw new AssertionError("User activity " + i + " isn't listed in" + + " AttentionDetector#onUserActivity. Please consider how this new activity" + + " type affects the attention service."); + } + } + } + + @Test + public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() { + long now = SystemClock.uptimeMillis(); + mNextDimming = now; + mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(mNextDimming + 5000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_ignoresAfterMaximumExtension() { + long now = SystemClock.uptimeMillis(); + mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(now + 2000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_skipsIfAlreadyScheduled() { + registerAttention(); + reset(mAttentionManagerInternal); + long when = mAttentionDetector.updateUserActivity(mNextDimming); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() { + registerAttention(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP); + verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt()); + } + + @Test + public void testCallbackOnSuccess_ignoresIfNoAttention() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + @Test + public void testCallbackOnSuccess_callsCallback() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention).run(); + } + + @Test + public void testCallbackOnFailure_unregistersCurrentRequestCode() { + registerAttention(); + mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_FAILURE_UNKNOWN); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + private long registerAttention() { + mAttentionTimeout = 4000L; + mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH); + return mAttentionDetector.updateUserActivity(mNextDimming); + } + + private class TestableAttentionDetector extends AttentionDetector { + + private boolean mAttentionServiceSupported; + + TestableAttentionDetector() { + super(AttentionDetectorTest.this.mOnUserAttention, new Object()); + mAttentionManager = mAttentionManagerInternal; + mMaximumExtensionMillis = 10000L; + } + + void setAttentionServiceSupported(boolean supported) { + mAttentionServiceSupported = supported; + } + + @Override + public boolean isAttentionServiceSupported() { + return mAttentionServiceSupported; + } + + @Override + public long getAttentionTimeout() { + return mAttentionTimeout; + } + } +} |