Merge "[Inline Reply] Add Flag for expand heads up on inline reply" into main
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 5956e2b..80764af 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1128,7 +1128,7 @@
private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
mGetPackagesForUidCache =
new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>(
- 32, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
+ 1024, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
@Override
public GetPackagesForUidResult recompute(Integer uid) {
try {
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index dea4e9c8..7948cec 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -32,7 +32,7 @@
public final class ChangeIdStateCache
extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
- private static final int MAX_ENTRIES = 64;
+ private static final int MAX_ENTRIES = 2048;
private static boolean sDisabled = false;
private volatile IPlatformCompat mPlatformCompat;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 282ede3..8c56a9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -11436,7 +11436,7 @@
private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
sApplicationInfoCache =
new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
- 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getApplicationInfo") {
@Override
public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -11537,7 +11537,7 @@
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 64, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getPackageInfo") {
@Override
public PackageInfo recompute(PackageInfoQuery query) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index a3111b3..c9d3dbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -324,6 +324,7 @@
enteringHasSameLetterbox = false
lastPostCommitFlingScale = SPRING_SCALE
gestureProgress = 0f
+ triggerBack = false
}
protected fun applyTransform(
@@ -499,10 +500,12 @@
}
override fun onBackCancelled() {
+ triggerBack = false
progressAnimator.onBackCancelled { finishAnimation() }
}
override fun onBackInvoked() {
+ triggerBack = true
progressAnimator.reset()
onGestureCommitted(progressAnimator.velocity)
}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 5f84862..acfe473 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -45,13 +45,6 @@
}
flag {
- name: "gnss_call_stop_before_set_position_mode"
- namespace: "location"
- description: "Flag for calling stop() before setPositionMode()"
- bug: "306874828"
-}
-
-flag {
name: "gnss_api_measurement_request_work_source"
namespace: "location"
description: "Flag for GnssMeasurementRequest WorkSource API"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 7d82920..1ce51af 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -27,6 +27,7 @@
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
+import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -44,81 +45,86 @@
private val lockSection: LockSection,
private val bottomAreaSection: BottomAreaSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
+ private val communalPopupSection: CommunalPopupSection,
) {
@Composable
fun SceneScope.Content(modifier: Modifier = Modifier) {
- Layout(
- modifier = modifier.fillMaxSize(),
- content = {
- Box(modifier = Modifier.fillMaxSize()) {
- with(ambientStatusBarSection) {
- AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+ CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) {
+ Layout(
+ modifier = Modifier.fillMaxSize(),
+ content = {
+ Box(modifier = Modifier.fillMaxSize()) {
+ with(communalPopupSection) { Popup() }
+ with(ambientStatusBarSection) {
+ AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+ }
+ CommunalHub(
+ viewModel = viewModel,
+ interactionHandler = interactionHandler,
+ dialogFactory = dialogFactory,
+ modifier = Modifier.element(Communal.Elements.Grid)
+ )
}
- CommunalHub(
- viewModel = viewModel,
- interactionHandler = interactionHandler,
- dialogFactory = dialogFactory,
- modifier = Modifier.element(Communal.Elements.Grid)
+ with(lockSection) {
+ LockIcon(
+ overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
+ modifier = Modifier.element(Communal.Elements.LockIcon)
+ )
+ }
+ with(bottomAreaSection) {
+ IndicationArea(
+ Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
+ )
+ }
+ }
+ ) { measurables, constraints ->
+ val communalGridMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val bottomAreaMeasurable = measurables[2]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val bottomAreaPlaceable =
+ bottomAreaMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+
+ val communalGridPlaceable =
+ communalGridMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ communalGridPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ bottomAreaPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - bottomAreaPlaceable.height,
)
}
- with(lockSection) {
- LockIcon(
- overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
- modifier = Modifier.element(Communal.Elements.LockIcon)
- )
- }
- with(bottomAreaSection) {
- IndicationArea(
- Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
- )
- }
- }
- ) { measurables, constraints ->
- val communalGridMeasurable = measurables[0]
- val lockIconMeasurable = measurables[1]
- val bottomAreaMeasurable = measurables[2]
-
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
-
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
-
- val bottomAreaPlaceable =
- bottomAreaMeasurable.measure(
- noMinConstraints.copy(
- maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
-
- val communalGridPlaceable =
- communalGridMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
-
- layout(constraints.maxWidth, constraints.maxHeight) {
- communalGridPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- bottomAreaPlaceable.place(
- x = 0,
- y = constraints.maxHeight - bottomAreaPlaceable.height,
- )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9ea435e..5efa5d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -25,7 +25,6 @@
import android.widget.RemoteViews
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
@@ -71,7 +70,6 @@
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.Edit
-import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
@@ -103,10 +101,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
-import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.key.onPreviewKeyEvent
-import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -126,13 +121,11 @@
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.compose.ui.window.Popup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.animation.Easings.Emphasized
@@ -151,7 +144,6 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
@@ -171,7 +163,6 @@
) {
val communalContent by
viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
- val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
@@ -213,43 +204,29 @@
}
.thenIf(!viewModel.isEditMode && !isEmptyState) {
Modifier.pointerInput(
- gridState,
- contentOffset,
- communalContent,
- gridCoordinates
- ) {
- detectLongPressGesture { offset ->
- // Deduct both grid offset relative to its container and content
- // offset.
- val adjustedOffset =
- gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
- }
- val index =
- adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
- // Display the button only when the gesture initiates from widgets,
- // the CTA tile, or an empty area on the screen. UMO/smartspace have
- // their own long-press handlers. To prevent user confusion, we
- // should
- // not display this button.
- if (
- index == null ||
- communalContent[index].isWidgetContent() ||
- communalContent[index] is
- CommunalContentModel.CtaTileInViewMode
- ) {
- viewModel.onShowCustomizeWidgetButton()
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ // Deduct both grid offset relative to its container and content
+ // offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
}
- val key =
- index?.let { keyAtIndexIfEditable(communalContent, index) }
+ val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
+ // Handle long-click on widgets and set the selected index
+ // correctly. We only handle widgets here because long click on
+ // empty spaces is handled by CommunalPopupSection.
+ if (key != null) {
+ viewModel.onLongClick()
viewModel.setSelectedKey(key)
}
}
- .onPreviewKeyEvent {
- onKeyEvent(viewModel)
- false
- }
- .motionEventSpy { onMotionEvent(viewModel) }
+ }
},
) {
AccessibilityContainer(viewModel) {
@@ -342,22 +319,6 @@
)
}
}
- if (currentPopup == PopupType.CtaTile) {
- PopupOnDismissCtaTile(viewModel::onHidePopup)
- }
-
- AnimatedVisibility(
- visible = currentPopup == PopupType.CustomizeWidgetButton,
- modifier = Modifier.fillMaxSize()
- ) {
- ButtonToEditWidgets(
- onClick = {
- viewModel.onHidePopup()
- viewModel.onOpenWidgetEditor(selectedKey.value)
- },
- onHide = { viewModel.onHidePopup() }
- )
- }
if (viewModel is CommunalViewModel && dialogFactory != null) {
val isEnableWidgetDialogShowing by
@@ -413,14 +374,6 @@
}
}
-private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
- viewModel.signalUserInteraction()
-}
-
-private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
- viewModel.signalUserInteraction()
-}
-
@Composable
private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
val colors = LocalAndroidColorScheme.current
@@ -821,107 +774,6 @@
}
@Composable
-private fun AnimatedVisibilityScope.ButtonToEditWidgets(
- onClick: () -> Unit,
- onHide: () -> Unit,
-) {
- Popup(
- alignment = Alignment.TopCenter,
- offset = IntOffset(0, 40),
- onDismissRequest = onHide,
- ) {
- val colors = LocalAndroidColorScheme.current
- Button(
- modifier =
- Modifier.height(56.dp)
- .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
- .animateEnterExit(
- enter =
- fadeIn(
- initialAlpha = 0f,
- animationSpec = tween(durationMillis = 83, easing = LinearEasing)
- ),
- exit =
- fadeOut(
- animationSpec =
- tween(
- durationMillis = 83,
- delayMillis = 167,
- easing = LinearEasing
- )
- )
- )
- .background(colors.secondary, RoundedCornerShape(50.dp)),
- onClick = onClick,
- ) {
- Row(
- modifier =
- Modifier.animateEnterExit(
- enter =
- fadeIn(
- animationSpec =
- tween(
- durationMillis = 167,
- delayMillis = 83,
- easing = LinearEasing
- )
- ),
- exit =
- fadeOut(
- animationSpec = tween(durationMillis = 167, easing = LinearEasing)
- )
- )
- ) {
- Icon(
- imageVector = Icons.Outlined.Widgets,
- contentDescription = stringResource(R.string.button_to_configure_widgets_text),
- tint = colors.onSecondary,
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.size(8.dp))
- Text(
- text = stringResource(R.string.button_to_configure_widgets_text),
- style = MaterialTheme.typography.titleSmall,
- color = colors.onSecondary
- )
- }
- }
- }
-}
-
-@Composable
-private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) {
- Popup(
- alignment = Alignment.TopCenter,
- offset = IntOffset(0, 40),
- onDismissRequest = onHidePopup
- ) {
- val colors = LocalAndroidColorScheme.current
- Row(
- modifier =
- Modifier.height(56.dp)
- .background(colors.secondary, RoundedCornerShape(50.dp))
- .padding(16.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Icon(
- imageVector = Icons.Outlined.TouchApp,
- contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
- tint = colors.onSecondary,
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.size(8.dp))
- Text(
- text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
- style = MaterialTheme.typography.titleSmall,
- color = colors.onSecondary,
- )
- }
- }
-}
-
-@Composable
private fun filledButtonColors(): ButtonColors {
val colors = LocalAndroidColorScheme.current
return ButtonDefaults.buttonColors(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
new file mode 100644
index 0000000..3707a87
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+@Composable
+fun CommunalTouchableSurface(
+ viewModel: CommunalViewModel,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit,
+) {
+
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ onLongClick = viewModel::onLongClick,
+ onClick = viewModel::onClick,
+ interactionSource = interactionSource,
+ indication = null,
+ )
+ .onPreviewKeyEvent {
+ viewModel.signalUserInteraction()
+ false
+ }
+ .motionEventSpy { viewModel.signalUserInteraction() }
+ ) {
+ content()
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
new file mode 100644
index 0000000..1ea73e1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.communal.ui.compose.section
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.TouchApp
+import androidx.compose.material.icons.outlined.Widgets
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.PopupType
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class CommunalPopupSection
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) {
+
+ @Composable
+ fun Popup() {
+ val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
+
+ if (currentPopup == PopupType.CtaTile) {
+ PopupOnDismissCtaTile(viewModel::onHidePopup)
+ }
+
+ AnimatedVisibility(
+ visible = currentPopup == PopupType.CustomizeWidgetButton,
+ modifier = Modifier.fillMaxSize()
+ ) {
+ ButtonToEditWidgets(
+ onClick = {
+ viewModel.onHidePopup()
+ viewModel.onOpenWidgetEditor()
+ },
+ onDismissRequest = {
+ viewModel.onHidePopup()
+ viewModel.setSelectedKey(null)
+ }
+ )
+ }
+ }
+
+ @Composable
+ private fun AnimatedVisibilityScope.ButtonToEditWidgets(
+ onClick: () -> Unit,
+ onDismissRequest: () -> Unit,
+ ) {
+ Popup(
+ alignment = Alignment.TopCenter,
+ offset = IntOffset(0, 40),
+ onDismissRequest = onDismissRequest,
+ ) {
+ val colors = LocalAndroidColorScheme.current
+ Button(
+ modifier =
+ Modifier.height(56.dp)
+ .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
+ .animateEnterExit(
+ enter =
+ fadeIn(
+ initialAlpha = 0f,
+ animationSpec =
+ tween(durationMillis = 83, easing = LinearEasing)
+ ),
+ exit =
+ fadeOut(
+ animationSpec =
+ tween(
+ durationMillis = 83,
+ delayMillis = 167,
+ easing = LinearEasing
+ )
+ )
+ )
+ .background(colors.secondary, RoundedCornerShape(50.dp)),
+ onClick = onClick,
+ ) {
+ Row(
+ modifier =
+ Modifier.animateEnterExit(
+ enter =
+ fadeIn(
+ animationSpec =
+ tween(
+ durationMillis = 167,
+ delayMillis = 83,
+ easing = LinearEasing
+ )
+ ),
+ exit =
+ fadeOut(
+ animationSpec =
+ tween(durationMillis = 167, easing = LinearEasing)
+ )
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription =
+ stringResource(R.string.button_to_configure_widgets_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.button_to_configure_widgets_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary
+ )
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun PopupOnDismissCtaTile(onDismissRequest: () -> Unit) {
+ Popup(
+ alignment = Alignment.TopCenter,
+ offset = IntOffset(0, 40),
+ onDismissRequest = onDismissRequest
+ ) {
+ val colors = LocalAndroidColorScheme.current
+ Row(
+ modifier =
+ Modifier.height(56.dp)
+ .background(colors.secondary, RoundedCornerShape(50.dp))
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.TouchApp,
+ contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary,
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 7a5f81c..51991de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -75,6 +75,7 @@
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -91,6 +92,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -154,13 +156,14 @@
context.resources,
kosmos.keyguardTransitionInteractor,
kosmos.keyguardInteractor,
+ mock<KeyguardIndicationController>(),
kosmos.communalSceneInteractor,
kosmos.communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
mediaHost,
- logcatLogBuffer("CommunalViewModelTest"),
+ logcatLogBuffer("CommunalViewModelTest")
)
}
@@ -358,7 +361,7 @@
val currentPopup by collectLastValue(underTest.currentPopup)
assertThat(currentPopup).isNull()
- underTest.onShowCustomizeWidgetButton()
+ underTest.onLongClick()
assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton)
advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS)
assertThat(currentPopup).isNull()
@@ -370,7 +373,7 @@
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
val currentPopup by collectLastValue(underTest.currentPopup)
- underTest.onShowCustomizeWidgetButton()
+ underTest.onLongClick()
assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton)
underTest.onHidePopup()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index cb4e2d3..16c7090 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -28,12 +28,14 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -56,7 +58,7 @@
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
- private val underTest = kosmos.notificationsShadeSceneViewModel
+ private val underTest by lazy { kosmos.notificationsShadeSceneViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -65,11 +67,23 @@
lockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
.isEqualTo(Scenes.Lockscreen)
}
@Test
+ fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -77,6 +91,33 @@
unlockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
+ .isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+ unlockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d43d50a..e01ffa6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -160,6 +161,37 @@
}
@Test
+ fun destinations_whenNotCustomizing_withPreviousSceneLockscreen_butLockscreenDisabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ qsFlexiglassAdapter.setCustomizing(false)
+ val destinations by collectLastValue(underTest.destinationScenes)
+
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val backScene by collectLastValue(sceneBackInteractor.backScene)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(backScene).isNull()
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ Back to UserActionResult(Scenes.Shade),
+ Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
+ Swipe(
+ fromSource = Edge.Bottom,
+ direction = SwipeDirection.Up,
+ ) to UserActionResult(SceneFamilies.Home)
+ )
+ )
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun destinations_whenNotCustomizing_authMethodSwipe_lockscreenNotDismissed() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
index ac67ac8..411a7a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
@@ -28,12 +28,14 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -56,7 +58,7 @@
private val sceneInteractor = kosmos.sceneInteractor
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- private val underTest = kosmos.quickSettingsShadeSceneViewModel
+ private val underTest by lazy { kosmos.quickSettingsShadeSceneViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -66,10 +68,23 @@
lockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
}
@Test
+ fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ lockDevice()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -78,6 +93,34 @@
unlockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ lockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ lockDevice()
+ unlockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 9edc3af..ae615dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -36,12 +36,15 @@
import com.android.systemui.classifier.falsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
@@ -1339,6 +1342,111 @@
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
+ @Test
+ fun switchToGone_whenKeyguardBecomesDisabled() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun switchToGone_whenKeyguardBecomesDisabled_whenOnShadeScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState(
+ initialSceneKey = Scenes.Shade,
+ )
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ underTest.start()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun doesNotSwitchToGone_whenKeyguardBecomesDisabled_whenInLockdownMode() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsUserInLockdown(true)
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun doesNotSwitchToGone_whenKeyguardBecomesDisabled_whenDeviceEntered() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
+ underTest.start()
+ sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ }
+
+ @Test
+ fun switchToLockscreen_whenKeyguardBecomesEnabled_afterHidingWhenDisabled() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun doesNotSwitchToLockscreen_whenKeyguardBecomesEnabled_ifAuthMethodBecameInsecure() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
@@ -1381,15 +1489,10 @@
isDeviceProvisioned: Boolean = true,
isInteractive: Boolean = true,
): MutableStateFlow<ObservableTransitionState> {
- if (authenticationMethod?.isSecure == true) {
- assert(isLockscreenEnabled) {
- "Lockscreen cannot be disabled while having a secure authentication method"
- }
- if (isDeviceUnlocked) {
- kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- }
+ if (isDeviceUnlocked) {
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
}
check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c53cdf8..a0295c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -139,6 +140,21 @@
}
@Test
+ fun upTransitionSceneKey_keyguardDisabled_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index cc90730..6ec6ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -129,7 +129,6 @@
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
open fun onOpenWidgetEditor(
- preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
) {}
@@ -146,7 +145,7 @@
open fun onReorderWidgetCancel() {}
/** Called as the user request to show the customize widget button. */
- open fun onShowCustomizeWidgetButton() {}
+ open fun onLongClick() {}
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 11247df..2043dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -40,6 +40,7 @@
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -76,9 +77,10 @@
@Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
+ private val keyguardIndicationController: KeyguardIndicationController,
communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
- private val communalSettingsInteractor: CommunalSettingsInteractor,
+ communalSettingsInteractor: CommunalSettingsInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -218,9 +220,8 @@
}
override fun onOpenWidgetEditor(
- preselectedKey: String?,
shouldOpenWidgetPickerOnStart: Boolean,
- ) = communalInteractor.showWidgetEditor(preselectedKey, shouldOpenWidgetPickerOnStart)
+ ) = communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
override fun onDismissCtaTile() {
scope.launch {
@@ -229,7 +230,11 @@
}
}
- override fun onShowCustomizeWidgetButton() {
+ fun onClick() {
+ keyguardIndicationController.showActionToUnlock()
+ }
+
+ override fun onLongClick() {
setCurrentPopupType(PopupType.CustomizeWidgetButton)
}
@@ -319,7 +324,7 @@
}
sealed class PopupType {
- object CtaTile : PopupType()
+ data object CtaTile : PopupType()
- object CustomizeWidgetButton : PopupType()
+ data object CustomizeWidgetButton : PopupType()
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 10d6881..4c7142b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -211,6 +211,9 @@
}
}
+ /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+ val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -259,6 +262,27 @@
return repository.isLockscreenEnabled()
}
+ fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+ return when (this) {
+ DeviceEntryRestrictionReason.UserLockdown -> true
+ DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+ // Add individual enum value instead of using "else" so new reasons are guaranteed
+ // to be added here at compile-time.
+ null -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+ DeviceEntryRestrictionReason.BouncerLockedOut -> false
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+ DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+ DeviceEntryRestrictionReason.SecurityTimeout -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+ DeviceEntryRestrictionReason.UnattendedUpdate -> false
+ DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+ }
+ }
+
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed. For example, completing a biometric
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index cdf3b06..888d047 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -39,6 +39,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -293,6 +294,15 @@
/** Sets whether the keyguard is enabled (see [isKeyguardEnabled]). */
fun setKeyguardEnabled(enabled: Boolean)
+
+ /** @see isShowKeyguardWhenReenabled */
+ fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean)
+
+ /**
+ * Returns `true` if the keyguard should be re-shown once it becomes re-enabled again; `false`
+ * otherwise.
+ */
+ fun isShowKeyguardWhenReenabled(): Boolean
}
/** Encapsulates application state for the keyguard. */
@@ -474,6 +484,8 @@
private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
+ private var isShowKeyguardWhenReenabled: Boolean = false
+
override fun setIsDozing(isDozing: Boolean) {
_isDozing.value = isDozing
}
@@ -692,6 +704,16 @@
_isKeyguardEnabled.value = enabled
}
+ override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+ SceneContainerFlag.assertInNewMode()
+ this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
+ }
+
+ override fun isShowKeyguardWhenReenabled(): Boolean {
+ SceneContainerFlag.assertInNewMode()
+ return isShowKeyguardWhenReenabled
+ }
+
private fun statusBarStateIntToObject(value: Int): StatusBarState {
return when (value) {
0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 2f40c99..506a18d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -232,5 +232,6 @@
val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
val TO_GONE_DURATION = DEFAULT_DURATION
val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index f5b12a2..07a2b04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -99,19 +99,21 @@
}
}
- scope.launch {
- keyguardRepository.isKeyguardEnabled
- .filterRelevantKeyguardStateAnd { enabled -> enabled }
- .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
- .filter { reshow -> reshow }
- .collect {
- startTransitionTo(
- KeyguardState.LOCKSCREEN,
- ownerReason =
- "Keyguard was re-enabled, and we weren't GONE when it " +
- "was originally disabled"
- )
- }
+ if (!SceneContainerFlag.isEnabled) {
+ scope.launch {
+ keyguardRepository.isKeyguardEnabled
+ .filterRelevantKeyguardStateAnd { enabled -> enabled }
+ .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
+ .filter { reshow -> reshow }
+ .collect {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason =
+ "Keyguard was re-enabled, and we weren't GONE when it " +
+ "was originally disabled"
+ )
+ }
+ }
}
} else {
scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f8208b3..f23b12f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -233,6 +233,7 @@
KeyguardState.DOZING -> TO_DOZING_DURATION
KeyguardState.GONE -> TO_GONE_DURATION
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -245,6 +246,7 @@
val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
val TO_LOCKSCREEN_DURATION = 450.milliseconds
+ val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 9cc0b3c..8d683f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -28,6 +29,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
@@ -68,6 +70,7 @@
*/
val showKeyguardWhenReenabled: Flow<Boolean> =
repository.isKeyguardEnabled
+ .onEach { SceneContainerFlag.assertInLegacyMode() }
// Whenever the keyguard is disabled...
.filter { enabled -> !enabled }
.sampleCombine(
@@ -103,4 +106,12 @@
fun notifyKeyguardEnabled(enabled: Boolean) {
repository.setKeyguardEnabled(enabled)
}
+
+ fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+ repository.setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled)
+ }
+
+ fun isShowKeyguardWhenReenabled(): Boolean {
+ return repository.isShowKeyguardWhenReenabled()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index ecdc21c..dc7a649 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
@@ -49,6 +50,7 @@
import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import dagger.Binds
import dagger.Module
@@ -253,4 +255,16 @@
abstract fun goneToGlanceableHub(
impl: GoneToGlanceableHubTransitionViewModel
): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun primaryBouncerToGlanceableHub(
+ impl: PrimaryBouncerToGlanceableHubTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun dozingToGlanceableHub(
+ impl: DozingToGlanceableHubTransitionViewModel
+ ): DeviceEntryIconTransition
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 215ac46..1c63235 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -73,14 +73,14 @@
AccessibilityNodeInfoCompat.ACTION_CLICK,
resources.getString(R.string.accessibility_enter_hint)
)
+
override fun onInitializeAccessibilityNodeInfo(
v: View,
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(v, info)
when (accessibilityHintType) {
- AccessibilityHintType.BOUNCER ->
- info.addAction(accessibilityBouncerHint)
+ AccessibilityHintType.BOUNCER -> info.addAction(accessibilityBouncerHint)
AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint)
AccessibilityHintType.NONE -> return
}
@@ -204,12 +204,12 @@
/* reversible */ false,
)
- // LockscreenFingerprint <=> LockscreenLocked
+ // LockscreenFingerprint => LockscreenLocked
animatedIconDrawable.addTransition(
R.id.locked_fp,
R.id.locked,
context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable,
- /* reversible */ true,
+ /* reversible */ false,
)
// LockscreenUnlocked <=> AodLocked
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..aee34e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class DozingToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow
+ .setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(DOZING, Scenes.Communal)
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(DOZING, GLANCEABLE_HUB))
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..754fb94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class PrimaryBouncerToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow
+ .setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(PRIMARY_BOUNCER, Scenes.Communal)
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, GLANCEABLE_HUB))
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index d0c7fbc..1f74716 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -22,6 +22,8 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,11 +31,19 @@
/** Models UI state and handles user input for the Notifications Shade scene. */
@SysUISingleton
-class NotificationsShadeSceneViewModel @Inject constructor() {
+class NotificationsShadeSceneViewModel
+@Inject
+constructor(
+ shadeInteractor: ShadeInteractor,
+) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
MutableStateFlow(
mapOf(
- Swipe.Up to SceneFamilies.Home,
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
Back to SceneFamilies.Home,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index bd748d5..e395d30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -26,6 +26,8 @@
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,6 +39,7 @@
class QuickSettingsShadeSceneViewModel
@Inject
constructor(
+ shadeInteractor: ShadeInteractor,
val overlayShadeViewModel: OverlayShadeViewModel,
val brightnessSliderViewModel: BrightnessSliderViewModel,
val tileGridViewModel: TileGridViewModel,
@@ -46,7 +49,11 @@
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
MutableStateFlow(
mapOf(
- Swipe.Up to SceneFamilies.Home,
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
Back to SceneFamilies.Home,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index 9e91b66..41a3c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import dagger.Binds
@@ -45,11 +46,13 @@
constructor(
@Application private val applicationScope: CoroutineScope,
deviceEntryInteractor: DeviceEntryInteractor,
+ keyguardEnabledInteractor: KeyguardEnabledInteractor,
) : SceneResolver {
override val targetFamily: SceneKey = SceneFamilies.Home
override val resolvedScene: StateFlow<SceneKey> =
combine(
+ keyguardEnabledInteractor.isKeyguardEnabled,
deviceEntryInteractor.canSwipeToEnter,
deviceEntryInteractor.isDeviceEntered,
deviceEntryInteractor.isUnlocked,
@@ -60,20 +63,23 @@
started = SharingStarted.Eagerly,
initialValue =
homeScene(
- deviceEntryInteractor.canSwipeToEnter.value,
- deviceEntryInteractor.isDeviceEntered.value,
- deviceEntryInteractor.isUnlocked.value,
+ isKeyguardEnabled = keyguardEnabledInteractor.isKeyguardEnabled.value,
+ canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value,
+ isDeviceEntered = deviceEntryInteractor.isDeviceEntered.value,
+ isUnlocked = deviceEntryInteractor.isUnlocked.value,
)
)
override fun includesScene(scene: SceneKey): Boolean = scene in homeScenes
private fun homeScene(
+ isKeyguardEnabled: Boolean,
canSwipeToEnter: Boolean?,
isDeviceEntered: Boolean,
isUnlocked: Boolean,
): SceneKey =
when {
+ !isKeyguardEnabled -> Scenes.Gone
canSwipeToEnter == true -> Scenes.Lockscreen
!isDeviceEntered -> Scenes.Lockscreen
!isUnlocked -> Scenes.Lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 218853d7..fbc0748 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -22,7 +22,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
-import com.android.internal.policy.IKeyguardStateCallback
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -39,6 +38,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
import com.android.systemui.model.SceneContainerPlugin
@@ -83,6 +83,7 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -124,12 +125,11 @@
private val sceneBackInteractor: SceneBackInteractor,
private val shadeSessionStorage: SessionStorage,
private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
+ private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
- private val keyguardStateCallbacks = mutableListOf<IKeyguardStateCallback>()
-
override fun start() {
if (SceneContainerFlag.isEnabled) {
sceneLogger.logFrameworkEnabled(isEnabled = true)
@@ -143,6 +143,7 @@
hydrateWindowController()
hydrateBackStack()
resetShadeSessions()
+ handleKeyguardEnabledness()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -653,6 +654,51 @@
}
}
+ private fun handleKeyguardEnabledness() {
+ // Automatically switches scenes when keyguard is enabled or disabled, as needed.
+ applicationScope.launch {
+ keyguardEnabledInteractor.isKeyguardEnabled
+ .sample(
+ combine(
+ deviceEntryInteractor.isInLockdown,
+ deviceEntryInteractor.isDeviceEntered,
+ ::Pair,
+ )
+ ) { isKeyguardEnabled, (isInLockdown, isDeviceEntered) ->
+ when {
+ !isKeyguardEnabled && !isInLockdown && !isDeviceEntered -> {
+ keyguardEnabledInteractor.setShowKeyguardWhenReenabled(true)
+ Scenes.Gone to "Keyguard became disabled"
+ }
+ isKeyguardEnabled &&
+ keyguardEnabledInteractor.isShowKeyguardWhenReenabled() -> {
+ keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false)
+ Scenes.Lockscreen to "Keyguard became enabled"
+ }
+ else -> null
+ }
+ }
+ .filterNotNull()
+ .collect { (targetScene, loggingReason) ->
+ switchToScene(targetScene, loggingReason)
+ }
+ }
+
+ // Clears the showKeyguardWhenReenabled if the auth method changes to an insecure one.
+ applicationScope.launch {
+ authenticationInteractor
+ .get()
+ .authenticationMethod
+ .map { it.isSecure }
+ .distinctUntilChanged()
+ .collect { isAuthenticationMethodSecure ->
+ if (!isAuthenticationMethodSecure) {
+ keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false)
+ }
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
toScene = targetSceneKey,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d382b7a..a62edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -25,6 +25,7 @@
import android.app.Activity;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Gravity;
@@ -32,7 +33,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
@@ -152,18 +155,27 @@
Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
- int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
+ int windowWidth = getWindowAvailableWidth();
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
boolean shouldBeFullWidth = getIntent()
.getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
- lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
+ lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - horizontalMargin * 2;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- lp.width = screenWidth - horizontalMargin * 2;
+ lp.width = windowWidth - horizontalMargin * 2;
}
frame.setLayoutParams(lp);
+ }
+ private int getWindowAvailableWidth() {
+ final WindowMetrics metrics = getWindowManager().getCurrentWindowMetrics();
+ // Gets all excluding insets
+ final WindowInsets windowInsets = metrics.getWindowInsets();
+ Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
+ | WindowInsets.Type.displayCutout());
+ int insetsWidth = insets.right + insets.left;
+ return metrics.getBounds().width() - insetsWidth;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d54e66e..af40cab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4474,6 +4474,7 @@
}
void goToFullShade(long delay) {
+ SceneContainerFlag.assertInLegacyMode();
mGoToFullShadeNeedsAnimation = true;
mGoToFullShadeDelay = delay;
mNeedsAnimation = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d984685..bf53ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1177,6 +1177,7 @@
}
public void goToFullShade(long delay) {
+ SceneContainerFlag.assertInLegacyMode();
mView.goToFullShade(delay);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index dca531a..5bae6ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -133,6 +133,8 @@
override val topClippingBounds = MutableStateFlow<Int?>(null)
+ private var isShowKeyguardWhenReenabled: Boolean = false
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -268,6 +270,14 @@
fun setIsEncryptedOrLockdown(value: Boolean) {
_isEncryptedOrLockdown.value = value
}
+
+ override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+ this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
+ }
+
+ override fun isShowKeyguardWhenReenabled(): Boolean {
+ return isShowKeyguardWhenReenabled
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
index 6be1939..ae33aea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -20,6 +20,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -39,6 +40,7 @@
HomeSceneFamilyResolver(
applicationScope = applicationCoroutineScope,
deviceEntryInteractor = deviceEntryInteractor,
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 8b887d3..a661ab6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
import com.android.systemui.kosmos.Kosmos
@@ -71,5 +72,6 @@
sceneBackInteractor = sceneBackInteractor,
shadeSessionStorage = shadeSessionStorage,
windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 4813794..b9918f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -30,7 +30,7 @@
@SysUISingleton
class FakeShadeRepository @Inject constructor() : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
- override val qsExpansion = _qsExpansion
+ @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion = _qsExpansion
private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
@@ -59,12 +59,15 @@
private val _legacyIsQsExpanded = MutableStateFlow(false)
@Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+ @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
override val legacyLockscreenShadeTracking = MutableStateFlow(false)
private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
- override val isDualShadeAlignedToBottom = false
+ private var _isDualShadeAlignedToBottom = false
+ override val isDualShadeAlignedToBottom
+ get() = _isDualShadeAlignedToBottom
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
@@ -139,8 +142,12 @@
_legacyShadeExpansion.value = expandedFraction
}
- override fun setShadeMode(shadeMode: ShadeMode) {
- _shadeMode.value = shadeMode
+ override fun setShadeMode(mode: ShadeMode) {
+ _shadeMode.value = mode
+ }
+
+ fun setDualShadeAlignedToBottom(isAlignedToBottom: Boolean) {
+ _isDualShadeAlignedToBottom = isAlignedToBottom
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
index 1ca3509..72a80d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
- Kosmos.Fixture { NotificationsShadeSceneViewModel() }
+ Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index 4d81ea1..8adb26f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
Kosmos.Fixture {
@@ -31,5 +32,6 @@
tileGridViewModel = tileGridViewModel,
editModeViewModel = editModeViewModel,
qsSceneAdapter = qsSceneAdapter,
+ shadeInteractor = shadeInteractor,
)
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6d1983e..c89992d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11698,7 +11698,7 @@
static final int LOG_NB_EVENTS_VOLUME = 100;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
static final int LOG_NB_EVENTS_SPATIAL = 30;
- static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
+ static final int LOG_NB_EVENTS_SOUND_DOSE = 50;
static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 749044e..8ea28be 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -577,6 +577,7 @@
static final int DOSE_REPEAT_5X = 2;
static final int DOSE_ACCUMULATION_START = 3;
static final int LOWER_VOLUME_TO_RS1 = 4;
+ static final int UPDATE_ABS_VOLUME_ATTENUATION = 5;
final int mEventType;
final float mFloatValue;
@@ -608,6 +609,10 @@
return new SoundDoseEvent(LOWER_VOLUME_TO_RS1, 0 /*ignored*/, 0 /*ignored*/);
}
+ static SoundDoseEvent getAbsVolumeAttenuationEvent(float attenuation, int device) {
+ return new SoundDoseEvent(UPDATE_ABS_VOLUME_ATTENUATION, attenuation, device);
+ }
+
@Override
public String eventToString() {
switch (mEventType) {
@@ -623,6 +628,10 @@
return "CSD accumulating: RS2 entered";
case LOWER_VOLUME_TO_RS1:
return "CSD lowering volume to RS1";
+ case UPDATE_ABS_VOLUME_ATTENUATION:
+ return String.format(java.util.Locale.US,
+ "Updating CSD absolute volume attenuation on device %d with %.2f dB ",
+ mLongValue, mFloatValue);
}
return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index e28ae95..5c74304 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -39,6 +39,7 @@
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.SoundDoseRecord;
+import android.media.VolumeInfo;
import android.os.Binder;
import android.os.Message;
import android.os.RemoteException;
@@ -895,6 +896,8 @@
try {
if (!isAbsoluteVolume) {
+ mLogger.enqueue(
+ SoundDoseEvent.getAbsVolumeAttenuationEvent(/*attenuation=*/0.f, device));
// remove any possible previous attenuation
soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
@@ -903,10 +906,11 @@
if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
&& safeDevicesContains(device)) {
- soundDose.updateAttenuation(
- -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
- (newIndex + 5) / 10,
- device), device);
+ float attenuationDb = -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
+ (newIndex + 5) / 10, device);
+ mLogger.enqueue(
+ SoundDoseEvent.getAbsVolumeAttenuationEvent(attenuationDb, device));
+ soundDose.updateAttenuation(attenuationDb, device);
}
} catch (RemoteException e) {
Log.e(TAG, "Could not apply the attenuation for MEL calculation with volume index "
@@ -1313,22 +1317,30 @@
/** Called when handling MSG_LOWER_VOLUME_TO_RS1 */
private void onLowerVolumeToRs1() {
- mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
final ArrayList<AudioDeviceAttributes> devices = mAudioService.getDevicesForAttributesInt(
- new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(), true);
- final int nativeDeviceType;
- final AudioDeviceAttributes ada;
- if (!devices.isEmpty()) {
- ada = devices.get(0);
- nativeDeviceType = ada.getInternalType();
- } else {
- nativeDeviceType = AudioSystem.DEVICE_OUT_USB_HEADSET;
- ada = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_USB_HEADSET, "");
+ new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_MEDIA).build(), /*forVolume=*/true);
+ if (devices.isEmpty()) {
+ Log.e(TAG, "Cannot lower the volume to RS1, no devices registered for USAGE_MEDIA");
+ return;
}
- final int index = safeMediaVolumeIndex(nativeDeviceType);
- mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index / 10, /*flags*/ 0, ada,
- mContext.getOpPackageName(), /*attributionTag=*/null,
- true /*canChangeMuteAndUpdateController*/);
+ final AudioDeviceAttributes ada = devices.get(0);
+ final int nativeDeviceType = ada.getInternalType();
+ final int index = safeMediaVolumeIndex(nativeDeviceType) / 10;
+ final VolumeInfo curVolume = mAudioService.getDeviceVolume(
+ new VolumeInfo.Builder(STREAM_MUSIC).build(), ada,
+ /*callingPackage=*/"sounddosehelper");
+
+ if (index < curVolume.getVolumeIndex()) {
+ mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
+ mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index, /*flags*/ 0, ada,
+ mContext.getOpPackageName(), /*attributionTag=*/null,
+ /*canChangeMuteAndUpdateController=*/true);
+ } else {
+ Log.i(TAG, "The current volume " + curVolume.getVolumeIndex()
+ + " for device type " + nativeDeviceType
+ + " is already smaller or equal to the safe index volume " + index);
+ }
}
// StreamVolumeCommand contains the information needed to defer the process of
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 286e789..1938150 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -68,7 +68,6 @@
import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.LocationResult.BadLocationException;
-import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -1050,22 +1049,10 @@
stopBatching();
if (mStarted && mGnssNative.getCapabilities().hasScheduling()) {
- if (Flags.gnssCallStopBeforeSetPositionMode()) {
- GnssPositionMode positionMode = new GnssPositionMode(mPositionMode,
- GNSS_POSITION_RECURRENCE_PERIODIC, mFixInterval,
- /* preferredAccuracy= */ 0,
- /* preferredTime= */ 0,
- mProviderRequest.isLowPower());
- if (!positionMode.equals(mLastPositionMode)) {
- stopNavigating();
- startNavigating();
- }
- } else {
- // change period and/or lowPowerMode
- if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
- mFixInterval, mProviderRequest.isLowPower())) {
- Log.e(TAG, "set_position_mode failed in updateRequirements");
- }
+ // change period and/or lowPowerMode
+ if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+ mFixInterval, mProviderRequest.isLowPower())) {
+ Log.e(TAG, "set_position_mode failed in updateRequirements");
}
} else if (!mStarted) {
// start GPS
@@ -1248,32 +1235,11 @@
}
int interval = mGnssNative.getCapabilities().hasScheduling() ? mFixInterval : 1000;
-
- if (Flags.gnssCallStopBeforeSetPositionMode()) {
- boolean success = mGnssNative.setPositionMode(mPositionMode,
- GNSS_POSITION_RECURRENCE_PERIODIC, interval,
- /* preferredAccuracy= */ 0,
- /* preferredTime= */ 0,
- mProviderRequest.isLowPower());
- if (success) {
- mLastPositionMode = new GnssPositionMode(mPositionMode,
- GNSS_POSITION_RECURRENCE_PERIODIC, interval,
- /* preferredAccuracy= */ 0,
- /* preferredTime= */ 0,
- mProviderRequest.isLowPower());
- } else {
- mLastPositionMode = null;
- setStarted(false);
- Log.e(TAG, "set_position_mode failed in startNavigating()");
- return;
- }
- } else {
- if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
- interval, mProviderRequest.isLowPower())) {
- setStarted(false);
- Log.e(TAG, "set_position_mode failed in startNavigating()");
- return;
- }
+ if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+ interval, mProviderRequest.isLowPower())) {
+ setStarted(false);
+ Log.e(TAG, "set_position_mode failed in startNavigating()");
+ return;
}
if (!mGnssNative.start()) {
setStarted(false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
deleted file mode 100644
index c5e6824..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.gnss;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.location.GnssCapabilities;
-import android.location.LocationManager;
-import android.location.LocationManagerInternal;
-import android.location.flags.Flags;
-import android.location.provider.ProviderRequest;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.location.gnss.hal.FakeGnssHal;
-import com.android.server.location.gnss.hal.GnssNative;
-import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.TestInjector;
-import com.android.server.timedetector.TimeDetectorInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.quality.Strictness;
-
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-@Presubmit
-@androidx.test.filters.SmallTest
-@RunWith(AndroidJUnit4.class)
-public class GnssLocationProviderTest {
-
- @Rule
- public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .setStrictness(Strictness.WARN)
- .mockStatic(Settings.Global.class)
- .build();
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- private @Mock Context mContext;
- private @Mock LocationManagerInternal mLocationManagerInternal;
- private @Mock LocationManager mLocationManager;
- private @Mock TimeDetectorInternal mTimeDetectorInternal;
- private @Mock GnssConfiguration mMockConfiguration;
- private @Mock GnssMetrics mGnssMetrics;
- private @Mock PowerManager mPowerManager;
- private @Mock TelephonyManager mTelephonyManager;
- private @Mock AppOpsManager mAppOpsManager;
- private @Mock AlarmManager mAlarmManager;
- private @Mock PowerManager.WakeLock mWakeLock;
- private @Mock ContentResolver mContentResolver;
- private @Mock UserManager mUserManager;
- private @Mock UserHandle mUserHandle;
- private Set<UserHandle> mUserHandleSet = new HashSet<>();
-
- private GnssNative mGnssNative;
-
- private GnssLocationProvider mTestProvider;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- doReturn("mypackage").when(mContext).getPackageName();
- doReturn("attribution").when(mContext).getAttributionTag();
- doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
- doReturn(mPowerManager).when(mContext).getSystemService("power");
- doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
- doReturn(mAlarmManager).when(mContext).getSystemService(Context.ALARM_SERVICE);
- doReturn(mLocationManager).when(mContext).getSystemService(LocationManager.class);
- doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
- mUserHandleSet.add(mUserHandle);
- doReturn(true).when(mLocationManager).isLocationEnabledForUser(eq(mUserHandle));
- doReturn(mUserHandleSet).when(mUserManager).getVisibleUsers();
- doReturn(mContentResolver).when(mContext).getContentResolver();
- doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
- LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
- LocalServices.addService(TimeDetectorInternal.class, mTimeDetectorInternal);
- FakeGnssHal fakeGnssHal = new FakeGnssHal();
- GnssNative.setGnssHalForTest(fakeGnssHal);
- Injector injector = new TestInjector(mContext);
- mGnssNative = spy(Objects.requireNonNull(
- GnssNative.create(injector, mMockConfiguration)));
- doReturn(true).when(mGnssNative).init();
- GnssCapabilities gnssCapabilities = new GnssCapabilities.Builder().setHasScheduling(
- true).build();
- doReturn(gnssCapabilities).when(mGnssNative).getCapabilities();
-
- mTestProvider = new GnssLocationProvider(mContext, mGnssNative, mGnssMetrics);
- mGnssNative.register();
- }
-
- @After
- public void tearDown() {
- LocalServices.removeServiceForTest(LocationManagerInternal.class);
- LocalServices.removeServiceForTest(TimeDetectorInternal.class);
- }
-
- @Test
- public void testStartNavigating() {
- ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
- 0).build();
-
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).start();
- }
-
- @Test
- public void testUpdateRequirements_sameRequest() {
- mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
- ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
- 0).build();
-
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).start();
-
- // set the same request
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative, never()).stop();
- verify(mGnssNative, times(1)).start();
- }
-
- @Test
- public void testUpdateRequirements_differentRequest() {
- mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
- ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
- 0).build();
-
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).start();
-
- // set a different request
- providerRequest = new ProviderRequest.Builder().setIntervalMillis(2000).build();
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).stop();
- verify(mGnssNative, times(2)).start();
- }
-}