diff options
Diffstat (limited to 'packages')
57 files changed, 1130 insertions, 781 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt index cca43b92ef19..84d61fc86073 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt @@ -501,6 +501,7 @@ open class WifiUtils { dialogWindowType: Int, onStartActivity: (intent: Intent) -> Unit, onAllowed: () -> Unit, + onStartAapmActivity: (intent: Intent) -> Unit = onStartActivity, ): Job = coroutineScope.launch { val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch @@ -510,7 +511,7 @@ open class WifiUtils { AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP, AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION) intent.putExtra(DIALOG_WINDOW_TYPE, dialogWindowType) - withContext(Dispatchers.Main) { onStartActivity(intent) } + withContext(Dispatchers.Main) { onStartAapmActivity(intent) } } else if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) { withContext(Dispatchers.Main) { onAllowed() } } else { diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 91492b2959d8..f65ca3b60818 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -600,6 +600,16 @@ flag { } flag { + name: "avalanche_replace_hun_when_critical" + namespace: "systemui" + description: "Fix for replacing a sticky HUN when a critical HUN posted" + bug: "403301297" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "indication_text_a11y_fix" namespace: "systemui" description: "add double shadow to the indication text at the bottom of the lock screen" diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt index cb713ece12a5..5ed72c7d94a2 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt @@ -55,7 +55,12 @@ open class BaseContentOverscrollEffect( get() = animatable.value override val isInProgress: Boolean - get() = overscrollDistance != 0f + /** + * We need both checks, because [overscrollDistance] can be + * - zero while it is already being animated, if the animation starts from 0 + * - greater than zero without an animation, if the content is still being dragged + */ + get() = overscrollDistance != 0f || animatable.isRunning override fun applyToScroll( delta: Offset, diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt index e7c47fb56130..8a1fa3724d15 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt @@ -16,12 +16,17 @@ package com.android.compose.gesture.effect +import androidx.compose.foundation.OverscrollEffect import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.overscroll +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalDensity @@ -32,11 +37,14 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeWithVelocity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import kotlin.properties.Delegates +import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -47,7 +55,13 @@ class OffsetOverscrollEffectTest { private val BOX_TAG = "box" - private data class LayoutInfo(val layoutSize: Dp, val touchSlop: Float, val density: Density) { + private data class LayoutInfo( + val layoutSize: Dp, + val touchSlop: Float, + val density: Density, + val scrollableState: ScrollableState, + val overscrollEffect: OverscrollEffect, + ) { fun expectedOffset(currentOffset: Dp): Dp { return with(density) { OffsetOverscrollEffect.computeOffset(this, currentOffset.toPx()).toDp() @@ -55,22 +69,29 @@ class OffsetOverscrollEffectTest { } } - private fun setupOverscrollableBox(scrollableOrientation: Orientation): LayoutInfo { + private fun setupOverscrollableBox( + scrollableOrientation: Orientation, + canScroll: () -> Boolean, + ): LayoutInfo { val layoutSize: Dp = 200.dp var touchSlop: Float by Delegates.notNull() // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. lateinit var density: Density + lateinit var scrollableState: ScrollableState + lateinit var overscrollEffect: OverscrollEffect + rule.setContent { density = LocalDensity.current touchSlop = LocalViewConfiguration.current.touchSlop - val overscrollEffect = rememberOffsetOverscrollEffect() + scrollableState = rememberScrollableState { if (canScroll()) it else 0f } + overscrollEffect = rememberOffsetOverscrollEffect() Box( Modifier.overscroll(overscrollEffect) // A scrollable that does not consume the scroll gesture. .scrollable( - state = rememberScrollableState { 0f }, + state = scrollableState, orientation = scrollableOrientation, overscrollEffect = overscrollEffect, ) @@ -78,12 +99,16 @@ class OffsetOverscrollEffectTest { .testTag(BOX_TAG) ) } - return LayoutInfo(layoutSize, touchSlop, density) + return LayoutInfo(layoutSize, touchSlop, density, scrollableState, overscrollEffect) } @Test fun applyVerticalOffset_duringVerticalOverscroll() { - val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { false }, + ) rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) @@ -99,7 +124,11 @@ class OffsetOverscrollEffectTest { @Test fun applyNoOffset_duringHorizontalOverscroll() { - val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { false }, + ) rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) @@ -113,7 +142,11 @@ class OffsetOverscrollEffectTest { @Test fun backToZero_afterOverscroll() { - val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { false }, + ) rule.onRoot().performTouchInput { down(center) @@ -131,7 +164,11 @@ class OffsetOverscrollEffectTest { @Test fun offsetOverscroll_followTheTouchPointer() { - val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { false }, + ) // First gesture, drag down. rule.onRoot().performTouchInput { @@ -165,4 +202,130 @@ class OffsetOverscrollEffectTest { .onNodeWithTag(BOX_TAG) .assertTopPositionInRootIsEqualTo(info.expectedOffset(-info.layoutSize)) } + + @Test + fun isScrollInProgress_overscroll() = runTest { + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { false }, + ) + + // Start a swipe gesture, and swipe down to start an overscroll. + rule.onRoot().performTouchInput { + down(center) + moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2)) + } + + assertThat(info.scrollableState.isScrollInProgress).isTrue() + assertThat(info.overscrollEffect.isInProgress).isTrue() + + // Finish the swipe gesture. + rule.onRoot().performTouchInput { up() } + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isTrue() + + // Wait until the overscroll returns to idle. + rule.awaitIdle() + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isFalse() + } + + @Test + fun isScrollInProgress_scroll() = runTest { + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { true }, + ) + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + + // Start a swipe gesture, and swipe down to scroll. + rule.onRoot().performTouchInput { + down(center) + moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2)) + } + + assertThat(info.scrollableState.isScrollInProgress).isTrue() + assertThat(info.overscrollEffect.isInProgress).isFalse() + + // Finish the swipe gesture. + rule.onRoot().performTouchInput { up() } + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isTrue() + + // Wait until the overscroll returns to idle. + rule.awaitIdle() + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isFalse() + } + + @Test + fun isScrollInProgress_flingToScroll() = runTest { + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + canScroll = { true }, + ) + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + + // Swipe down and leave some velocity to start a fling. + rule.onRoot().performTouchInput { + swipeWithVelocity( + Offset.Zero, + Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2), + endVelocity = 100f, + ) + } + + assertThat(info.scrollableState.isScrollInProgress).isTrue() + assertThat(info.overscrollEffect.isInProgress).isFalse() + + // Wait until the fling is finished. + rule.awaitIdle() + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isFalse() + } + + @Test + fun isScrollInProgress_flingToOverscroll() = runTest { + // Start with a scrollable state. + var canScroll by mutableStateOf(true) + val info = + setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) { canScroll } + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + + // Swipe down and leave some velocity to start a fling. + rule.onRoot().performTouchInput { + swipeWithVelocity( + Offset.Zero, + Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2), + endVelocity = 100f, + ) + } + + assertThat(info.scrollableState.isScrollInProgress).isTrue() + assertThat(info.overscrollEffect.isInProgress).isFalse() + + // The fling reaches the end of the scrollable region, and an overscroll starts. + canScroll = false + rule.mainClock.advanceTimeUntil { !info.scrollableState.isScrollInProgress } + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isTrue() + + // Wait until the overscroll returns to idle. + rule.awaitIdle() + + assertThat(info.scrollableState.isScrollInProgress).isFalse() + assertThat(info.overscrollEffect.isInProgress).isFalse() + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index d903c3d16fdb..748c3b89649a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -53,7 +53,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.Notificati import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView @@ -111,7 +111,7 @@ constructor( @Composable fun AodPromotedNotificationArea(modifier: Modifier = Modifier) { - if (!PromotedNotificationUiAod.isEnabled) { + if (!PromotedNotificationUi.isEnabled) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt deleted file mode 100644 index e1ee59ba0626..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.notifications.ui.composable - -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.ScrollableDefaults -import androidx.compose.foundation.layout.offset -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastCoerceAtLeast -import com.android.compose.nestedscroll.OnStopScope -import com.android.compose.nestedscroll.PriorityNestedScrollConnection -import com.android.compose.nestedscroll.ScrollController -import kotlin.math.max -import kotlin.math.roundToInt -import kotlin.math.tanh -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -@Composable -fun Modifier.stackVerticalOverscroll( - coroutineScope: CoroutineScope, - canScrollForward: () -> Boolean, -): Modifier { - val screenHeight = - with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } - val overscrollOffset = remember { Animatable(0f) } - val flingBehavior = ScrollableDefaults.flingBehavior() - val stackNestedScrollConnection = - remember(flingBehavior) { - NotificationStackNestedScrollConnection( - stackOffset = { overscrollOffset.value }, - canScrollForward = canScrollForward, - onScroll = { offsetAvailable -> - coroutineScope.launch { - val maxProgress = screenHeight * 0.2f - val tilt = 3f - var offset = - overscrollOffset.value + - maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt)) - offset = max(offset, -1f * maxProgress) - overscrollOffset.snapTo(offset) - } - }, - onStop = { velocityAvailable -> - coroutineScope.launch { - overscrollOffset.animateTo( - targetValue = 0f, - initialVelocity = velocityAvailable, - animationSpec = tween(), - ) - } - }, - flingBehavior = flingBehavior, - ) - } - - return this.then( - Modifier.nestedScroll( - remember { - object : NestedScrollConnection { - override suspend fun onPostFling( - consumed: Velocity, - available: Velocity, - ): Velocity { - return if (available.y < 0f && !canScrollForward()) { - overscrollOffset.animateTo( - targetValue = 0f, - initialVelocity = available.y, - animationSpec = tween(), - ) - available - } else { - Velocity.Zero - } - } - } - } - ) - .nestedScroll(stackNestedScrollConnection) - .offset { IntOffset(x = 0, y = overscrollOffset.value.roundToInt()) } - ) -} - -fun NotificationStackNestedScrollConnection( - stackOffset: () -> Float, - canScrollForward: () -> Boolean, - onStart: (Float) -> Unit = {}, - onScroll: (Float) -> Unit, - onStop: (Float) -> Unit = {}, - flingBehavior: FlingBehavior, -): PriorityNestedScrollConnection { - return PriorityNestedScrollConnection( - orientation = Orientation.Vertical, - canStartPreScroll = { _, _, _ -> false }, - canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> - offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward() - }, - onStart = { firstScroll -> - onStart(firstScroll) - object : ScrollController { - override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float { - val minOffset = 0f - val consumed = deltaScroll.fastCoerceAtLeast(minOffset - stackOffset()) - if (consumed != 0f) { - onScroll(consumed) - } - return consumed - } - - override suspend fun OnStopScope.onStop(initialVelocity: Float): Float { - val consumedByScroll = flingToScroll(initialVelocity, flingBehavior) - onStop(initialVelocity - consumedByScroll) - return initialVelocity - } - - override fun onCancel() { - onStop(0f) - } - - override fun canStopOnPreFling() = false - } - }, - ) -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 79b346439d5d..2f9cfb6aa211 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -78,6 +78,7 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset @@ -92,7 +93,11 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.gesture.NestedScrollableBound +import com.android.compose.gesture.effect.OffsetOverscrollEffect +import com.android.compose.gesture.effect.rememberOffsetOverscrollEffect import com.android.compose.modifiers.thenIf +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession @@ -288,17 +293,19 @@ fun ContentScope.NotificationScrollingStack( shadeSession: SaveableSession, stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, + jankMonitor: InteractionJankMonitor, maxScrimTop: () -> Float, shouldPunchHoleBehindScrim: Boolean, stackTopPadding: Dp, stackBottomPadding: Dp, + modifier: Modifier = Modifier, shouldFillMaxSize: Boolean = true, shouldIncludeHeadsUpSpace: Boolean = true, shouldShowScrim: Boolean = true, supportNestedScrolling: Boolean, onEmptySpaceClick: (() -> Unit)? = null, - modifier: Modifier = Modifier, ) { + val composeViewRoot = LocalView.current val coroutineScope = shadeSession.sessionCoroutineScope() val density = LocalDensity.current val screenCornerRadius = LocalScreenCornerRadius.current @@ -477,6 +484,21 @@ fun ContentScope.NotificationScrollingStack( ) } + val overScrollEffect: OffsetOverscrollEffect = rememberOffsetOverscrollEffect() + // whether the stack is moving due to a swipe or fling + val isScrollInProgress = + scrollState.isScrollInProgress || overScrollEffect.isInProgress || scrimOffset.isRunning + + LaunchedEffect(isScrollInProgress) { + if (isScrollInProgress) { + jankMonitor.begin(composeViewRoot, CUJ_NOTIFICATION_SHADE_SCROLL_FLING) + debugLog(viewModel) { "STACK scroll begins" } + } else { + debugLog(viewModel) { "STACK scroll ends" } + jankMonitor.end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING) + } + } + Box( modifier = modifier @@ -577,8 +599,7 @@ fun ContentScope.NotificationScrollingStack( .thenIf(supportNestedScrolling) { Modifier.nestedScroll(scrimNestedScrollConnection) } - .stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward } - .verticalScroll(scrollState) + .verticalScroll(scrollState, overscrollEffect = overScrollEffect) .padding(top = stackTopPadding, bottom = stackBottomPadding) .fillMaxWidth() .onGloballyPositioned { coordinates -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 7cd6c6b47f2a..6d37e0affd6a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -29,6 +29,7 @@ import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection @@ -49,6 +50,7 @@ import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.OverlayShadeHeader +import com.android.systemui.shade.ui.composable.isFullWidthShade import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.util.Utils import dagger.Lazy @@ -68,6 +70,7 @@ constructor( private val keyguardClockViewModel: KeyguardClockViewModel, private val mediaCarouselController: MediaCarouselController, @Named(QUICK_QS_PANEL) private val mediaHost: Lazy<MediaHost>, + private val jankMonitor: InteractionJankMonitor, ) : Overlay { override val key = Overlays.NotificationsShade @@ -117,7 +120,7 @@ constructor( ) { Box { Column { - if (viewModel.showClock) { + if (isFullWidthShade()) { val burnIn = rememberBurnIn(keyguardClockViewModel) with(clockSection) { @@ -145,6 +148,7 @@ constructor( shadeSession = shadeSession, stackScrollView = stackScrollView.get(), viewModel = placeholderViewModel, + jankMonitor = jankMonitor, maxScrimTop = { 0f }, shouldPunchHoleBehindScrim = false, stackTopPadding = notificationStackPadding, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 0a711487ccb1..d667f68e4fdd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -75,6 +75,7 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.compose.modifiers.sysuiResTag @@ -126,6 +127,7 @@ constructor( private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory, private val mediaCarouselController: MediaCarouselController, @Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost, + private val jankMonitor: InteractionJankMonitor, ) : ExclusiveActivatable(), Scene { override val key = Scenes.QuickSettings @@ -165,6 +167,7 @@ constructor( mediaHost = mediaHost, modifier = modifier, shadeSession = shadeSession, + jankMonitor = jankMonitor, ) } @@ -186,6 +189,7 @@ private fun ContentScope.QuickSettingsScene( mediaHost: MediaHost, modifier: Modifier = Modifier, shadeSession: SaveableSession, + jankMonitor: InteractionJankMonitor, ) { val cutoutLocation = LocalDisplayCutout.current.location val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle() @@ -432,6 +436,7 @@ private fun ContentScope.QuickSettingsScene( shadeSession = shadeSession, stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, + jankMonitor = jankMonitor, maxScrimTop = { minNotificationStackTop.toFloat() }, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, stackTopPadding = notificationStackPadding, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 885d34fb95c9..60e32d7ce824 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -73,6 +73,7 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout @@ -145,6 +146,7 @@ constructor( private val mediaCarouselController: MediaCarouselController, @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost, @Named(QS_PANEL) private val qsMediaHost: MediaHost, + private val jankMonitor: InteractionJankMonitor, ) : ExclusiveActivatable(), Scene { override val key = Scenes.Shade @@ -182,6 +184,7 @@ constructor( mediaCarouselController = mediaCarouselController, qqsMediaHost = qqsMediaHost, qsMediaHost = qsMediaHost, + jankMonitor = jankMonitor, modifier = modifier, shadeSession = shadeSession, usingCollapsedLandscapeMedia = @@ -212,6 +215,7 @@ private fun ContentScope.ShadeScene( mediaCarouselController: MediaCarouselController, qqsMediaHost: MediaHost, qsMediaHost: MediaHost, + jankMonitor: InteractionJankMonitor, modifier: Modifier = Modifier, shadeSession: SaveableSession, usingCollapsedLandscapeMedia: Boolean, @@ -229,6 +233,7 @@ private fun ContentScope.ShadeScene( modifier = modifier, shadeSession = shadeSession, usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia, + jankMonitor = jankMonitor, ) is ShadeMode.Split -> SplitShade( @@ -240,6 +245,7 @@ private fun ContentScope.ShadeScene( mediaHost = qsMediaHost, modifier = modifier, shadeSession = shadeSession, + jankMonitor = jankMonitor, ) is ShadeMode.Dual -> error("Dual shade is implemented separately as an overlay.") } @@ -253,6 +259,7 @@ private fun ContentScope.SingleShade( notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, mediaCarouselController: MediaCarouselController, mediaHost: MediaHost, + jankMonitor: InteractionJankMonitor, modifier: Modifier = Modifier, shadeSession: SaveableSession, usingCollapsedLandscapeMedia: Boolean, @@ -379,6 +386,7 @@ private fun ContentScope.SingleShade( shadeSession = shadeSession, stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, + jankMonitor = jankMonitor, maxScrimTop = { maxNotifScrimTop.toFloat() }, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, stackTopPadding = notificationStackPadding, @@ -419,6 +427,7 @@ private fun ContentScope.SplitShade( mediaHost: MediaHost, modifier: Modifier = Modifier, shadeSession: SaveableSession, + jankMonitor: InteractionJankMonitor, ) { val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() val isQsEnabled by viewModel.isQsEnabled.collectAsStateWithLifecycle() @@ -596,6 +605,7 @@ private fun ContentScope.SplitShade( shadeSession = shadeSession, stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, + jankMonitor = jankMonitor, maxScrimTop = { 0f }, stackTopPadding = notificationStackPadding, stackBottomPadding = notificationStackPadding, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt new file mode 100644 index 000000000000..581f3cb172fe --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt @@ -0,0 +1,74 @@ +/* + * 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() + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt index adce9d65cbe0..e89c05f3a84d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt @@ -16,6 +16,9 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.content.res.Configuration +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -44,7 +47,7 @@ class KeyguardSmartspaceViewModelTest : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val underTest = kosmos.keyguardSmartspaceViewModel - val res = context.resources + @Mock private lateinit var mockConfiguration: Configuration @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController @@ -119,4 +122,63 @@ class KeyguardSmartspaceViewModelTest : SysuiTestCase() { assertThat(isShadeLayoutWide).isFalse() } } + + @Test + @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT) + fun dateWeatherBelowSmallClock_smartspacelayoutflag_off_true() { + val result = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + + assertThat(result).isTrue() + } + + @Test + @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT) + fun dateWeatherBelowSmallClock_defaultFontAndDisplaySize_false() { + val fontScale = 1.0f + val screenWidthDp = 347 + mockConfiguration.fontScale = fontScale + mockConfiguration.screenWidthDp = screenWidthDp + + val result = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + + assertThat(result).isFalse() + } + + @Test + @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT) + fun dateWeatherBelowSmallClock_variousFontAndDisplaySize_false() { + mockConfiguration.fontScale = 1.0f + mockConfiguration.screenWidthDp = 347 + val result1 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + assertThat(result1).isFalse() + + mockConfiguration.fontScale = 1.2f + mockConfiguration.screenWidthDp = 347 + val result2 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + assertThat(result2).isFalse() + + mockConfiguration.fontScale = 1.7f + mockConfiguration.screenWidthDp = 412 + val result3 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + assertThat(result3).isFalse() + } + + @Test + @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT) + fun dateWeatherBelowSmallClock_variousFontAndDisplaySize_true() { + mockConfiguration.fontScale = 1.0f + mockConfiguration.screenWidthDp = 310 + val result1 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + assertThat(result1).isTrue() + + mockConfiguration.fontScale = 1.5f + mockConfiguration.screenWidthDp = 347 + val result2 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + assertThat(result2).isTrue() + + mockConfiguration.fontScale = 2.0f + mockConfiguration.screenWidthDp = 411 + val result3 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration) + assertThat(result3).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt index 04ef1be9c057..ab605c0ea14e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt @@ -18,12 +18,17 @@ package com.android.systemui.mediaprojection.permission import android.app.AlertDialog import android.media.projection.MediaProjectionConfig +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.testing.TestableLooper import android.view.WindowManager import android.widget.Spinner import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.res.R @@ -32,6 +37,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.google.common.truth.Truth.assertThat import kotlin.test.assertEquals import org.junit.After +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock @@ -41,6 +47,8 @@ import org.mockito.kotlin.mock @TestableLooper.RunWithLooper(setAsMainLooper = true) class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { + @get:Rule val checkFlagRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + private lateinit var dialog: AlertDialog private val appName = "Test App" @@ -51,6 +59,8 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { R.string.media_projection_entry_app_permission_dialog_option_text_entire_screen private val resIdSingleAppDisabled = R.string.media_projection_entry_app_permission_dialog_single_app_disabled + private val resIdSingleAppNotSupported = + R.string.media_projection_entry_app_permission_dialog_single_app_not_supported @After fun teardown() { @@ -78,6 +88,7 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { } @Test + @RequiresFlagsDisabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT) fun showDialog_disableSingleApp() { setUpAndShowDialog( mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay() @@ -98,10 +109,34 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { } @Test + @RequiresFlagsEnabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT) + fun showDialog_disableSingleApp_appNotSupported() { + setUpAndShowDialog( + mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay() + ) + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) + val secondOptionWarningText = + spinner.adapter + .getDropDownView(1, null, spinner) + .findViewById<TextView>(android.R.id.text2) + ?.text + + // check that the first option is full screen and enabled + assertEquals(context.getString(resIdFullScreen), spinner.selectedItem) + + // check that the second option is single app and disabled + assertEquals( + context.getString(resIdSingleAppNotSupported, appName), + secondOptionWarningText, + ) + } + + @Test fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() { setUpAndShowDialog( mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(), - overrideDisableSingleAppOption = true + overrideDisableSingleAppOption = true, ) val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) @@ -161,7 +196,7 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { appName, overrideDisableSingleAppOption, hostUid = 12345, - mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>() + mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>(), ) dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt index 6495b66cc148..17cdb8dd592d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt @@ -18,12 +18,17 @@ package com.android.systemui.mediaprojection.permission import android.app.AlertDialog import android.media.projection.MediaProjectionConfig +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.testing.TestableLooper import android.view.WindowManager import android.widget.Spinner import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.res.R @@ -32,6 +37,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.google.common.truth.Truth.assertThat import kotlin.test.assertEquals import org.junit.After +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock @@ -41,6 +47,8 @@ import org.mockito.kotlin.mock @TestableLooper.RunWithLooper(setAsMainLooper = true) class SystemCastPermissionDialogDelegateTest : SysuiTestCase() { + @get:Rule val checkFlagRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + private lateinit var dialog: AlertDialog private val appName = "Test App" @@ -51,6 +59,8 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() { R.string.media_projection_entry_cast_permission_dialog_option_text_entire_screen private val resIdSingleAppDisabled = R.string.media_projection_entry_app_permission_dialog_single_app_disabled + private val resIdSingleAppNotSupported = + R.string.media_projection_entry_app_permission_dialog_single_app_not_supported @After fun teardown() { @@ -78,6 +88,7 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() { } @Test + @RequiresFlagsDisabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT) fun showDialog_disableSingleApp() { setUpAndShowDialog( mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay() @@ -98,6 +109,30 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() { } @Test + @RequiresFlagsEnabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT) + fun showDialog_disableSingleApp_appNotSupported() { + setUpAndShowDialog( + mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay() + ) + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) + val secondOptionWarningText = + spinner.adapter + .getDropDownView(1, null, spinner) + .findViewById<TextView>(android.R.id.text2) + ?.text + + // check that the first option is full screen and enabled + assertEquals(context.getString(resIdFullScreen), spinner.selectedItem) + + // check that the second option is single app and disabled + assertEquals( + context.getString(resIdSingleAppNotSupported, appName), + secondOptionWarningText, + ) + } + + @Test fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() { setUpAndShowDialog( mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(), @@ -169,7 +204,7 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() { SystemUIDialog.setDialogSize(dialog) dialog.window?.addSystemFlags( - WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS ) delegate.onCreate(dialog, savedInstanceState = null) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt index ffcd95bc7a4a..cd7b658518b6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt @@ -38,13 +38,10 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository -import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository -import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -116,38 +113,6 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { } @Test - fun showClock_showsOnNarrowScreen() = - testScope.runTest { - kosmos.shadeRepository.setShadeLayoutWide(false) - - // Shown when notifications are present. - kosmos.activeNotificationListRepository.setActiveNotifs(1) - runCurrent() - assertThat(underTest.showClock).isTrue() - - // Hidden when notifications are not present. - kosmos.activeNotificationListRepository.setActiveNotifs(0) - runCurrent() - assertThat(underTest.showClock).isFalse() - } - - @Test - fun showClock_hidesOnWideScreen() = - testScope.runTest { - kosmos.shadeRepository.setShadeLayoutWide(true) - - // Hidden when notifications are present. - kosmos.activeNotificationListRepository.setActiveNotifs(1) - runCurrent() - assertThat(underTest.showClock).isFalse() - - // Hidden when notifications are not present. - kosmos.activeNotificationListRepository.setActiveNotifs(0) - runCurrent() - assertThat(underTest.showClock).isFalse() - } - - @Test fun showMedia_activeMedia_true() = testScope.runTest { kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java index a831e6344a66..fd796a56652b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java @@ -204,6 +204,21 @@ public class ScrollCaptureControllerTest extends SysuiTestCase { assertEquals("bottom", 200, screenshot.getBottom()); } + @Test + public void testCancellation() { + ScrollCaptureController controller = new TestScenario() + .withPageHeight(100) + .withMaxPages(2.5f) + .withTileHeight(10) + .withAvailableRange(-10, Integer.MAX_VALUE) + .createController(mContext); + + ScrollCaptureController.LongScreenshot screenshot = + getUnchecked(controller.run(EMPTY_RESPONSE)); + + assertEquals("top", -10, screenshot.getTop()); + assertEquals("bottom", 240, screenshot.getBottom()); + } /** * Build and configure a stubbed controller for each test case. */ diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt index bad33a402ff7..915edc03952d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt @@ -32,12 +32,11 @@ import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips -import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi -import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization +import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController import com.android.systemui.testKosmos @@ -50,12 +49,8 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) -@EnableFlags( - PromotedNotificationUi.FLAG_NAME, - StatusBarNotifChips.FLAG_NAME, - StatusBarChipsModernization.FLAG_NAME, - StatusBarRootModernization.FLAG_NAME, -) +@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) +@EnableChipsModernization class AODPromotedNotificationsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() @@ -111,10 +106,10 @@ class AODPromotedNotificationsInteractorTest : SysuiTestCase() { renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) - // THEN aod content is sensitive + // THEN aod content is redacted val content by collectLastValue(underTest.content) assertThat(content).isNotNull() - assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") + assertThat(content!!.title).isEqualTo("REDACTED") } @Test @@ -128,10 +123,10 @@ class AODPromotedNotificationsInteractorTest : SysuiTestCase() { renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) - // THEN aod content is sensitive + // THEN aod content is redacted val content by collectLastValue(underTest.content) assertThat(content).isNotNull() - assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") + assertThat(content!!.title).isEqualTo("REDACTED") } private fun Kosmos.setKeyguardLocked(locked: Boolean) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index f7bbf989ad3f..e03dbf54e101 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController @@ -155,7 +155,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) fun maxKeyguardNotificationsForPromotedOngoing_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 @@ -283,7 +283,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) fun getSpaceNeeded_onLockscreenEnoughSpacePromotedOngoing_intrinsicHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 @@ -342,7 +342,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) fun getSpaceNeeded_onLockscreenSavingSpacePromotedOngoing_minHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index a31c0bd35453..2875b7e2ae92 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -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 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt b/packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt new file mode 100644 index 000000000000..fec98a311fbd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt @@ -0,0 +1,58 @@ +/* + * 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() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index 02e04aa279d8..21b28a24213f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -35,7 +35,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor import com.android.systemui.util.kotlin.combine import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated @@ -90,14 +90,14 @@ constructor( var clock: ClockController? by keyguardClockRepository.clockEventController::clock private val isAodPromotedNotificationPresent: Flow<Boolean> = - if (PromotedNotificationUiAod.isEnabled) { + if (PromotedNotificationUi.isEnabled) { aodPromotedNotificationInteractor.isPresent } else { flowOf(false) } private val areAnyNotificationsPresent: Flow<Boolean> = - if (PromotedNotificationUiAod.isEnabled) { + if (PromotedNotificationUi.isEnabled) { combine( activeNotificationsInteractor.areAnyNotificationsPresent, isAodPromotedNotificationPresent, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index fc5914b02e05..f38a2430b8fc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -128,13 +128,7 @@ object KeyguardBlueprintViewBinder { cs: ConstraintSet, constraintLayout: ConstraintLayout, ) { - val ids = - listOf( - sharedR.id.date_smartspace_view, - sharedR.id.date_smartspace_view_large, - sharedR.id.weather_smartspace_view, - sharedR.id.weather_smartspace_view_large, - ) + val ids = listOf(sharedR.id.date_smartspace_view, sharedR.id.date_smartspace_view_large) for (i in ids) { constraintLayout.getViewById(i)?.visibility = cs.getVisibility(i) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 60460bf68c12..2fdca6bc68d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -193,7 +193,6 @@ object KeyguardRootViewBinder { childViews[largeClockId]?.translationY = y if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { childViews[largeClockDateId]?.translationY = y - childViews[largeClockWeatherId]?.translationY = y } childViews[aodPromotedNotificationId]?.translationY = y childViews[aodNotificationIconContainerId]?.translationY = y @@ -584,7 +583,6 @@ object KeyguardRootViewBinder { private val aodNotificationIconContainerId = R.id.aod_notification_icon_container private val largeClockId = customR.id.lockscreen_clock_view_large private val largeClockDateId = sharedR.id.date_smartspace_view_large - private val largeClockWeatherId = sharedR.id.weather_smartspace_view_large private val smallClockId = customR.id.lockscreen_clock_view private val indicationArea = R.id.keyguard_indication_area private val startButton = R.id.start_button diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 5ef2d6fd3256..39fe588d8b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -91,14 +91,9 @@ object KeyguardSmartspaceViewBinder { R.dimen.smartspace_padding_vertical ) - val smallViewIds = - listOf(sharedR.id.date_smartspace_view, sharedR.id.weather_smartspace_view) + val smallViewId = sharedR.id.date_smartspace_view - val largeViewIds = - listOf( - sharedR.id.date_smartspace_view_large, - sharedR.id.weather_smartspace_view_large, - ) + val largeViewId = sharedR.id.date_smartspace_view_large launch("$TAG#smartspaceViewModel.burnInLayerVisibility") { combine( @@ -109,10 +104,8 @@ object KeyguardSmartspaceViewBinder { .collect { (visibility, isLargeClock) -> if (isLargeClock) { // hide small clock date/weather - for (viewId in smallViewIds) { - keyguardRootView.findViewById<View>(viewId)?.let { - it.visibility = View.GONE - } + keyguardRootView.findViewById<View>(smallViewId)?.let { + it.visibility = View.GONE } } } @@ -130,10 +123,9 @@ object KeyguardSmartspaceViewBinder { ::Pair, ) .collect { (isLargeClock, clockBounds) -> - for (id in (if (isLargeClock) smallViewIds else largeViewIds)) { - keyguardRootView.findViewById<View>(id)?.let { - it.visibility = View.GONE - } + val viewId = if (isLargeClock) smallViewId else largeViewId + keyguardRootView.findViewById<View>(viewId)?.let { + it.visibility = View.GONE } if (clockBounds == VRectF.ZERO) return@collect @@ -144,26 +136,26 @@ object KeyguardSmartspaceViewBinder { sharedR.id.date_smartspace_view_large ) ?.height ?: 0 - for (id in largeViewIds) { - keyguardRootView.findViewById<View>(id)?.let { view -> - val viewHeight = view.height - val offset = (largeDateHeight - viewHeight) / 2 - view.top = - (clockBounds.bottom + yBuffer + offset).toInt() - view.bottom = view.top + viewHeight - } + + keyguardRootView.findViewById<View>(largeViewId)?.let { view -> + val viewHeight = view.height + val offset = (largeDateHeight - viewHeight) / 2 + view.top = (clockBounds.bottom + yBuffer + offset).toInt() + view.bottom = view.top + viewHeight } - } else { - for (id in smallViewIds) { - keyguardRootView.findViewById<View>(id)?.let { view -> - val viewWidth = view.width - if (view.isLayoutRtl()) { - view.right = (clockBounds.left - xBuffer).toInt() - view.left = view.right - viewWidth - } else { - view.left = (clockBounds.right + xBuffer).toInt() - view.right = view.left + viewWidth - } + } else if ( + !KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock( + keyguardRootView.resources.configuration + ) + ) { + keyguardRootView.findViewById<View>(smallViewId)?.let { view -> + val viewWidth = view.width + if (view.isLayoutRtl()) { + view.right = (clockBounds.left - xBuffer).toInt() + view.left = view.right - viewWidth + } else { + view.left = (clockBounds.right + xBuffer).toInt() + view.right = view.left + viewWidth } } } @@ -218,11 +210,6 @@ object KeyguardSmartspaceViewBinder { val dateView = constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view) addView(dateView) - if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { - val weatherView = - constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view) - addView(weatherView) - } } } } @@ -240,11 +227,6 @@ object KeyguardSmartspaceViewBinder { val dateView = constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view) removeView(dateView) - if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { - val weatherView = - constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view) - removeView(weatherView) - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index f717431f6a40..bca0bedc7350 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -39,7 +39,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDi import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.ui.SystemBarUtilsState import com.android.systemui.util.ui.value @@ -102,7 +102,7 @@ constructor( val isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value constraintSet.apply { - if (PromotedNotificationUiAod.isEnabled) { + if (PromotedNotificationUi.isEnabled) { connect(nicId, TOP, AodPromotedNotificationSection.viewId, BOTTOM, bottomMargin) } else { connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin) @@ -111,7 +111,7 @@ constructor( setGoneMargin(nicId, BOTTOM, bottomMargin) setVisibility(nicId, if (isVisible.value) VISIBLE else GONE) - if (PromotedNotificationUiAod.isEnabled && isShadeLayoutWide) { + if (PromotedNotificationUi.isEnabled && isShadeLayoutWide) { // Don't create a start constraint, so the icons can hopefully right-align. } else { connect(nicId, START, PARENT_ID, START, horizontalMargin) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt index efdc5abf1f67..f75b53017500 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt @@ -31,7 +31,7 @@ import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel import javax.inject.Inject @@ -50,7 +50,7 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!PromotedNotificationUiAod.isEnabled) { + if (!PromotedNotificationUi.isEnabled) { return } @@ -67,7 +67,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!PromotedNotificationUiAod.isEnabled) { + if (!PromotedNotificationUi.isEnabled) { return } @@ -79,7 +79,7 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!PromotedNotificationUiAod.isEnabled) { + if (!PromotedNotificationUi.isEnabled) { return } @@ -119,7 +119,7 @@ constructor( } override fun removeViews(constraintLayout: ConstraintLayout) { - if (!PromotedNotificationUiAod.isEnabled) { + if (!PromotedNotificationUi.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index 8a33c6471326..9c6f46570b1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -121,18 +121,22 @@ constructor( setAlpha(getNonTargetClockFace(clock).views, 0F) if (!keyguardClockViewModel.isLargeClockVisible.value) { - if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + if ( + KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock( + context.resources.configuration + ) + ) { connect( sharedR.id.bc_smartspace_view, TOP, - customR.id.lockscreen_clock_view, + sharedR.id.date_smartspace_view, BOTTOM, ) } else { connect( sharedR.id.bc_smartspace_view, TOP, - sharedR.id.date_smartspace_view, + customR.id.lockscreen_clock_view, BOTTOM, ) } @@ -187,6 +191,8 @@ constructor( val guideline = if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID else R.id.split_shade_guideline + val dateWeatherBelowSmallClock = + KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(context.resources.configuration) constraints.apply { connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START) connect(customR.id.lockscreen_clock_view_large, END, guideline, END) @@ -254,11 +260,7 @@ constructor( 0 } - if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { - clockInteractor.setNotificationStackDefaultTop( - (smallClockBottom + marginBetweenSmartspaceAndNotification).toFloat() - ) - } else { + if (dateWeatherBelowSmallClock) { val dateWeatherSmartspaceHeight = getDimen(context, DATE_WEATHER_VIEW_HEIGHT).toFloat() clockInteractor.setNotificationStackDefaultTop( @@ -266,6 +268,10 @@ constructor( dateWeatherSmartspaceHeight + marginBetweenSmartspaceAndNotification ) + } else { + clockInteractor.setNotificationStackDefaultTop( + (smallClockBottom + marginBetweenSmartspaceAndNotification).toFloat() + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index d0b5f743c277..d9652b590678 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -20,6 +20,7 @@ import android.content.Context import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver.OnGlobalLayoutListener +import android.widget.LinearLayout import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet @@ -57,10 +58,8 @@ constructor( private val keyguardRootViewModel: KeyguardRootViewModel, ) : KeyguardSection() { private var smartspaceView: View? = null - private var weatherView: View? = null private var dateView: ViewGroup? = null - private var weatherViewLargeClock: View? = null - private var dateViewLargeClock: View? = null + private var dateViewLargeClock: ViewGroup? = null private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null private var pastVisibility: Int = -1 @@ -77,34 +76,47 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) { if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return smartspaceView = smartspaceController.buildAndConnectView(constraintLayout) - weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout, false) dateView = smartspaceController.buildAndConnectDateView(constraintLayout, false) as? ViewGroup + var weatherViewLargeClock: View? = null + val weatherView: View? = + smartspaceController.buildAndConnectWeatherView(constraintLayout, false) if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { weatherViewLargeClock = smartspaceController.buildAndConnectWeatherView(constraintLayout, true) dateViewLargeClock = - smartspaceController.buildAndConnectDateView(constraintLayout, true) + smartspaceController.buildAndConnectDateView(constraintLayout, true) as? ViewGroup } pastVisibility = smartspaceView?.visibility ?: View.GONE constraintLayout.addView(smartspaceView) if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { dateView?.visibility = View.GONE - weatherView?.visibility = View.GONE dateViewLargeClock?.visibility = View.GONE - weatherViewLargeClock?.visibility = View.GONE - constraintLayout.addView(dateView) - constraintLayout.addView(weatherView) - constraintLayout.addView(weatherViewLargeClock) constraintLayout.addView(dateViewLargeClock) - } else { if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) { - constraintLayout.addView(dateView) // Place weather right after the date, before the extras (alarm and dnd) - val index = if (dateView?.childCount == 0) 0 else 1 - dateView?.addView(weatherView, index) + val index = if (dateViewLargeClock?.childCount == 0) 0 else 1 + dateViewLargeClock?.addView(weatherViewLargeClock, index) + } + + if ( + KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock( + context.resources.configuration, + keyguardClockViewModel.hasCustomWeatherDataDisplay.value, + ) + ) { + (dateView as? LinearLayout)?.orientation = LinearLayout.HORIZONTAL + } else { + (dateView as? LinearLayout)?.orientation = LinearLayout.VERTICAL } } + + if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) { + constraintLayout.addView(dateView) + // Place weather right after the date, before the extras (alarm and dnd) + val index = if (dateView?.childCount == 0) 0 else 1 + dateView?.addView(weatherView, index) + } keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView smartspaceVisibilityListener = OnGlobalLayoutListener { smartspaceView?.let { @@ -136,10 +148,15 @@ constructor( val dateWeatherPaddingStart = KeyguardSmartspaceViewModel.getDateWeatherStartMargin(context) val smartspaceHorizontalPadding = KeyguardSmartspaceViewModel.getSmartspaceHorizontalMargin(context) + val dateWeatherBelowSmallClock = + KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock( + context.resources.configuration, + keyguardClockViewModel.hasCustomWeatherDataDisplay.value, + ) constraintSet.apply { constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) - if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + if (dateWeatherBelowSmallClock) { connect( sharedR.id.date_smartspace_view, ConstraintSet.START, @@ -167,7 +184,7 @@ constructor( smartspaceHorizontalPadding, ) if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) { - if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + if (dateWeatherBelowSmallClock) { clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP) connect( sharedR.id.date_smartspace_view, @@ -179,12 +196,27 @@ constructor( } else { clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM) if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { - connect( - sharedR.id.bc_smartspace_view, - ConstraintSet.TOP, - customR.id.lockscreen_clock_view, - ConstraintSet.BOTTOM, - ) + if (dateWeatherBelowSmallClock) { + connect( + sharedR.id.date_smartspace_view, + ConstraintSet.TOP, + customR.id.lockscreen_clock_view, + ConstraintSet.BOTTOM, + ) + connect( + sharedR.id.bc_smartspace_view, + ConstraintSet.TOP, + sharedR.id.date_smartspace_view, + ConstraintSet.BOTTOM, + ) + } else { + connect( + sharedR.id.bc_smartspace_view, + ConstraintSet.TOP, + customR.id.lockscreen_clock_view, + ConstraintSet.BOTTOM, + ) + } } else { connect( sharedR.id.date_smartspace_view, @@ -203,7 +235,6 @@ constructor( if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { if (keyguardClockViewModel.isLargeClockVisible.value) { - setVisibility(sharedR.id.weather_smartspace_view, GONE) setVisibility(sharedR.id.date_smartspace_view, GONE) constrainHeight( sharedR.id.date_smartspace_view_large, @@ -238,118 +269,79 @@ constructor( connect( sharedR.id.date_smartspace_view_large, ConstraintSet.END, - sharedR.id.weather_smartspace_view_large, - ConstraintSet.START, - ) - - connect( - sharedR.id.weather_smartspace_view_large, - ConstraintSet.BOTTOM, - sharedR.id.date_smartspace_view_large, - ConstraintSet.BOTTOM, - ) - - connect( - sharedR.id.weather_smartspace_view_large, - ConstraintSet.TOP, - sharedR.id.date_smartspace_view_large, - ConstraintSet.TOP, - ) - - connect( - sharedR.id.weather_smartspace_view_large, - ConstraintSet.START, - sharedR.id.date_smartspace_view_large, - ConstraintSet.END, - ) - - connect( - sharedR.id.weather_smartspace_view_large, - ConstraintSet.END, customR.id.lockscreen_clock_view_large, ConstraintSet.END, ) - - setHorizontalChainStyle( - sharedR.id.weather_smartspace_view_large, - ConstraintSet.CHAIN_PACKED, - ) setHorizontalChainStyle( sharedR.id.date_smartspace_view_large, ConstraintSet.CHAIN_PACKED, ) } else { - setVisibility(sharedR.id.weather_smartspace_view_large, GONE) - setVisibility(sharedR.id.date_smartspace_view_large, GONE) - constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) - constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) - constrainHeight(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT) - constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT) + if (dateWeatherBelowSmallClock) { + connect( + sharedR.id.date_smartspace_view, + ConstraintSet.START, + ConstraintSet.PARENT_ID, + ConstraintSet.START, + dateWeatherPaddingStart, + ) + } else { + setVisibility(sharedR.id.date_smartspace_view_large, GONE) + constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) + constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) + connect( + sharedR.id.date_smartspace_view, + ConstraintSet.START, + customR.id.lockscreen_clock_view, + ConstraintSet.END, + context.resources.getDimensionPixelSize( + R.dimen.smartspace_padding_horizontal + ), + ) + connect( + sharedR.id.date_smartspace_view, + ConstraintSet.TOP, + customR.id.lockscreen_clock_view, + ConstraintSet.TOP, + ) + connect( + sharedR.id.date_smartspace_view, + ConstraintSet.BOTTOM, + customR.id.lockscreen_clock_view, + ConstraintSet.BOTTOM, + ) + } + } + } - connect( - sharedR.id.date_smartspace_view, - ConstraintSet.START, - customR.id.lockscreen_clock_view, - ConstraintSet.END, - context.resources.getDimensionPixelSize( - R.dimen.smartspace_padding_horizontal - ), - ) - connect( - sharedR.id.date_smartspace_view, - ConstraintSet.TOP, - customR.id.lockscreen_clock_view, - ConstraintSet.TOP, - ) - connect( - sharedR.id.date_smartspace_view, - ConstraintSet.BOTTOM, - sharedR.id.weather_smartspace_view, - ConstraintSet.TOP, - ) - connect( - sharedR.id.weather_smartspace_view, - ConstraintSet.START, - sharedR.id.date_smartspace_view, - ConstraintSet.START, - ) - connect( - sharedR.id.weather_smartspace_view, - ConstraintSet.TOP, - sharedR.id.date_smartspace_view, - ConstraintSet.BOTTOM, + if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + if (dateWeatherBelowSmallClock) { + createBarrier( + R.id.smart_space_barrier_bottom, + Barrier.BOTTOM, + 0, + *intArrayOf(sharedR.id.bc_smartspace_view, sharedR.id.date_smartspace_view), ) - connect( - sharedR.id.weather_smartspace_view, - ConstraintSet.BOTTOM, - customR.id.lockscreen_clock_view, - ConstraintSet.BOTTOM, + createBarrier( + R.id.smart_space_barrier_top, + Barrier.TOP, + 0, + *intArrayOf(sharedR.id.bc_smartspace_view, sharedR.id.date_smartspace_view), ) - - setVerticalChainStyle( - sharedR.id.weather_smartspace_view, - ConstraintSet.CHAIN_PACKED, + } else { + createBarrier( + R.id.smart_space_barrier_bottom, + Barrier.BOTTOM, + 0, + sharedR.id.bc_smartspace_view, ) - setVerticalChainStyle( - sharedR.id.date_smartspace_view, - ConstraintSet.CHAIN_PACKED, + createBarrier( + R.id.smart_space_barrier_top, + Barrier.TOP, + 0, + sharedR.id.bc_smartspace_view, ) } - } - - if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { - createBarrier( - R.id.smart_space_barrier_bottom, - Barrier.BOTTOM, - 0, - sharedR.id.bc_smartspace_view, - ) - createBarrier( - R.id.smart_space_barrier_top, - Barrier.TOP, - 0, - sharedR.id.bc_smartspace_view, - ) } else { createBarrier( R.id.smart_space_barrier_bottom, @@ -373,13 +365,7 @@ constructor( val list = if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { - listOf( - smartspaceView, - dateView, - weatherView, - weatherViewLargeClock, - dateViewLargeClock, - ) + listOf(smartspaceView, dateView, dateViewLargeClock) } else { listOf(smartspaceView, dateView) } @@ -424,10 +410,8 @@ constructor( if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { if (keyguardClockViewModel.isLargeClockVisible.value) { - setVisibility(sharedR.id.weather_smartspace_view, GONE) setVisibility(sharedR.id.date_smartspace_view, GONE) } else { - setVisibility(sharedR.id.weather_smartspace_view_large, GONE) setVisibility(sharedR.id.date_smartspace_view_large, GONE) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 434d7eadd742..d830a8456d66 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -299,14 +299,12 @@ class ClockSizeTransition( } if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { addTarget(sharedR.id.date_smartspace_view_large) - addTarget(sharedR.id.weather_smartspace_view_large) } } else { logger.i("Adding small clock") addTarget(customR.id.lockscreen_clock_view) - if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + if (!viewModel.dateWeatherBelowSmallClock()) { addTarget(sharedR.id.date_smartspace_view) - addTarget(sharedR.id.weather_smartspace_view) } } } @@ -386,7 +384,7 @@ class ClockSizeTransition( duration = if (isLargeClock) STATUS_AREA_MOVE_UP_MILLIS else STATUS_AREA_MOVE_DOWN_MILLIS interpolator = Interpolators.EMPHASIZED - if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + if (viewModel.dateWeatherBelowSmallClock()) { addTarget(sharedR.id.date_smartspace_view) } addTarget(sharedR.id.bc_smartspace_view) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt index 0874b6da180e..9faca7567279 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt @@ -32,7 +32,6 @@ class DefaultClockSteppingTransition(private val clock: ClockController) : Trans addTarget(clock.largeClock.view) if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { addTarget(sharedR.id.date_smartspace_view_large) - addTarget(sharedR.id.weather_smartspace_view_large) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index dcbf7b5a9335..cf6845354f44 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -180,6 +180,9 @@ constructor( val largeClockTextSize: Flow<Int> = configurationInteractor.dimensionPixelSize(customR.dimen.large_clock_text_size) + fun dateWeatherBelowSmallClock() = + KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(context.resources.configuration) + enum class ClockLayout { LARGE_CLOCK, SMALL_CLOCK, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index 5cc34e749b46..a00d0ced2c07 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -17,6 +17,8 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context +import android.content.res.Configuration +import android.util.Log import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -94,6 +96,43 @@ constructor( val isShadeLayoutWide: StateFlow<Boolean> = shadeModeInteractor.isShadeLayoutWide companion object { + private const val TAG = "KeyguardSmartspaceVM" + + fun dateWeatherBelowSmallClock( + configuration: Configuration, + customDateWeather: Boolean = false, + ): Boolean { + return if ( + com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout() && + !customDateWeather + ) { + // font size to display size + // These values come from changing the font size and display size on a non-foldable. + // Visually looked at which configs cause the date/weather to push off of the screen + val breakingPairs = + listOf( + 0.85f to 320, // tiny font size but large display size + 1f to 346, + 1.15f to 346, + 1.5f to 376, + 1.8f to 411, // large font size but tiny display size + ) + val screenWidthDp = configuration.screenWidthDp + val fontScale = configuration.fontScale + var fallBelow = false + for ((font, width) in breakingPairs) { + if (fontScale >= font && screenWidthDp <= width) { + fallBelow = true + break + } + } + Log.d(TAG, "Width: $screenWidthDp, Font: $fontScale, BelowClock: $fallBelow") + return fallBelow + } else { + true + } + } + fun getDateWeatherStartMargin(context: Context): Int { return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) + context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal) diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index bf1f971c0f8c..4f86257e3870 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -609,8 +609,7 @@ public class MediaSwitchingController devices, getSelectedMediaDevice(), connectedMediaDevice, - needToHandleMutingExpectedDevice, - getConnectNewDeviceItem()); + needToHandleMutingExpectedDevice); } else { List<MediaItem> updatedMediaItems = buildMediaItems( @@ -701,7 +700,6 @@ public class MediaSwitchingController } } dividerItems.forEach(finalMediaItems::add); - attachConnectNewDeviceItemIfNeeded(finalMediaItems); return finalMediaItems; } } @@ -765,7 +763,6 @@ public class MediaSwitchingController finalMediaItems.add(MediaItem.createDeviceMediaItem(device)); } } - attachConnectNewDeviceItemIfNeeded(finalMediaItems); return finalMediaItems; } @@ -879,6 +876,15 @@ public class MediaSwitchingController }); } + private List<MediaItem> getOutputDeviceList(boolean addConnectDeviceButton) { + List<MediaItem> mediaItems = new ArrayList<>( + mOutputMediaItemListProxy.getOutputMediaItemList()); + if (addConnectDeviceButton) { + attachConnectNewDeviceItemIfNeeded(mediaItems); + } + return mediaItems; + } + private void addInputDevices(List<MediaItem> mediaItems) { mediaItems.add( MediaItem.createGroupDividerMediaItem( @@ -886,22 +892,34 @@ public class MediaSwitchingController mediaItems.addAll(mInputMediaItemList); } - private void addOutputDevices(List<MediaItem> mediaItems) { + private void addOutputDevices(List<MediaItem> mediaItems, boolean addConnectDeviceButton) { mediaItems.add( MediaItem.createGroupDividerMediaItem( mContext.getString(R.string.media_output_group_title))); - mediaItems.addAll(mOutputMediaItemListProxy.getOutputMediaItemList()); + mediaItems.addAll(getOutputDeviceList(addConnectDeviceButton)); } + /** + * Returns a list of media items to be rendered in the device list. For backward compatibility + * reasons, adds a "Connect a device" button by default. + */ public List<MediaItem> getMediaItemList() { + return getMediaItemList(true /* addConnectDeviceButton */); + } + + /** + * Returns a list of media items to be rendered in the device list. + * @param addConnectDeviceButton Whether to add a "Connect a device" button to the list. + */ + public List<MediaItem> getMediaItemList(boolean addConnectDeviceButton) { // If input routing is not enabled, only return output media items. if (!enableInputRouting()) { - return mOutputMediaItemListProxy.getOutputMediaItemList(); + return getOutputDeviceList(addConnectDeviceButton); } // If input routing is enabled, return both output and input media items. List<MediaItem> mediaItems = new ArrayList<>(); - addOutputDevices(mediaItems); + addOutputDevices(mediaItems, addConnectDeviceButton); addInputDevices(mediaItems); return mediaItems; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java index 45ca2c6ee8e5..c15ef82f0378 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java @@ -44,7 +44,6 @@ public class OutputMediaItemListProxy { private final List<MediaItem> mSelectedMediaItems; private final List<MediaItem> mSuggestedMediaItems; private final List<MediaItem> mSpeakersAndDisplaysMediaItems; - @Nullable private MediaItem mConnectNewDeviceMediaItem; public OutputMediaItemListProxy(Context context) { mContext = context; @@ -88,9 +87,6 @@ public class OutputMediaItemListProxy { R.string.media_output_group_title_speakers_and_displays))); finalMediaItems.addAll(mSpeakersAndDisplaysMediaItems); } - if (mConnectNewDeviceMediaItem != null) { - finalMediaItems.add(mConnectNewDeviceMediaItem); - } return finalMediaItems; } @@ -99,8 +95,7 @@ public class OutputMediaItemListProxy { List<MediaDevice> devices, List<MediaDevice> selectedDevices, @Nullable MediaDevice connectedMediaDevice, - boolean needToHandleMutingExpectedDevice, - @Nullable MediaItem connectNewDeviceMediaItem) { + boolean needToHandleMutingExpectedDevice) { Set<String> selectedOrConnectedMediaDeviceIds = selectedDevices.stream().map(MediaDevice::getId).collect(Collectors.toSet()); if (connectedMediaDevice != null) { @@ -177,7 +172,6 @@ public class OutputMediaItemListProxy { mSuggestedMediaItems.addAll(updatedSuggestedMediaItems); mSpeakersAndDisplaysMediaItems.clear(); mSpeakersAndDisplaysMediaItems.addAll(updatedSpeakersAndDisplaysMediaItems); - mConnectNewDeviceMediaItem = connectNewDeviceMediaItem; // The cached mOutputMediaItemList is cleared upon any update to individual media item // lists. This ensures getOutputMediaItemList() computes and caches a fresh list on the next @@ -197,10 +191,6 @@ public class OutputMediaItemListProxy { mSelectedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); mSuggestedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); mSpeakersAndDisplaysMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); - if (mConnectNewDeviceMediaItem != null - && mConnectNewDeviceMediaItem.isMutingExpectedDevice()) { - mConnectNewDeviceMediaItem = null; - } } mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); } @@ -211,7 +201,6 @@ public class OutputMediaItemListProxy { mSelectedMediaItems.clear(); mSuggestedMediaItems.clear(); mSpeakersAndDisplaysMediaItems.clear(); - mConnectNewDeviceMediaItem = null; } mOutputMediaItemList.clear(); } @@ -221,8 +210,7 @@ public class OutputMediaItemListProxy { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { return mSelectedMediaItems.isEmpty() && mSuggestedMediaItems.isEmpty() - && mSpeakersAndDisplaysMediaItems.isEmpty() - && (mConnectNewDeviceMediaItem == null); + && mSpeakersAndDisplaysMediaItems.isEmpty(); } else { return mOutputMediaItemList.isEmpty(); } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt index 88cbc3867744..a8d0e0573d89 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt @@ -18,6 +18,7 @@ package com.android.systemui.mediaprojection.permission import android.content.Context import android.media.projection.MediaProjectionConfig +import com.android.media.projection.flags.Flags import com.android.systemui.res.R /** Various utility methods related to media projection permissions. */ @@ -28,13 +29,27 @@ object MediaProjectionPermissionUtils { mediaProjectionConfig: MediaProjectionConfig?, overrideDisableSingleAppOption: Boolean, ): String? { - // The single app option should only be disabled if the client has setup a - // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND - // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override. + val singleAppOptionDisabled = !overrideDisableSingleAppOption && - mediaProjectionConfig?.regionToCapture == - MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY + if (Flags.appContentSharing()) { + // The single app option should only be disabled if the client has setup a + // MediaProjection with MediaProjection.isChoiceAppEnabled == false (e.g by + // creating it + // with MediaProjectionConfig#createConfigForDefaultDisplay AND + // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app + // override. + mediaProjectionConfig?.isSourceEnabled( + MediaProjectionConfig.PROJECTION_SOURCE_APP + ) == false + } else { + // The single app option should only be disabled if the client has setup a + // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND + // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app + // override. + mediaProjectionConfig?.regionToCapture == + MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY + } return if (singleAppOptionDisabled) { context.getString( R.string.media_projection_entry_app_permission_dialog_single_app_disabled, diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt index 465c78e91e53..2a7fb5467173 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt @@ -23,17 +23,14 @@ import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOf @@ -51,31 +48,12 @@ constructor( val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, - shadeModeInteractor: ShadeModeInteractor, disableFlagsInteractor: DisableFlagsInteractor, mediaCarouselInteractor: MediaCarouselInteractor, - activeNotificationsInteractor: ActiveNotificationsInteractor, ) : ExclusiveActivatable() { private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator") - val showClock: Boolean by - hydrator.hydratedStateOf( - traceName = "showClock", - initialValue = - shouldShowClock( - isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value, - areAnyNotificationsPresent = - activeNotificationsInteractor.areAnyNotificationsPresentValue, - ), - source = - combine( - shadeModeInteractor.isShadeLayoutWide, - activeNotificationsInteractor.areAnyNotificationsPresent, - this::shouldShowClock, - ), - ) - val showMedia: Boolean by hydrator.hydratedStateOf( traceName = "showMedia", @@ -114,13 +92,6 @@ constructor( shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked") } - private fun shouldShowClock( - isShadeLayoutWide: Boolean, - areAnyNotificationsPresent: Boolean, - ): Boolean { - return !isShadeLayoutWide && areAnyNotificationsPresent - } - @AssistedFactory interface Factory { fun create(): NotificationsShadeOverlayContentViewModel diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index 1a0af514cf87..bd7e7832751a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -290,6 +290,8 @@ private fun TileLabel( ) { var textSize by remember { mutableIntStateOf(0) } + val iterations = if (isVisible()) TILE_MARQUEE_ITERATIONS else 0 + BasicText( text = text, color = color, @@ -322,14 +324,10 @@ private fun TileLabel( ) } } - .thenIf(isVisible()) { - // Only apply the marquee when the label is visible, which is needed for the - // always composed QS - Modifier.basicMarquee( - iterations = TILE_MARQUEE_ITERATIONS, - initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, - ) - }, + .basicMarquee( + iterations = iterations, + initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, + ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index b21c3e4e44e1..6236fff87f63 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -196,11 +196,16 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern if (mJob == null) { mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(), WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> { - mInternetDetailsContentController.startActivityForDialog(intent); + mInternetDetailsContentController + .startActivityForDialog(intent); return null; }, () -> { wifiConnect(wifiEntry, view); return null; + }, intent -> { + mInternetDetailsContentController + .startActivityForDialogDismissDialogFirst(intent, view); + return null; }); } return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java index 945e051606b9..2497daebdd6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java @@ -784,6 +784,17 @@ public class InternetDetailsContentController implements AccessPointController.A mActivityStarter.startActivity(intent, false /* dismissShade */); } + // Closes the dialog first, as the WEP dialog is in a different process and can have weird + // interactions otherwise. + void startActivityForDialogDismissDialogFirst(Intent intent, View view) { + ActivityTransitionAnimator.Controller controller = + mDialogTransitionAnimator.createActivityTransitionController(view); + if (mCallback != null) { + mCallback.dismissDialog(); + } + mActivityStarter.startActivity(intent, false /* dismissShade */, controller); + } + void launchNetworkSetting(View view) { startActivity(getSettingsIntent(), view); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java index f4c77da674b0..742067a98057 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java @@ -24,6 +24,8 @@ import android.provider.Settings; import android.util.Log; import android.view.ScrollCaptureResponse; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; @@ -68,11 +70,15 @@ public class ScrollCaptureController { private final UiEventLogger mEventLogger; private final ScrollCaptureClient mClient; + @Nullable private Completer<LongScreenshot> mCaptureCompleter; + @Nullable private ListenableFuture<Session> mSessionFuture; private Session mSession; + @Nullable private ListenableFuture<CaptureResult> mTileFuture; + @Nullable private ListenableFuture<Void> mEndFuture; private String mWindowOwner; private volatile boolean mCancelled; @@ -148,8 +154,9 @@ public class ScrollCaptureController { } @Inject - ScrollCaptureController(Context context, @Background Executor bgExecutor, - ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) { + ScrollCaptureController(@NonNull Context context, @Background Executor bgExecutor, + @NonNull ScrollCaptureClient client, @NonNull ImageTileSet imageTileSet, + @NonNull UiEventLogger logger) { mContext = context; mBgExecutor = bgExecutor; mClient = client; @@ -214,7 +221,9 @@ public class ScrollCaptureController { } catch (InterruptedException | ExecutionException e) { // Failure to start, propagate to caller Log.e(TAG, "session start failed!"); - mCaptureCompleter.setException(e); + if (mCaptureCompleter != null) { + mCaptureCompleter.setException(e); + } mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner); } } @@ -235,7 +244,9 @@ public class ScrollCaptureController { Log.e(TAG, "requestTile cancelled"); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestTile failed!", e); - mCaptureCompleter.setException(e); + if (mCaptureCompleter != null) { + mCaptureCompleter.setException(e); + } } }, mBgExecutor); } @@ -350,7 +361,9 @@ public class ScrollCaptureController { } // Provide result to caller and complete the top-level future // Caller is responsible for releasing this resource (ImageReader/HardwareBuffers) - mCaptureCompleter.set(new LongScreenshot(mSession, mImageTileSet)); + if (mCaptureCompleter != null) { + mCaptureCompleter.set(new LongScreenshot(mSession, mImageTileSet)); + } }, mContext.getMainExecutor()); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index c800ab3d0bf2..913aacb53e12 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -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) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt index 9282e166f605..2238db505948 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt @@ -81,7 +81,7 @@ fun AODPromotedNotification( viewModelFactory: AODPromotedNotificationViewModel.Factory, modifier: Modifier = Modifier, ) { - if (!PromotedNotificationUiAod.isEnabled) { + if (!PromotedNotificationUi.isEnabled) { return } @@ -170,24 +170,35 @@ private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : Frame // This mirrors the logic in NotificationContentView.onMeasure. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - if (childCount < 1) { - return + if (childCount != 1) { + Log.wtf(TAG, "Should contain exactly one child.") + return super.onMeasure(widthMeasureSpec, heightMeasureSpec) } - val child = getChildAt(0) - val childLayoutHeight = child.layoutParams.height - val childHeightSpec = - if (childLayoutHeight >= 0) { - makeMeasureSpec(maxHeight.coerceAtMost(childLayoutHeight), EXACTLY) - } else { - makeMeasureSpec(maxHeight, AT_MOST) - } - measureChildWithMargins(child, widthMeasureSpec, 0, childHeightSpec, 0) - val childMeasuredHeight = child.measuredHeight + val horizPadding = paddingStart + paddingEnd + val vertPadding = paddingTop + paddingBottom + val ownWidthSize = MeasureSpec.getSize(widthMeasureSpec) val ownHeightMode = MeasureSpec.getMode(heightMeasureSpec) val ownHeightSize = MeasureSpec.getSize(heightMeasureSpec) + val availableHeight = + if (ownHeightMode != UNSPECIFIED) { + maxHeight.coerceAtMost(ownHeightSize) + } else { + maxHeight + } + + val child = getChildAt(0) + val childWidthSpec = makeMeasureSpec(ownWidthSize, EXACTLY) + val childHeightSpec = + child.layoutParams.height + .takeIf { it >= 0 } + ?.let { makeMeasureSpec(availableHeight.coerceAtMost(it), EXACTLY) } + ?: run { makeMeasureSpec(availableHeight, AT_MOST) } + measureChildWithMargins(child, childWidthSpec, horizPadding, childHeightSpec, vertPadding) + val childMeasuredHeight = child.measuredHeight + val ownMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec) val ownMeasuredHeight = if (ownHeightMode != UNSPECIFIED) { @@ -195,7 +206,6 @@ private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : Frame } else { childMeasuredHeight } - setMeasuredDimension(ownMeasuredWidth, ownMeasuredHeight) } } @@ -205,18 +215,22 @@ private val PromotedNotificationContentModel.layoutResource: Int? return if (notificationsRedesignTemplates()) { when (style) { Style.Base -> R.layout.notification_2025_template_expanded_base + Style.CollapsedBase -> R.layout.notification_2025_template_collapsed_base Style.BigPicture -> R.layout.notification_2025_template_expanded_big_picture Style.BigText -> R.layout.notification_2025_template_expanded_big_text Style.Call -> R.layout.notification_2025_template_expanded_call + Style.CollapsedCall -> R.layout.notification_2025_template_collapsed_call Style.Progress -> R.layout.notification_2025_template_expanded_progress Style.Ineligible -> null } } else { when (style) { Style.Base -> R.layout.notification_template_material_big_base + Style.CollapsedBase -> R.layout.notification_template_material_base Style.BigPicture -> R.layout.notification_template_material_big_picture Style.BigText -> R.layout.notification_template_material_big_text Style.Call -> R.layout.notification_template_material_big_call + Style.CollapsedCall -> R.layout.notification_template_material_call Style.Progress -> R.layout.notification_template_material_progress Style.Ineligible -> null } @@ -333,10 +347,12 @@ private class AODPromotedNotificationViewUpdater(root: View) { fun update(content: PromotedNotificationContentModel, audiblyAlertedIconVisible: Boolean) { when (content.style) { - Style.Base -> updateBase(content) + Style.Base -> updateBase(content, collapsed = false) + Style.CollapsedBase -> updateBase(content, collapsed = true) Style.BigPicture -> updateBigPictureStyle(content) Style.BigText -> updateBigTextStyle(content) - Style.Call -> updateCallStyle(content) + Style.Call -> updateCallStyle(content, collapsed = false) + Style.CollapsedCall -> updateCallStyle(content, collapsed = true) Style.Progress -> updateProgressStyle(content) Style.Ineligible -> {} } @@ -346,11 +362,15 @@ private class AODPromotedNotificationViewUpdater(root: View) { private fun updateBase( content: PromotedNotificationContentModel, + collapsed: Boolean, textView: ImageFloatingTextView? = text, ) { - updateHeader(content) + val headerTitleView = if (collapsed) title else null + updateHeader(content, titleView = headerTitleView, collapsed = collapsed) - updateTitle(title, content) + if (headerTitleView == null) { + updateTitle(title, content) + } updateText(textView, content) updateSmallIcon(icon, content) updateImageView(rightIcon, content.skeletonLargeIcon) @@ -358,21 +378,21 @@ private class AODPromotedNotificationViewUpdater(root: View) { } private fun updateBigPictureStyle(content: PromotedNotificationContentModel) { - updateBase(content) + updateBase(content, collapsed = false) } private fun updateBigTextStyle(content: PromotedNotificationContentModel) { - updateBase(content, textView = bigText) + updateBase(content, collapsed = false, textView = bigText) } - private fun updateCallStyle(content: PromotedNotificationContentModel) { - updateConversationHeader(content) + private fun updateCallStyle(content: PromotedNotificationContentModel, collapsed: Boolean) { + updateConversationHeader(content, collapsed = collapsed) updateText(text, content) } private fun updateProgressStyle(content: PromotedNotificationContentModel) { - updateBase(content) + updateBase(content, collapsed = false) updateNewProgressBar(content) } @@ -409,24 +429,35 @@ private class AODPromotedNotificationViewUpdater(root: View) { } } - private fun updateHeader(content: PromotedNotificationContentModel) { - updateAppName(content) + private fun updateHeader( + content: PromotedNotificationContentModel, + collapsed: Boolean, + titleView: TextView?, + ) { + val hasTitle = titleView != null && content.title != null + val hasSubText = content.subText != null + // the collapsed form doesn't show the app name unless there is no other text in the header + val appNameRequired = !hasTitle && !hasSubText + val hideAppName = (!appNameRequired && collapsed) + + updateAppName(content, forceHide = hideAppName) updateTextView(headerTextSecondary, content.subText) - // Not calling updateTitle(headerText, content) because the title is always a separate - // element in the expanded layout used for AOD RONs. + updateTitle(titleView, content) updateTimeAndChronometer(content) - updateHeaderDividers(content) + updateHeaderDividers(content, hideTitle = !hasTitle, hideAppName = hideAppName) updateTopLine(content) } - private fun updateHeaderDividers(content: PromotedNotificationContentModel) { - val hasAppName = content.appName != null + private fun updateHeaderDividers( + content: PromotedNotificationContentModel, + hideAppName: Boolean, + hideTitle: Boolean, + ) { + val hasAppName = content.appName != null && !hideAppName val hasSubText = content.subText != null - // Not setting hasHeader = content.title because the title is always a separate element in - // the expanded layout used for AOD RONs. - val hasHeader = false + val hasHeader = content.title != null && !hideTitle val hasTimeOrChronometer = content.time != null val hasTextBeforeSubText = hasAppName @@ -442,13 +473,17 @@ private class AODPromotedNotificationViewUpdater(root: View) { timeDivider?.isVisible = showDividerBeforeTime } - private fun updateConversationHeader(content: PromotedNotificationContentModel) { - updateAppName(content) + private fun updateConversationHeader( + content: PromotedNotificationContentModel, + collapsed: Boolean, + ) { + updateAppName(content, forceHide = collapsed) updateTimeAndChronometer(content) + updateImageView(verificationIcon, content.verificationIcon) updateTextView(verificationText, content.verificationText) - updateConversationHeaderDividers(content) + updateConversationHeaderDividers(content, hideTitle = true, hideAppName = collapsed) updateTopLine(content) @@ -456,11 +491,13 @@ private class AODPromotedNotificationViewUpdater(root: View) { updateTitle(conversationText, content) } - private fun updateConversationHeaderDividers(content: PromotedNotificationContentModel) { - // Not setting hasTitle = content.title because the title is always a separate element in - // the expanded layout used for AOD RONs. - val hasTitle = false - val hasAppName = content.appName != null + private fun updateConversationHeaderDividers( + content: PromotedNotificationContentModel, + hideTitle: Boolean, + hideAppName: Boolean, + ) { + val hasTitle = content.title != null && !hideTitle + val hasAppName = content.appName != null && !hideAppName val hasTimeOrChronometer = content.time != null val hasVerification = !content.verificationIcon.isNullOrEmpty() || content.verificationText != null @@ -478,8 +515,8 @@ private class AODPromotedNotificationViewUpdater(root: View) { verificationDivider?.isVisible = showDividerBeforeVerification } - private fun updateAppName(content: PromotedNotificationContentModel) { - updateTextView(appNameText, content.appName) + private fun updateAppName(content: PromotedNotificationContentModel, forceHide: Boolean) { + updateTextView(appNameText, content.appName?.takeUnless { forceHide }) } private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index d9bdfbc81145..9fe3ff4c4bce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -112,12 +112,13 @@ constructor( if (redactionType == REDACTION_TYPE_NONE) { privateVersion } else { - if (notification.publicVersion == null) { - privateVersion.toDefaultPublicVersion() - } else { - // TODO(b/400991304): implement extraction for [Notification.publicVersion] - privateVersion.toDefaultPublicVersion() - } + notification.publicVersion?.let { publicNotification -> + createAppDefinedPublicVersion( + privateModel = privateVersion, + publicNotification = publicNotification, + imageModelProvider = imageModelProvider, + ) + } ?: createDefaultPublicVersion(privateModel = privateVersion) } return PromotedNotificationContentModels( privateVersion = privateVersion, @@ -126,19 +127,59 @@ constructor( .also { logger.logExtractionSucceeded(entry, it) } } - private fun PromotedNotificationContentModel.toDefaultPublicVersion(): - PromotedNotificationContentModel = - PromotedNotificationContentModel.Builder(key = identity.key).let { - it.style = if (style == Style.Ineligible) Style.Ineligible else Style.Base - it.smallIcon = smallIcon - it.iconLevel = iconLevel - it.appName = appName - it.time = time - it.lastAudiblyAlertedMs = lastAudiblyAlertedMs - it.profileBadgeResId = profileBadgeResId - it.colors = colors - it.build() - } + private fun copyNonSensitiveFields( + privateModel: PromotedNotificationContentModel, + publicBuilder: PromotedNotificationContentModel.Builder, + ) { + publicBuilder.smallIcon = privateModel.smallIcon + publicBuilder.iconLevel = privateModel.iconLevel + publicBuilder.appName = privateModel.appName + publicBuilder.time = privateModel.time + publicBuilder.lastAudiblyAlertedMs = privateModel.lastAudiblyAlertedMs + publicBuilder.profileBadgeResId = privateModel.profileBadgeResId + publicBuilder.colors = privateModel.colors + } + + private fun createDefaultPublicVersion( + privateModel: PromotedNotificationContentModel + ): PromotedNotificationContentModel = + PromotedNotificationContentModel.Builder(key = privateModel.identity.key) + .also { + it.style = + if (privateModel.style == Style.Ineligible) Style.Ineligible else Style.Base + copyNonSensitiveFields(privateModel, it) + } + .build() + + private fun createAppDefinedPublicVersion( + privateModel: PromotedNotificationContentModel, + publicNotification: Notification, + imageModelProvider: ImageModelProvider, + ): PromotedNotificationContentModel = + PromotedNotificationContentModel.Builder(key = privateModel.identity.key) + .also { publicBuilder -> + val notificationStyle = publicNotification.notificationStyle + publicBuilder.style = + when { + privateModel.style == Style.Ineligible -> Style.Ineligible + notificationStyle == CallStyle::class.java -> Style.CollapsedCall + else -> Style.CollapsedBase + } + copyNonSensitiveFields(privateModel = privateModel, publicBuilder = publicBuilder) + publicBuilder.shortCriticalText = publicNotification.shortCriticalText() + publicBuilder.subText = publicNotification.subText() + // The standard public version is extracted as a collapsed notification, + // so avoid using bigTitle or bigText, and instead get the collapsed versions. + publicBuilder.title = publicNotification.title(notificationStyle, expanded = false) + publicBuilder.text = publicNotification.text() + publicBuilder.skeletonLargeIcon = + publicNotification.skeletonLargeIcon(imageModelProvider) + // Only CallStyle has styled content that shows in the collapsed version. + if (publicBuilder.style == Style.Call) { + extractCallStyleContent(publicNotification, publicBuilder, imageModelProvider) + } + } + .build() private fun extractPrivateContent( key: String, @@ -163,8 +204,8 @@ constructor( contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO - contentBuilder.title = notification.title(recoveredBuilder.style) - contentBuilder.text = notification.text(recoveredBuilder.style) + contentBuilder.title = notification.title(recoveredBuilder.style?.javaClass) + contentBuilder.text = notification.text(recoveredBuilder.style?.javaClass) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) contentBuilder.oldProgress = notification.oldProgress() @@ -191,12 +232,16 @@ constructor( private fun Notification.callPerson(): Person? = extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java) - private fun Notification.title(style: Notification.Style?): CharSequence? { - return when (style) { - is BigTextStyle, - is BigPictureStyle, - is InboxStyle -> bigTitle() - is CallStyle -> callPerson()?.name + private fun Notification.title( + styleClass: Class<out Notification.Style>?, + expanded: Boolean = true, + ): CharSequence? { + // bigTitle is only used in the expanded form of 3 styles. + return when (styleClass) { + BigTextStyle::class.java, + BigPictureStyle::class.java, + InboxStyle::class.java -> if (expanded) bigTitle() else null + CallStyle::class.java -> callPerson()?.name?.takeUnlessEmpty() else -> null } ?: title() } @@ -206,9 +251,9 @@ constructor( private fun Notification.bigText(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_BIG_TEXT) - private fun Notification.text(style: Notification.Style?): CharSequence? { - return when (style) { - is BigTextStyle -> bigText() + private fun Notification.text(styleClass: Class<out Notification.Style>?): CharSequence? { + return when (styleClass) { + BigTextStyle::class.java -> bigText() else -> null } ?: text() } @@ -293,17 +338,15 @@ constructor( null -> Style.Base is BigPictureStyle -> { - style.extractContent(contentBuilder) Style.BigPicture } is BigTextStyle -> { - style.extractContent(contentBuilder) Style.BigText } is CallStyle -> { - style.extractContent(notification, contentBuilder, imageModelProvider) + extractCallStyleContent(notification, contentBuilder, imageModelProvider) Style.Call } @@ -316,19 +359,7 @@ constructor( } } - private fun BigPictureStyle.extractContent( - contentBuilder: PromotedNotificationContentModel.Builder - ) { - // Big title is handled in resolveTitle, and big picture is unsupported. - } - - private fun BigTextStyle.extractContent( - contentBuilder: PromotedNotificationContentModel.Builder - ) { - // Big title and big text are handled in resolveTitle and resolveText. - } - - private fun CallStyle.extractContent( + private fun extractCallStyleContent( notification: Notification, contentBuilder: PromotedNotificationContentModel.Builder, imageModelProvider: ImageModelProvider, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt deleted file mode 100644 index 5c0991059dec..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.promoted - -import android.app.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -// NOTE: We're merging this flag with the `ui_rich_ongoing` flag. -// We'll replace all usages of this class with PromotedNotificationUi as a follow-up. - -/** Helper for reading or using the expanded ui rich ongoing flag state. */ -@Suppress("NOTHING_TO_INLINE") -object PromotedNotificationUiForceExpanded { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.uiRichOngoing() - - /** - * 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 enabled. This will throw an exception if - * the flag is not enabled to ensure that the refactor author catches issues in testing. - * Caution!! Using this check incorrectly will cause crashes in nextfood builds! - */ - @JvmStatic - @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) - inline fun unsafeAssertInNewMode() = - RefactorFlagUtils.unsafeAssertInNewMode(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/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 339a5bb29a34..ae6b2cc6cb1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -174,9 +174,11 @@ data class PromotedNotificationContentModel( /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */ enum class Style { Base, // style == null + CollapsedBase, // style == null BigPicture, BigText, Call, + CollapsedCall, Progress, Ineligible, } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 740391d7010e..3fed78674cf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -128,7 +128,6 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl; @@ -882,7 +881,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void updateLimitsForView(NotificationContentView layout) { final int maxExpandedHeight; - if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { + if (isPromotedOngoing()) { maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing; } else { maxExpandedHeight = mMaxExpandedHeight; @@ -1381,7 +1380,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren) { return mChildrenContainer.getIntrinsicHeight(); } - if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { + if (isPromotedOngoing()) { return getMaxExpandHeight(); } if (mExpandedWhenPinned) { @@ -3030,7 +3029,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren && !shouldShowPublic()) { return !mChildrenExpanded; } - if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { + if (isPromotedOngoing()) { return false; } return mEnableNonGroupedNotificationExpand && mExpandable; @@ -3141,7 +3140,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setUserLocked(boolean userLocked) { - if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) return; + if (isPromotedOngoing()) return; mUserLocked = userLocked; mPrivateLayout.setUserExpanding(userLocked); @@ -3411,7 +3410,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isExpanded(boolean allowOnKeyguard) { - if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { + if (isPromotedOngoing()) { return isPromotedNotificationExpanded(allowOnKeyguard); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt index 7bac17f4c227..215988471f00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt @@ -21,7 +21,7 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import androidx.annotation.VisibleForTesting import com.android.app.tracing.traceSection -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.row.shared.IconData import com.android.systemui.statusbar.notification.row.shared.ImageModel import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider @@ -80,7 +80,7 @@ interface RowImageInflater { companion object { @Suppress("NOTHING_TO_INLINE") @JvmStatic - inline fun featureFlagEnabled() = PromotedNotificationUiAod.isEnabled + inline fun featureFlagEnabled() = PromotedNotificationUi.isEnabled @JvmStatic fun newInstance(previousIndex: ImageModelIndex?, reinflating: Boolean): RowImageInflater = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java index d5e2e7eb3a9c..c4fe25031de3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java @@ -50,7 +50,6 @@ import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.ViewTransformationHelper; import com.android.systemui.statusbar.notification.ImageTransformState; import com.android.systemui.statusbar.notification.TransformState; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.HybridNotificationView; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -196,8 +195,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp } private void adjustTitleAndRightIconForPromotedOngoing() { - if (PromotedNotificationUiForceExpanded.isEnabled() && - mRow.isPromotedOngoing() && mRightIcon != null) { + if (mRow.isPromotedOngoing() && mRightIcon != null) { final int horizontalMargin; if (notificationsRedesignTemplates()) { horizontalMargin = mView.getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/AvalancheReplaceHunWhenCritical.kt index 69e27dcc2e6c..0ccd6064d9a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/AvalancheReplaceHunWhenCritical.kt @@ -14,19 +14,17 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.promoted +package com.android.systemui.statusbar.notification.shared -import android.app.Flags +import com.android.systemui.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils -// NOTE: We're merging this flag with the `ui_rich_ongoing` flag. -// We'll replace all usages of this class with PromotedNotificationUi as a follow-up. - -/** Helper for reading or using the promoted ongoing notifications AOD flag state. */ -object PromotedNotificationUiAod { +/** Helper for reading or using the avalanche replace Hun when critical flag state. */ +@Suppress("NOTHING_TO_INLINE") +object AvalancheReplaceHunWhenCritical { /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING + const val FLAG_NAME = Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL /** A token used for dependency declaration */ val token: FlagToken @@ -35,7 +33,7 @@ object PromotedNotificationUiAod { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.uiRichOngoing() + get() = Flags.avalancheReplaceHunWhenCritical() /** * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -48,16 +46,6 @@ object PromotedNotificationUiAod { /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is not enabled to ensure that the refactor author catches issues in testing. - * Caution!! Using this check incorrectly will cause crashes in nextfood builds! - */ - @JvmStatic - @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) - inline fun unsafeAssertInNewMode() = - RefactorFlagUtils.unsafeAssertInNewMode(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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index e5071d9c1e53..58df1703a925 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -29,7 +29,6 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.shared.NotificationBundleUi @@ -476,9 +475,7 @@ constructor( if (onLockscreen) { if ( view is ExpandableNotificationRow && - (canPeek || - (PromotedNotificationUiForceExpanded.isEnabled && - view.isPromotedOngoing)) + (canPeek || view.isPromotedOngoing) ) { height } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index eb72acc0dade..ca8fae875244 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -1483,6 +1483,44 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { verify(mLocalMediaManager, atLeastOnce()).connectDevice(outputMediaDevice); } + @Test + public void connectDeviceButton_remoteDevice_noButton() { + when(mMediaDevice1.getFeatures()).thenReturn( + ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); + mMediaSwitchingController.start(mCb); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList(); + + assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(0); + } + + @Test + public void connectDeviceButton_localDevice_hasButton() { + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); + mMediaSwitchingController.start(mCb); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList(); + + assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(1); + assertThat(resultList.get(resultList.size() - 1).getMediaItemType()).isEqualTo( + MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE); + } + + @Test + public void connectDeviceButton_localDeviceButtonDisabledByParam_noButton() { + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); + mMediaSwitchingController.start(mCb); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList( + false /* addConnectDeviceButton */); + + assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(0); + } + @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) @Test public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs() { @@ -1495,7 +1533,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); // Verify that there is initially one "Connect a device" button present. - assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1); + assertThat(getNumberOfConnectDeviceButtons( + mMediaSwitchingController.getMediaItemList())).isEqualTo(1); // Change the selected device, and verify that there is still one "Connect a device" button // present. @@ -1504,7 +1543,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1); + assertThat(getNumberOfConnectDeviceButtons( + mMediaSwitchingController.getMediaItemList())).isEqualTo(1); } @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) @@ -1523,7 +1563,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice(); // Verify that there is initially one "Connect a device" button present. - assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1); + assertThat(getNumberOfConnectDeviceButtons( + mMediaSwitchingController.getMediaItemList())).isEqualTo(1); // Change the selected device, and verify that there is still one "Connect a device" button // present. @@ -1532,7 +1573,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1); + assertThat(getNumberOfConnectDeviceButtons( + mMediaSwitchingController.getMediaItemList())).isEqualTo(1); } @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) @@ -1633,7 +1675,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); mMediaSwitchingController.start(mCb); reset(mCb); - mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.clearMediaItemList(); } @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) @@ -1691,9 +1733,9 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { assertThat(items.get(0).isFirstDeviceInGroup()).isTrue(); } - private int getNumberOfConnectDeviceButtons() { + private int getNumberOfConnectDeviceButtons(List<MediaItem> itemList) { int numberOfConnectDeviceButtons = 0; - for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { + for (MediaItem item : itemList) { if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE) { numberOfConnectDeviceButtons++; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java index f6edd49f142f..11a3670c20f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java @@ -58,7 +58,6 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { private MediaItem mMediaItem1; private MediaItem mMediaItem2; - private MediaItem mConnectNewDeviceMediaItem; private OutputMediaItemListProxy mOutputMediaItemListProxy; @Parameters(name = "{0}") @@ -83,7 +82,6 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { when(mMediaDevice4.getId()).thenReturn(DEVICE_ID_4); mMediaItem1 = MediaItem.createDeviceMediaItem(mMediaDevice1); mMediaItem2 = MediaItem.createDeviceMediaItem(mMediaDevice2); - mConnectNewDeviceMediaItem = MediaItem.createPairNewDeviceMediaItem(); mOutputMediaItemListProxy = new OutputMediaItemListProxy(mContext); } @@ -98,8 +96,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice2, mMediaDevice3), /* selectedDevices */ List.of(mMediaDevice3), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); // Check the output media items to be // * a media item with the selected mMediaDevice3 @@ -115,8 +112,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2), /* selectedDevices */ List.of(mMediaDevice3), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); // Check the output media items to be // * a media item with the selected route mMediaDevice3 @@ -136,8 +132,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice2), /* selectedDevices */ List.of(mMediaDevice1, mMediaDevice3), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); // Check the output media items to be // * a media item with the selected route mMediaDevice3 @@ -161,8 +156,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice2, mMediaDevice4, mMediaDevice3, mMediaDevice1), /* selectedDevices */ List.of(mMediaDevice1, mMediaDevice2, mMediaDevice3), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); if (Flags.enableOutputSwitcherDeviceGrouping()) { // When the device grouping is enabled, the order of selected devices are preserved: @@ -197,8 +191,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2), /* selectedDevices */ List.of(mMediaDevice2, mMediaDevice3), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); if (Flags.enableOutputSwitcherDeviceGrouping()) { // When the device grouping is enabled, the order of selected devices are preserved: @@ -233,8 +226,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice4), /* selectedDevices */ List.of(mMediaDevice3), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); if (Flags.enableOutputSwitcherDeviceGrouping()) { // When the device grouping is enabled, the order of selected devices are preserved: @@ -261,47 +253,6 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { } } - @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) - @Test - public void updateMediaDevices_withConnectNewDeviceMediaItem_shouldUpdateMediaItemList() { - assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); - - // Create the initial output media item list with a connect new device media item. - mOutputMediaItemListProxy.updateMediaDevices( - /* devices= */ List.of(mMediaDevice2, mMediaDevice3), - /* selectedDevices */ List.of(mMediaDevice3), - /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - mConnectNewDeviceMediaItem); - - // Check the output media items to be - // * a media item with the selected mMediaDevice3 - // * a group divider for suggested devices - // * a media item with the mMediaDevice2 - // * a connect new device media item - assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()) - .contains(mConnectNewDeviceMediaItem); - assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) - .containsExactly(mMediaDevice3, null, mMediaDevice2, null); - - // Update the output media item list without a connect new device media item. - mOutputMediaItemListProxy.updateMediaDevices( - /* devices= */ List.of(mMediaDevice2, mMediaDevice3), - /* selectedDevices */ List.of(mMediaDevice3), - /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); - - // Check the output media items to be - // * a media item with the selected mMediaDevice3 - // * a group divider for suggested devices - // * a media item with the mMediaDevice2 - assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()) - .doesNotContain(mConnectNewDeviceMediaItem); - assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) - .containsExactly(mMediaDevice3, null, mMediaDevice2); - } - @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) @Test public void clearAndAddAll_shouldUpdateMediaItemList() { @@ -325,8 +276,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice1), /* selectedDevices */ List.of(), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); mOutputMediaItemListProxy.clear(); @@ -354,8 +304,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase { /* devices= */ List.of(mMediaDevice1), /* selectedDevices */ List.of(), /* connectedMediaDevice= */ null, - /* needToHandleMutingExpectedDevice= */ false, - /* connectNewDeviceMediaItem= */ null); + /* needToHandleMutingExpectedDevice= */ false); assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); mOutputMediaItemListProxy.removeMutingExpectedDevices(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 7728f684f0f2..c21570928bde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -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, ) @@ -539,6 +545,18 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @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 { // Communal is open. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index eae23e70027b..e70ce53e74cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -83,7 +83,6 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; @@ -948,7 +947,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception { // GIVEN @@ -965,7 +964,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception { // GIVEN @@ -981,7 +980,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception { // GIVEN @@ -997,7 +996,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded() throws Exception { @@ -1035,7 +1034,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded() throws Exception { @@ -1053,7 +1052,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt index d0357603665d..4d9f20c0038f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt @@ -20,7 +20,7 @@ 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.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.row.shared.IconData import com.android.systemui.statusbar.notification.row.shared.ImageModel import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider.ImageSizeClass.SmallSquare @@ -31,7 +31,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -@EnableFlags(PromotedNotificationUiAod.FLAG_NAME) +@EnableFlags(PromotedNotificationUi.FLAG_NAME) class RowImageInflaterTest : SysuiTestCase() { private lateinit var rowImageInflater: RowImageInflater diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt new file mode 100644 index 000000000000..3452d097d3da --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt @@ -0,0 +1,28 @@ +/* + * 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 com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundScope +import com.android.systemui.power.domain.interactor.powerInteractor + +val Kosmos.userTouchActivityNotifier by + Kosmos.Fixture { + UserTouchActivityNotifier(backgroundScope, powerInteractor, USER_TOUCH_ACTIVITY_RATE_LIMIT) + } + +const val USER_TOUCH_ACTIVITY_RATE_LIMIT = 100 diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt index 23251d27cff9..90e23290e9e9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt @@ -22,9 +22,7 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarou import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.shade.domain.interactor.shadeModeInteractor import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor -import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory val Kosmos.notificationsShadeOverlayContentViewModel: @@ -34,9 +32,7 @@ val Kosmos.notificationsShadeOverlayContentViewModel: notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory, sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, - shadeModeInteractor = shadeModeInteractor, disableFlagsInteractor = disableFlagsInteractor, mediaCarouselInteractor = mediaCarouselInteractor, - activeNotificationsInteractor = activeNotificationsInteractor, ) } |