summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt3
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt7
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt181
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt151
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt244
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/AvalancheReplaceHunWhenCritical.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt4
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,
)
}