diff options
| author | 2024-03-01 13:03:20 +0000 | |
|---|---|---|
| committer | 2024-05-03 18:43:34 +0000 | |
| commit | ae0c57f43cd80aa33418480bc7839b2e0da103af (patch) | |
| tree | 07721f2a4113ce20589844a27d8c8dad797380ff | |
| parent | 56fd37ce621b599b43dd6f09941b3f97116082ac (diff) | |
Pause ViewFlippers while lock screen is showing
Fixes: 309146176
Flag: ACONFIG com.android.systemui.notification_view_flipper_pausing DEVELOPMENT
Test: atest SystemUITests
Merged-In: If6a35e062246becdcdb1430ccce6bb79bbe96d02
Change-Id: If6a35e062246becdcdb1430ccce6bb79bbe96d02
8 files changed, 339 insertions, 1 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index ae10bef45586..9e0f78ce0900 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -25,6 +25,16 @@ flag { } flag { + name: "notification_view_flipper_pausing" + namespace: "systemui" + description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed." + bug: "309146176" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "notification_async_group_header_inflation" namespace: "systemui" description: "Inflates the notification group summary header views from the background thread." diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt index dab89c5235b0..b90aa107d617 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.notification.row import android.widget.flags.Flags.notifLinearlayoutOptimized import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing import javax.inject.Inject +import javax.inject.Provider interface NotifRemoteViewsFactoryContainer { val factories: Set<NotifRemoteViewsFactory> @@ -31,7 +33,8 @@ constructor( featureFlags: FeatureFlags, precomputedTextViewFactory: PrecomputedTextViewFactory, bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory, - optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory + optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory, + notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>, ) : NotifRemoteViewsFactoryContainer { override val factories: Set<NotifRemoteViewsFactory> = buildSet { add(precomputedTextViewFactory) @@ -41,5 +44,8 @@ constructor( if (notifLinearlayoutOptimized()) { add(optimizedLinearLayoutFactory) } + if (NotificationViewFlipperPausing.isEnabled) { + add(notificationViewFlipperFactory.get()) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt new file mode 100644 index 000000000000..0594c1227a44 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 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.systemui.statusbar.notification.row + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.ViewFlipper +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder +import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel +import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing +import javax.inject.Inject + +/** + * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it + * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing. + */ +class NotificationViewFlipperFactory +@Inject +constructor( + private val viewModel: NotificationViewFlipperViewModel, +) : NotifRemoteViewsFactory { + init { + /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode() + } + + override fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? { + return when (name) { + ViewFlipper::class.java.name, + ViewFlipper::class.java.simpleName -> + ViewFlipper(context, attrs).also { viewFlipper -> + NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel) + } + else -> null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt new file mode 100644 index 000000000000..133d3e730eff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 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.systemui.statusbar.notification.row.ui.viewbinder + +import android.widget.ViewFlipper +import androidx.lifecycle.lifecycleScope +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch + +/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */ +object NotificationViewFlipperBinder { + fun bindWhileAttached( + viewFlipper: ViewFlipper, + viewModel: NotificationViewFlipperViewModel, + ): DisposableHandle { + if (viewFlipper.isAutoStart) { + // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless + return DisposableHandle {} + } + return viewFlipper.repeatWhenAttached { + lifecycleScope.launch { bind(viewFlipper, viewModel) } + } + } + + suspend fun bind( + viewFlipper: ViewFlipper, + viewModel: NotificationViewFlipperViewModel, + ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } } + + private fun ViewFlipper.setPaused(paused: Boolean) { + if (paused) { + stopFlipping() + } else if (isAutoStart) { + startFlipping() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt new file mode 100644 index 000000000000..7694e58296ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 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.systemui.statusbar.notification.row.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl +import javax.inject.Inject + +/** A model which represents whether ViewFlippers inside notifications should be paused. */ +@SysUISingleton +class NotificationViewFlipperViewModel +@Inject +constructor( + dumpManager: DumpManager, + stackInteractor: NotificationStackInteractor, +) : FlowDumperImpl(dumpManager) { + init { + /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode() + } + + val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused") +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt new file mode 100644 index 000000000000..cea6a2b197be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 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.systemui.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the notification view flipper pausing flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationViewFlipperPausing { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationViewFlipperPausing() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt new file mode 100644 index 000000000000..f88bd7ec60bb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.row.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME) +class NotificationViewFlipperViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + val underTest + get() = kosmos.notificationViewFlipperViewModel + + @Test + fun testIsPaused_falseWhenViewingShade() = + kosmos.testScope.runTest { + val isPaused by collectLastValue(underTest.isPaused) + + // WHEN shade is open + kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + runCurrent() + + // THEN view flippers should NOT be paused + assertThat(isPaused).isFalse() + } + + @Test + fun testIsPaused_trueWhenViewingKeyguard() = + kosmos.testScope.runTest { + val isPaused by collectLastValue(underTest.isPaused) + + // WHEN on keyguard + kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + runCurrent() + + // THEN view flippers should be paused + assertThat(isPaused).isTrue() + } + + @Test + fun testIsPaused_trueWhenStartingToSleep() = + kosmos.testScope.runTest { + val isPaused by collectLastValue(underTest.isPaused) + + // WHEN shade is open + kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + // AND device is starting to go to sleep + kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP) + runCurrent() + + // THEN view flippers should be paused + assertThat(isPaused).isTrue() + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt new file mode 100644 index 000000000000..7ffa262d55a4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 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.systemui.statusbar.notification.row.ui.viewmodel + +import com.android.systemui.dump.dumpManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor + +val Kosmos.notificationViewFlipperViewModel by Fixture { + NotificationViewFlipperViewModel( + dumpManager = dumpManager, + stackInteractor = notificationStackInteractor, + ) +} |