Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f0e35fc3 authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Limit user activity notifications to PowerManager from Hub." into main

parents dd2e6151 e843d1c0
Loading
Loading
Loading
Loading
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.systemui.communal.util

import android.testing.AndroidTestingRunner
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.runner.RunWith

@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserTouchActivityNotifierTest : SysuiTestCase() {
    private val kosmos: Kosmos = testKosmos().useUnconfinedTestDispatcher()

    @Test
    fun firstEventTriggersNotify() =
        kosmos.runTest { sendEventAndVerify(0, MotionEvent.ACTION_MOVE, true) }

    @Test
    fun secondEventTriggersRateLimited() =
        kosmos.runTest {
            var eventTime = 0L

            sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, true)
            eventTime += 50
            sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, false)
            eventTime += USER_TOUCH_ACTIVITY_RATE_LIMIT
            sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, true)
        }

    @Test
    fun overridingActionNotifies() =
        kosmos.runTest {
            var eventTime = 0L
            sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, true)
            sendEventAndVerify(eventTime, MotionEvent.ACTION_DOWN, true)
            sendEventAndVerify(eventTime, MotionEvent.ACTION_UP, true)
            sendEventAndVerify(eventTime, MotionEvent.ACTION_CANCEL, true)
        }

    private fun sendEventAndVerify(eventTime: Long, action: Int, shouldBeHandled: Boolean) {
        kosmos.fakePowerRepository.userTouchRegistered = false
        val motionEvent = MotionEvent.obtain(0, eventTime, action, 0f, 0f, 0)
        kosmos.userTouchActivityNotifier.notifyActivity(motionEvent)

        if (shouldBeHandled) {
            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
        } else {
            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -108,6 +108,9 @@ interface CommunalModule {
        const val LAUNCHER_PACKAGE = "launcher_package"
        const val SWIPE_TO_HUB = "swipe_to_hub"
        const val SHOW_UMO = "show_umo"
        const val TOUCH_NOTIFICATION_RATE_LIMIT = "TOUCH_NOTIFICATION_RATE_LIMIT"

        const val TOUCH_NOTIFIFCATION_RATE_LIMIT_MS = 100

        @Provides
        @Communal
@@ -159,5 +162,11 @@ interface CommunalModule {
        fun provideShowUmo(@Main resources: Resources): Boolean {
            return resources.getBoolean(R.bool.config_showUmoOnHub)
        }

        @Provides
        @Named(TOUCH_NOTIFICATION_RATE_LIMIT)
        fun providesRateLimit(): Int {
            return TOUCH_NOTIFIFCATION_RATE_LIMIT_MS
        }
    }
}
+58 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.systemui.communal.util

import android.view.MotionEvent
import com.android.systemui.communal.dagger.CommunalModule.Companion.TOUCH_NOTIFICATION_RATE_LIMIT
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.power.domain.interactor.PowerInteractor
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * {@link UserTouchActivityNotifier} helps rate limit the user activity notifications sent to {@link
 * PowerManager} from a single touch source.
 */
class UserTouchActivityNotifier
@Inject
constructor(
    @Background private val scope: CoroutineScope,
    private val powerInteractor: PowerInteractor,
    @Named(TOUCH_NOTIFICATION_RATE_LIMIT) private val rateLimitMs: Int,
) {
    private var lastNotification: Long? = null

    fun notifyActivity(event: MotionEvent) {
        val metered =
            when (event.action) {
                MotionEvent.ACTION_CANCEL -> false
                MotionEvent.ACTION_UP -> false
                MotionEvent.ACTION_DOWN -> false
                else -> true
            }

        if (metered && lastNotification?.let { event.eventTime - it < rateLimitMs } == true) {
            return
        }

        lastNotification = event.eventTime

        scope.launch { powerInteractor.onUserTouch() }
    }
}
+6 −13
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
import android.os.PowerManager
import android.os.SystemClock
import android.util.ArraySet
import android.view.GestureDetector
import android.view.MotionEvent
@@ -54,6 +53,7 @@ import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.communal.util.UserTouchActivityNotifier
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -101,6 +101,7 @@ constructor(
    private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
    private val keyguardMediaController: KeyguardMediaController,
    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
    private val userTouchActivityNotifier: UserTouchActivityNotifier,
    @CommunalTouchLog logBuffer: LogBuffer,
    private val userActivityNotifier: UserActivityNotifier,
) : LifecycleOwner {
@@ -646,8 +647,8 @@ constructor(
            // result in broken states.
            return true
        }
        var handled = hubShowing
        try {
            var handled = false
            if (!touchTakenByKeyguardGesture) {
                communalContainerWrapper?.dispatchTouchEvent(ev) {
                    if (it) {
@@ -655,18 +656,10 @@ constructor(
                    }
                }
            }
            return handled || hubShowing
            return handled
        } finally {
            if (Flags.bouncerUiRevamp()) {
                userActivityNotifier.notifyUserActivity(
                    event = PowerManager.USER_ACTIVITY_EVENT_TOUCH
                )
            } else {
                powerManager.userActivity(
                    SystemClock.uptimeMillis(),
                    PowerManager.USER_ACTIVITY_EVENT_TOUCH,
                    0,
                )
            if (handled) {
                userTouchActivityNotifier.notifyActivity(ev)
            }
        }
    }
+18 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.communal.util.userTouchActivityNotifier
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -64,6 +65,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.controller.keyguardMediaController
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -137,6 +139,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
                notificationStackScrollLayoutController,
                keyguardMediaController,
                lockscreenSmartspaceController,
                userTouchActivityNotifier,
                logcatLogBuffer("GlanceableHubContainerControllerTest"),
                kosmos.userActivityNotifier,
            )
@@ -178,6 +181,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
                    notificationStackScrollLayoutController,
                    keyguardMediaController,
                    lockscreenSmartspaceController,
                    userTouchActivityNotifier,
                    logcatLogBuffer("GlanceableHubContainerControllerTest"),
                    kosmos.userActivityNotifier,
                )
@@ -208,6 +212,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
                    notificationStackScrollLayoutController,
                    keyguardMediaController,
                    lockscreenSmartspaceController,
                    userTouchActivityNotifier,
                    logcatLogBuffer("GlanceableHubContainerControllerTest"),
                    kosmos.userActivityNotifier,
                )
@@ -234,6 +239,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
                    notificationStackScrollLayoutController,
                    keyguardMediaController,
                    lockscreenSmartspaceController,
                    userTouchActivityNotifier,
                    logcatLogBuffer("GlanceableHubContainerControllerTest"),
                    kosmos.userActivityNotifier,
                )
@@ -538,6 +544,18 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
            verify(containerView).onTouchEvent(UP_EVENT)
        }

    @Test
    fun onTouchEvent_touchHandled_notifyUserActivity() =
        kosmos.runTest {
            // Communal is open.
            goToScene(CommunalScenes.Communal)

            // Touch event is sent to the container view.
            assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
            verify(containerView).onTouchEvent(DOWN_EVENT)
            assertThat(fakePowerRepository.userTouchRegistered).isTrue()
        }

    @Test
    fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() =
        kosmos.runTest {
Loading