diff options
37 files changed, 816 insertions, 289 deletions
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 16dcf2ad7e45..8d20e46c7df8 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -483,6 +484,19 @@ public class ActivityTaskManager { } /** + * @return Whether the app could be universal resizeable (assuming it's on a large screen and + * ignoring possible overrides) + * @hide + */ + public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) { + try { + return getService().canBeUniversalResizeable(appInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Detaches the navigation bar from the app it was attached to during a transition. * @hide */ diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index c6f62a21641d..4b1afa517122 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -158,6 +158,12 @@ interface IActivityTaskManager { void reportAssistContextExtras(in IBinder assistToken, in Bundle extras, in AssistStructure structure, in AssistContent content, in Uri referrer); + /** + * @return whether the app could be universal resizeable (assuming it's on a large screen and + * ignoring possible overrides) + */ + boolean canBeUniversalResizeable(in ApplicationInfo appInfo); + void setFocusedRootTask(int taskId); ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo(); Rect getTaskBounds(int taskId); diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 2ca011bfe000..0a1e3b9495a0 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -111,7 +111,7 @@ public class GroupedTaskInfo implements Parcelable { * Create new for a pair of tasks in split screen */ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, - @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index dbefcd7cf45f..7cf83135c237 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1942,16 +1942,6 @@ flag { } flag { - name: "stabilize_heads_up_group" - namespace: "systemui" - description: "Stabilize heads up groups in VisualStabilityCoordinator" - bug: "381864715" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "magnetic_notification_horizontal_swipe" namespace: "systemui" description: "Add support for magnetic behavior on horizontal notification swipes." @@ -1990,4 +1980,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "shade_launch_accessibility" + namespace: "systemui" + description: "Intercept accessibility focus events for the Shade during launch animations to avoid stray TalkBack events." + bug: "379222226" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index bdd0da9ce4a4..4e10ff689b19 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -67,11 +67,10 @@ import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.compose.modifiers.sysuiResTag -import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig -import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R +import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState import kotlin.math.round import kotlinx.coroutines.flow.distinctUntilChanged @@ -103,6 +102,10 @@ fun VolumeSlider( } val value by valueState(state) + val interactionSource = remember { MutableInteractionSource() } + val hapticsViewModel: SliderHapticsViewModel? = + setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory) + Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -127,8 +130,14 @@ fun VolumeSlider( Slider( value = value, valueRange = state.valueRange, - onValueChange = onValueChange, - onValueChangeFinished = onValueChangeFinished, + onValueChange = { newValue -> + hapticsViewModel?.addVelocityDataPoint(newValue) + onValueChange(newValue) + }, + onValueChangeFinished = { + hapticsViewModel?.onValueChangeEnded() + onValueChangeFinished?.invoke() + }, enabled = state.isEnabled, modifier = Modifier.height(40.dp) @@ -210,41 +219,8 @@ private fun LegacyVolumeSlider( ) { val value by valueState(state) val interactionSource = remember { MutableInteractionSource() } - val sliderStepSize = 1f / (state.valueRange.endInclusive - state.valueRange.start) val hapticsViewModel: SliderHapticsViewModel? = - hapticsViewModelFactory?.let { - rememberViewModel(traceName = "SliderHapticsViewModel") { - it.create( - interactionSource, - state.valueRange, - Orientation.Horizontal, - SliderHapticFeedbackConfig( - lowerBookendScale = 0.2f, - progressBasedDragMinScale = 0.2f, - progressBasedDragMaxScale = 0.5f, - deltaProgressForDragThreshold = 0f, - additionalVelocityMaxBump = 0.2f, - maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */ - sliderStepSize = sliderStepSize, - ), - SeekableSliderTrackerConfig( - lowerBookendThreshold = 0f, - upperBookendThreshold = 1f, - ), - ) - } - } - var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } - LaunchedEffect(value) { - snapshotFlow { value } - .map { round(it) } - .filter { it != lastDiscreteStep } - .distinctUntilChanged() - .collect { discreteStep -> - lastDiscreteStep = discreteStep - hapticsViewModel?.onValueChange(discreteStep) - } - } + setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory) PlatformSlider( modifier = @@ -357,3 +333,36 @@ private fun SliderIcon( content = { Icon(modifier = Modifier.size(24.dp), icon = icon) }, ) } + +@Composable +fun setUpHapticsViewModel( + value: Float, + valueRange: ClosedFloatingPointRange<Float>, + interactionSource: MutableInteractionSource, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, +): SliderHapticsViewModel? { + return hapticsViewModelFactory?.let { + rememberViewModel(traceName = "SliderHapticsViewModel") { + it.create( + interactionSource, + valueRange, + Orientation.Horizontal, + VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(valueRange), + VolumeHapticsConfigsProvider.seekableSliderTrackerConfig, + ) + } + .also { hapticsViewModel -> + var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } + LaunchedEffect(value) { + snapshotFlow { value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel.onValueChange(discreteStep) + } + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index ee9cb141a700..555c717e1e65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -20,7 +20,7 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer -import android.view.MotionEvent +import android.view.accessibility.AccessibilityEvent import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -45,7 +45,10 @@ import com.android.systemui.res.R import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.data.repository.ShadeAnimationRepository +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -185,6 +188,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { notificationShadeDepthController, underTest, shadeViewController, + ShadeAnimationInteractorLegacyImpl( + ShadeAnimationRepository(), + ShadeRepositoryImpl(testScope), + ), panelExpansionInteractor, ShadeExpansionStateManager(), notificationStackScrollLayoutController, @@ -259,6 +266,20 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config)) } + @Test + @EnableFlags(AConfigFlags.FLAG_SHADE_LAUNCH_ACCESSIBILITY) + fun requestSendAccessibilityEvent_duringLaunchAnimation_blocksFocusEvent() { + underTest.setAnimatingContentLaunch(true) + + assertThat( + underTest.requestSendAccessibilityEvent( + underTest.getChildAt(0), + AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED), + ) + ) + .isFalse() + } + private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 7b120947b1d6..2aa1efaa429f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -107,7 +107,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return SceneContainerFlagParameterizationKt - .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP)); + .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2)); } private VisualStabilityCoordinator mCoordinator; diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index c5f468e731f5..2628f4991b49 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -18,14 +18,10 @@ android:layout_height="match_parent" android:maxHeight="@dimen/volume_dialog_slider_height"> - <com.google.android.material.slider.Slider + <androidx.compose.ui.platform.ComposeView android:id="@+id/volume_dialog_slider" - style="@style/SystemUI.Material3.Slider.Volume" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" - android:layout_marginTop="-20dp" - android:layout_marginBottom="-20dp" - android:orientation="vertical" - android:theme="@style/Theme.Material3.DayNight" /> + android:orientation="vertical" /> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b0d9bed05e27..c95c6dd89353 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -565,16 +565,6 @@ <item name="android:windowNoTitle">true</item> </style> - <style name="SystemUI.Material3.Slider.Volume"> - <item name="trackHeight">40dp</item> - <item name="thumbHeight">52dp</item> - <item name="trackCornerSize">12dp</item> - <item name="trackInsideCornerSize">2dp</item> - <item name="trackStopIndicatorSize">6dp</item> - <item name="trackIconSize">20dp</item> - <item name="labelBehavior">gone</item> - </style> - <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> <item name="labelStyle">@style/Widget.Material3.Slider.Label</item> <item name="thumbColor">@color/thumb_color</item> diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 48bbb0407ee3..48e374746bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -17,6 +17,7 @@ package com.android.systemui.shade; import static android.os.Trace.TRACE_TAG_APP; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; import static com.android.systemui.Flags.enableViewCaptureTracing; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -49,10 +50,12 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsetsController; +import android.view.accessibility.AccessibilityEvent; import com.android.app.viewcapture.ViewCaptureFactory; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; +import com.android.systemui.Flags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.statusbar.phone.ConfigurationForwarder; @@ -77,6 +80,8 @@ public class NotificationShadeWindowView extends WindowRootView { private SafeCloseable mViewCaptureCloseable; + private boolean mAnimatingContentLaunch = false; + public NotificationShadeWindowView(Context context, AttributeSet attrs) { super(context, attrs); setMotionEventSplittingEnabled(false); @@ -188,6 +193,22 @@ public class NotificationShadeWindowView extends WindowRootView { } } + @Override + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (Flags.shadeLaunchAccessibility() && mAnimatingContentLaunch + && event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) { + // Block accessibility focus events during launch animations to avoid stray TalkBack + // announcements. + return false; + } + + return super.requestSendAccessibilityEvent(child, event); + } + + public void setAnimatingContentLaunch(boolean animating) { + mAnimatingContentLaunch = animating; + } + public void setConfigurationForwarder(ConfigurationForwarder configurationForwarder) { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); mConfigurationForwarder = configurationForwarder; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index e5dcd2338b9d..ffec8f284c48 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,10 +16,12 @@ package com.android.systemui.shade; +import static com.android.systemui.Flags.shadeLaunchAccessibility; import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING; import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.app.StatusBarManager; import android.util.Log; @@ -59,6 +61,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.BlurUtils; @@ -85,6 +88,7 @@ import com.android.systemui.window.ui.WindowRootViewBinder; import com.android.systemui.window.ui.viewmodel.WindowRootViewModel; import kotlinx.coroutines.ExperimentalCoroutinesApi; +import kotlinx.coroutines.flow.Flow; import java.io.PrintWriter; import java.util.Optional; @@ -174,6 +178,7 @@ public class NotificationShadeWindowViewController implements Dumpable { NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, ShadeViewController shadeViewController, + ShadeAnimationInteractor shadeAnimationInteractor, PanelExpansionInteractor panelExpansionInteractor, ShadeExpansionStateManager shadeExpansionStateManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, @@ -238,9 +243,17 @@ public class NotificationShadeWindowViewController implements Dumpable { collectFlow(mView, keyguardTransitionInteractor.transition( Edge.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition); + Flow<Boolean> isLaunchAnimationRunning = + shadeLaunchAccessibility() + ? combineFlows( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), + shadeAnimationInteractor.isLaunchingActivity(), + (notificationLaunching, shadeLaunching) -> + notificationLaunching || shadeLaunching) + : notificationLaunchAnimationInteractor.isLaunchAnimationRunning(); collectFlow( mView, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), + isLaunchAnimationRunning, this::setExpandAnimationRunning); if (QSComposeFragment.isEnabled()) { collectFlow(mView, @@ -726,9 +739,17 @@ public class NotificationShadeWindowViewController implements Dumpable { if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "Setting mExpandAnimationRunning=" + running); } + if (running) { mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000; } + + if (shadeLaunchAccessibility()) { + // The view needs to know when an animation is ongoing so it can intercept + // unnecessary accessibility events. + mView.setAnimatingContentLaunch(running); + } + mExpandAnimationRunning = running; mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt index 705a11df83fc..e12b21eb2c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.phone import android.view.View +import com.android.systemui.Flags import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator import com.android.systemui.animation.TransitionAnimator.Companion.getProgress @@ -22,7 +23,7 @@ class StatusBarTransitionAnimatorController( private val notificationShadeWindowController: NotificationShadeWindowController, private val commandQueue: CommandQueue, @DisplayId private val displayId: Int, - private val isLaunchForActivity: Boolean = true + private val isLaunchForActivity: Boolean = true, ) : ActivityTransitionAnimator.Controller by delegate { private var hideIconsDuringLaunchAnimation: Boolean = true @@ -41,8 +42,16 @@ class StatusBarTransitionAnimatorController( } override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - shadeAnimationInteractor.setIsLaunchingActivity(true) + if (Flags.shadeLaunchAccessibility()) { + // We set this before calling the delegate to make sure that accessibility is disabled + // for the whole duration of the transition, so that we don't have stray TalkBack events + // once the animating view becomes invisible. + shadeAnimationInteractor.setIsLaunchingActivity(true) + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + } else { + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + shadeAnimationInteractor.setIsLaunchingActivity(true) + } if (!isExpandingFullyAbove) { shadeController.collapseWithDuration( ActivityTransitionAnimator.TIMINGS.totalDuration.toInt() @@ -59,7 +68,7 @@ class StatusBarTransitionAnimatorController( override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float + linearProgress: Float, ) { delegate.onTransitionAnimationProgress(state, progress, linearProgress) val hideIcons = @@ -67,7 +76,7 @@ class StatusBarTransitionAnimatorController( ActivityTransitionAnimator.TIMINGS, linearProgress, ANIMATION_DELAY_ICON_FADE_IN, - 100 + 100, ) == 0.0f if (hideIcons != hideIconsDuringLaunchAnimation) { hideIconsDuringLaunchAnimation = hideIcons diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt index 39b434ad65f1..83b7c1818341 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog -import android.app.Dialog import android.content.Context import android.graphics.PixelFormat import android.os.Bundle @@ -24,6 +23,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager +import androidx.activity.ComponentDialog import com.android.app.tracing.coroutines.coroutineScopeTraced import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.repeatWhenAttached @@ -40,7 +40,7 @@ constructor( @Application context: Context, private val componentFactory: VolumeDialogComponent.Factory, private val visibilityInteractor: VolumeDialogVisibilityInteractor, -) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) { +) : ComponentDialog(context, R.style.Theme_SystemUI_Dialog_Volume) { init { with(window!!) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt index 940c79c78d76..577e47bb3b83 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt @@ -18,7 +18,6 @@ package com.android.systemui.volume.dialog.sliders.dagger import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import dagger.BindsInstance import dagger.Subcomponent @@ -33,8 +32,6 @@ interface VolumeDialogSliderComponent { fun sliderViewBinder(): VolumeDialogSliderViewBinder - fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder - fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder @Subcomponent.Factory diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt index 8109b50aa34a..38feb69aad7b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt @@ -20,11 +20,9 @@ import android.view.View import androidx.dynamicanimation.animation.FloatValueHolder import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce -import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel.OverscrollEventModel -import com.google.android.material.slider.Slider import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -51,10 +49,6 @@ constructor(private val viewModel: VolumeDialogOverscrollViewModel) { ) .addUpdateListener { _, value, _ -> viewsToAnimate.setTranslationY(value) } - view.requireViewById<Slider>(R.id.volume_dialog_slider).addOnChangeListener { s, value, _ -> - viewModel.setSlider(value = value, min = s.valueFrom, max = s.valueTo) - } - viewModel.overscrollEvent .onEach { event -> when (event) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt deleted file mode 100644 index 5a7fbc6341f2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.dialog.sliders.ui - -import android.view.View -import com.android.systemui.haptics.slider.HapticSlider -import com.android.systemui.haptics.slider.HapticSliderPlugin -import com.android.systemui.res.R -import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.time.SystemClock -import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope -import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel -import com.google.android.material.slider.Slider -import com.google.android.msdl.domain.MSDLPlayer -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -@VolumeDialogSliderScope -class VolumeDialogSliderHapticsViewBinder -@Inject -constructor( - private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel, - private val vibratorHelper: VibratorHelper, - private val msdlPlayer: MSDLPlayer, - private val systemClock: SystemClock, -) { - - fun CoroutineScope.bind(view: View) { - val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider) - val hapticSliderPlugin = - HapticSliderPlugin( - slider = HapticSlider.Slider(sliderView), - vibratorHelper = vibratorHelper, - msdlPlayer = msdlPlayer, - systemClock = systemClock, - ) - hapticSliderPlugin.startInScope(this) - - sliderView.addOnChangeListener { _, value, fromUser -> - hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser) - } - sliderView.addOnSliderTouchListener( - object : Slider.OnSliderTouchListener { - - override fun onStartTrackingTouch(slider: Slider) { - hapticSliderPlugin.onStartTrackingTouch() - } - - override fun onStopTrackingTouch(slider: Slider) { - hapticSliderPlugin.onStopTrackingTouch() - } - } - ) - - inputEventsViewModel.event - .onEach { - when (it) { - is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown() - is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event) - } - } - .launchIn(this) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index d40302408dd6..932f8aececa8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -16,96 +16,219 @@ package com.android.systemui.volume.dialog.sliders.ui -import android.annotation.SuppressLint +import android.graphics.drawable.Drawable +import android.view.MotionEvent import android.view.View -import androidx.dynamicanimation.animation.FloatPropertyCompat -import androidx.dynamicanimation.animation.SpringAnimation -import androidx.dynamicanimation.animation.SpringForce +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.material3.VerticalSlider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.ui.graphics.painter.DrawablePainter +import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope +import com.android.systemui.volume.dialog.sliders.ui.compose.VolumeDialogSliderTrack +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel -import com.google.android.material.slider.Slider -import com.google.android.material.slider.Slider.OnSliderTouchListener +import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import javax.inject.Inject +import kotlin.math.round import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map @VolumeDialogSliderScope class VolumeDialogSliderViewBinder @Inject constructor( private val viewModel: VolumeDialogSliderViewModel, + private val overscrollViewModel: VolumeDialogOverscrollViewModel, private val inputViewModel: VolumeDialogSliderInputEventsViewModel, + private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, ) { + fun bind(view: View) { + val sliderComposeView: ComposeView = view.requireViewById(R.id.volume_dialog_slider) + sliderComposeView.setContent { + VolumeDialogSlider( + viewModel = viewModel, + inputViewModel = inputViewModel, + overscrollViewModel = overscrollViewModel, + hapticsViewModelFactory = + if (com.android.systemui.Flags.hapticsForComposeSliders()) { + hapticsViewModelFactory + } else { + null + }, + ) + } + } +} - private val sliderValueProperty = - object : FloatPropertyCompat<Slider>("value") { - override fun getValue(slider: Slider): Float = slider.value +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +private fun VolumeDialogSlider( + viewModel: VolumeDialogSliderViewModel, + inputViewModel: VolumeDialogSliderInputEventsViewModel, + overscrollViewModel: VolumeDialogOverscrollViewModel, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, + modifier: Modifier = Modifier, +) { + + val colors = + SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTickColor = MaterialTheme.colorScheme.surfaceContainerHighest, + inactiveTickColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, + ) + val collectedSliderState by viewModel.state.collectAsStateWithLifecycle(null) + val sliderState = collectedSliderState ?: return - override fun setValue(slider: Slider, value: Float) { - slider.value = value + val interactionSource = remember { MutableInteractionSource() } + val hapticsViewModel: SliderHapticsViewModel? = + hapticsViewModelFactory?.let { + rememberViewModel(traceName = "SliderHapticsViewModel") { + it.create( + interactionSource, + sliderState.valueRange, + Orientation.Vertical, + VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(sliderState.valueRange), + VolumeHapticsConfigsProvider.seekableSliderTrackerConfig, + ) } } - private val springForce = - SpringForce().apply { - stiffness = SpringForce.STIFFNESS_MEDIUM - dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - } - @SuppressLint("ClickableViewAccessibility") - fun CoroutineScope.bind(view: View) { - var isInitialUpdate = true - val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider) - val animation = SpringAnimation(sliderView, sliderValueProperty) - animation.spring = springForce - sliderView.setOnTouchListener { _, event -> - inputViewModel.onTouchEvent(event) - false - } - sliderView.addOnChangeListener { _, value, fromUser -> - viewModel.setStreamVolume(value.roundToInt(), fromUser) + val state = + remember(sliderState.valueRange) { + SliderState( + value = sliderState.value, + valueRange = sliderState.valueRange, + steps = + (sliderState.valueRange.endInclusive - sliderState.valueRange.start - 1) + .toInt(), + ) + .apply { + onValueChangeFinished = { + viewModel.onStreamChangeFinished(value.roundToInt()) + hapticsViewModel?.onValueChangeEnded() + } + setOnValueChangeListener { + value = it + hapticsViewModel?.addVelocityDataPoint(it) + overscrollViewModel.setSlider( + value = value, + min = valueRange.start, + max = valueRange.endInclusive, + ) + viewModel.setStreamVolume(it, true) + } + } } - sliderView.addOnSliderTouchListener( - object : OnSliderTouchListener { - override fun onStartTrackingTouch(slider: Slider) {} + var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderState.value)) } + LaunchedEffect(sliderState.value) { + state.value = sliderState.value + snapshotFlow { sliderState.value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel?.onValueChange(discreteStep) + } + } - override fun onStopTrackingTouch(slider: Slider) { - viewModel.onStreamChangeFinished(slider.value.roundToInt()) + VerticalSlider( + state = state, + enabled = !sliderState.isDisabled, + reverseDirection = true, + colors = colors, + interactionSource = interactionSource, + modifier = + modifier.pointerInput(Unit) { + awaitPointerEventScope { + // we should wait for all new pointer events + while (true) { + val event: PointerEvent = awaitPointerEvent() + PointerEvent::class + .java + .methods + .find { it.name.startsWith("getMotionEvent") }!! + .invoke(event) + ?.let { it as? MotionEvent? } + ?.let { inputViewModel.onTouchEvent(it) } + } } - } - ) + }, + track = { + VolumeDialogSliderTrack( + state, + colors = colors, + isEnabled = !sliderState.isDisabled, + activeTrackEndIcon = { iconsState -> + VolumeIcon(sliderState.icon, iconsState.isActiveTrackEndIconVisible) + }, + inactiveTrackEndIcon = { iconsState -> + VolumeIcon(sliderState.icon, !iconsState.isActiveTrackEndIconVisible) + }, + ) + }, + ) +} - viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this) - viewModel.state - .onEach { - sliderView.setModel(it, animation, isInitialUpdate) - isInitialUpdate = false - } - .launchIn(this) +@Composable +private fun BoxScope.VolumeIcon( + drawable: Drawable, + isVisible: Boolean, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + visible = isVisible, + enter = fadeIn(animationSpec = tween(durationMillis = 50)), + exit = fadeOut(animationSpec = tween(durationMillis = 50)), + modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp), + ) { + Icon(painter = DrawablePainter(drawable), contentDescription = null) } +} - @SuppressLint("UseCompatLoadingForDrawables") - private fun Slider.setModel( - model: VolumeDialogSliderStateModel, - animation: SpringAnimation, - isInitialUpdate: Boolean, - ) { - valueFrom = model.minValue - animation.setMinValue(model.minValue) - valueTo = model.maxValue - animation.setMaxValue(model.maxValue) - // coerce the current value to the new value range before animating it. This prevents - // animating from the value that is outside of current [valueFrom, valueTo]. - value = value.coerceIn(valueFrom, valueTo) - trackIconActiveStart = model.icon - if (isInitialUpdate) { - value = model.value - } else { - animation.animateToFinalPosition(model.value) - } +@OptIn(ExperimentalMaterial3Api::class) +fun SliderState.setOnValueChangeListener(onValueChange: ((Float) -> Unit)?) { + with(javaClass.getDeclaredField("onValueChange")) { + val oldIsAccessible = isAccessible + AutoCloseable { isAccessible = oldIsAccessible } + .use { + isAccessible = true + set(this@setOnValueChangeListener, onValueChange) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt index 75d427acc05b..c66955a0c187 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -71,7 +71,6 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) { viewsToAnimate: Array<View>, ) { with(component.sliderViewBinder()) { bind(sliderContainer) } - with(component.sliderHapticsViewBinder()) { bind(sliderContainer) } with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt new file mode 100644 index 000000000000..1dd9ddac79be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.ui.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastFilter +import androidx.compose.ui.util.fastFirst +import kotlin.math.min + +@Composable +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +fun VolumeDialogSliderTrack( + sliderState: SliderState, + colors: SliderColors, + isEnabled: Boolean, + modifier: Modifier = Modifier, + thumbTrackGapSize: Dp = 6.dp, + trackCornerSize: Dp = 12.dp, + trackInsideCornerSize: Dp = 2.dp, + trackSize: Dp = 40.dp, + activeTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + activeTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + inactiveTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + inactiveTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, +) { + val measurePolicy = remember(sliderState) { TrackMeasurePolicy(sliderState) } + Layout( + measurePolicy = measurePolicy, + content = { + SliderDefaults.Track( + sliderState = sliderState, + colors = colors, + enabled = isEnabled, + trackCornerSize = trackCornerSize, + trackInsideCornerSize = trackInsideCornerSize, + drawStopIndicator = null, + thumbTrackGapSize = thumbTrackGapSize, + drawTick = { _, _ -> }, + modifier = Modifier.width(trackSize).layoutId(Contents.Track), + ) + + TrackIcon( + icon = activeTrackStartIcon, + contentsId = Contents.Active.TrackStartIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = activeTrackEndIcon, + contentsId = Contents.Active.TrackEndIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = inactiveTrackStartIcon, + contentsId = Contents.Inactive.TrackStartIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = inactiveTrackEndIcon, + contentsId = Contents.Inactive.TrackEndIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + }, + modifier = modifier, + ) +} + +@Composable +private fun TrackIcon( + icon: (@Composable BoxScope.(sliderIconsState: SliderIconsState) -> Unit)?, + isEnabled: Boolean, + contentsId: Contents, + state: SliderIconsState, + colors: SliderColors, + modifier: Modifier = Modifier, +) { + icon ?: return + Box(modifier = modifier.layoutId(contentsId).fillMaxSize()) { + CompositionLocalProvider( + LocalContentColor provides contentsId.getColor(colors, isEnabled) + ) { + icon(state) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private class TrackMeasurePolicy(private val sliderState: SliderState) : + MeasurePolicy, SliderIconsState { + + private val isVisible: Map<Contents, MutableState<Boolean>> = + mutableMapOf( + Contents.Active.TrackStartIcon to mutableStateOf(false), + Contents.Active.TrackEndIcon to mutableStateOf(false), + Contents.Inactive.TrackStartIcon to mutableStateOf(false), + Contents.Inactive.TrackEndIcon to mutableStateOf(false), + ) + + override val isActiveTrackStartIconVisible: Boolean + get() = isVisible.getValue(Contents.Active.TrackStartIcon).value + + override val isActiveTrackEndIconVisible: Boolean + get() = isVisible.getValue(Contents.Active.TrackEndIcon).value + + override val isInactiveTrackStartIconVisible: Boolean + get() = isVisible.getValue(Contents.Inactive.TrackStartIcon).value + + override val isInactiveTrackEndIconVisible: Boolean + get() = isVisible.getValue(Contents.Inactive.TrackEndIcon).value + + override fun MeasureScope.measure( + measurables: List<Measurable>, + constraints: Constraints, + ): MeasureResult { + val track = measurables.fastFirst { it.layoutId == Contents.Track }.measure(constraints) + + val iconSize = min(track.width, track.height) + val iconConstraints = constraints.copy(maxWidth = iconSize, maxHeight = iconSize) + + val icons = + measurables + .fastFilter { it.layoutId != Contents.Track } + .associateBy( + keySelector = { it.layoutId as Contents }, + valueTransform = { it.measure(iconConstraints) }, + ) + + return layout(track.width, track.height) { + with(Contents.Track) { + performPlacing( + placeable = track, + width = track.width, + height = track.height, + sliderState = sliderState, + ) + } + + for (iconLayoutId in icons.keys) { + with(iconLayoutId) { + performPlacing( + placeable = icons.getValue(iconLayoutId), + width = track.width, + height = track.height, + sliderState = sliderState, + ) + + isVisible.getValue(iconLayoutId).value = + isVisible( + placeable = icons.getValue(iconLayoutId), + width = track.width, + height = track.height, + sliderState = sliderState, + ) + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private sealed interface Contents { + + data object Track : Contents { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = placeable.place(x = 0, y = 0) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = true + + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color = + error("Unsupported") + } + + interface Active : Contents { + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color { + return if (isEnabled) { + sliderColors.activeTickColor + } else { + sliderColors.disabledActiveTickColor + } + } + + data object TrackStartIcon : Active { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = + placeable.place( + x = 0, + y = (height * (1 - sliderState.coercedValueAsFraction)).toInt(), + ) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + + data object TrackEndIcon : Active { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = placeable.place(x = 0, y = (height - placeable.height)) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + } + + interface Inactive : Contents { + + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color { + return if (isEnabled) { + sliderColors.inactiveTickColor + } else { + sliderColors.disabledInactiveTickColor + } + } + + data object TrackStartIcon : Inactive { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) { + placeable.place(x = 0, y = 0) + } + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + + data object TrackEndIcon : Inactive { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) { + placeable.place( + x = 0, + y = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() - + placeable.height, + ) + } + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + } + + fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) + + fun isVisible(placeable: Placeable, width: Int, height: Int, sliderState: SliderState): Boolean + + fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color +} + +/** Provides visibility state for each of the Slider's icons. */ +interface SliderIconsState { + val isActiveTrackStartIconVisible: Boolean + val isActiveTrackEndIconVisible: Boolean + val isInactiveTrackStartIconVisible: Boolean + val isInactiveTrackEndIconVisible: Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt index 8df9e788905c..b01046b377b0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt @@ -20,17 +20,20 @@ import android.graphics.drawable.Drawable import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel data class VolumeDialogSliderStateModel( - val minValue: Float, - val maxValue: Float, val value: Float, + val isDisabled: Boolean, + val valueRange: ClosedFloatingPointRange<Float>, val icon: Drawable, ) -fun VolumeDialogStreamModel.toStateModel(icon: Drawable): VolumeDialogSliderStateModel { +fun VolumeDialogStreamModel.toStateModel( + isDisabled: Boolean, + icon: Drawable, +): VolumeDialogSliderStateModel { return VolumeDialogSliderStateModel( - minValue = levelMin.toFloat(), value = level.toFloat(), - maxValue = levelMax.toFloat(), + isDisabled = isDisabled, + valueRange = levelMin.toFloat()..levelMax.toFloat(), icon = icon, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index a752f1f78e74..df9b32c9326f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -26,17 +26,18 @@ import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import javax.inject.Inject +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn @@ -77,11 +78,12 @@ constructor( .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() - val isDisabledByZenMode: Flow<Boolean> = interactor.isDisabledByZenMode val state: Flow<VolumeDialogSliderStateModel> = - model - .flatMapLatest { streamModel -> - with(streamModel) { + combine( + interactor.isDisabledByZenMode, + model, + model.flatMapLatest { streamModel -> + with(streamModel) { val isMuted = muteSupported && muted when (sliderType) { is VolumeDialogSliderType.Stream -> @@ -101,7 +103,9 @@ constructor( } } } - .map { icon -> streamModel.toStateModel(icon) } + }, + ) { isDisabledByZenMode, model, icon -> + model.toStateModel(icon = icon, isDisabled = isDisabledByZenMode) } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() @@ -116,11 +120,14 @@ constructor( .launchIn(coroutineScope) } - fun setStreamVolume(volume: Int, fromUser: Boolean) { + fun setStreamVolume(volume: Float, fromUser: Boolean) { if (fromUser) { visibilityInteractor.resetDismissTimeout() userVolumeUpdates.value = - VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis()) + VolumeUpdate( + newVolumeLevel = volume.roundToInt(), + timestampMillis = getTimestampMillis(), + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt new file mode 100644 index 000000000000..92e9bf2d1ffc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.haptics.ui + +import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig +import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig + +object VolumeHapticsConfigsProvider { + + fun sliderHapticFeedbackConfig( + valueRange: ClosedFloatingPointRange<Float> + ): SliderHapticFeedbackConfig { + val sliderStepSize = 1f / (valueRange.endInclusive - valueRange.start) + return SliderHapticFeedbackConfig( + lowerBookendScale = 0.2f, + progressBasedDragMinScale = 0.2f, + progressBasedDragMaxScale = 0.5f, + deltaProgressForDragThreshold = 0f, + additionalVelocityMaxBump = 0.2f, + maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */ + sliderStepSize = sliderStepSize, + ) + } + + val seekableSliderTrackerConfig = + SeekableSliderTrackerConfig(lowerBookendThreshold = 0f, upperBookendThreshold = 1f) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index e68153ad2606..70450d29c74e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -57,7 +57,10 @@ import com.android.systemui.res.R import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.data.repository.ShadeAnimationRepository +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -219,6 +222,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : notificationShadeDepthController, view, shadeViewController, + ShadeAnimationInteractorLegacyImpl( + ShadeAnimationRepository(), + ShadeRepositoryImpl(testScope), + ), panelExpansionInteractor, ShadeExpansionStateManager(), stackScrollLayoutController, @@ -521,6 +528,18 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) } + @EnableFlags(Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY) + @Test + fun notifiesTheViewWhenLaunchAnimationIsRunning() { + testScope.runTest { + underTest.setExpandAnimationRunning(true) + verify(view).setAnimatingContentLaunch(true) + + underTest.setExpandAnimationRunning(false) + verify(view).setAnimatingContentLaunch(false) + } + } + @Test @DisableSceneContainer fun setsUpCommunalHubLayout_whenFlagEnabled() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt index 36fa82f82f0d..4ca044d60f3f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt @@ -32,10 +32,8 @@ import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibility import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder import com.android.systemui.volume.mediaControllerRepository import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor @@ -65,9 +63,6 @@ fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDial override fun sliderViewBinder(): VolumeDialogSliderViewBinder = localKosmos.volumeDialogSliderViewBinder - override fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder = - localKosmos.volumeDialogSliderHapticsViewBinder - override fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder = localKosmos.volumeDialogOverscrollViewBinder } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt deleted file mode 100644 index d6845b1ff7e3..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.dialog.sliders.ui - -import com.android.systemui.haptics.msdl.msdlPlayer -import com.android.systemui.haptics.vibratorHelper -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.time.systemClock -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel - -val Kosmos.volumeDialogSliderHapticsViewBinder by - Kosmos.Fixture { - VolumeDialogSliderHapticsViewBinder( - volumeDialogSliderInputEventsViewModel, - vibratorHelper, - msdlPlayer, - systemClock, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt index c6db717e004f..c7cbda1b0d49 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt @@ -16,7 +16,9 @@ package com.android.systemui.volume.dialog.sliders.ui +import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel @@ -24,6 +26,8 @@ val Kosmos.volumeDialogSliderViewBinder by Kosmos.Fixture { VolumeDialogSliderViewBinder( volumeDialogSliderViewModel, + volumeDialogOverscrollViewModel, volumeDialogSliderInputEventsViewModel, + sliderHapticsViewModelFactory, ) } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1d12c561f118..064ef1aa0eff 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3231,8 +3231,8 @@ final class ActivityRecord extends WindowToken { * Returns {@code true} if the fixed orientation, aspect ratio, resizability of the application * can be ignored. */ - static boolean canBeUniversalResizeable(ApplicationInfo appInfo, WindowManagerService wms, - boolean isLargeScreen, boolean forActivity) { + static boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo, + WindowManagerService wms, boolean isLargeScreen, boolean forActivity) { if (appInfo.category == ApplicationInfo.CATEGORY_GAME) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d4f9c0901162..cf111cdbcc6a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2154,6 +2154,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** + * @return ehether the application could be universal resizeable on a large screen, + * ignoring any overrides + */ + @Override + public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) { + return ActivityRecord.canBeUniversalResizeable(appInfo, mWindowManager, + /* isLargeScreen */ true, /* forActivity */ false); + } + @Override public void removeAllVisibleRecentTasks() { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()"); diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java index 038e1357159b..036e03c60091 100644 --- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java @@ -23,6 +23,7 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.Presubmit; import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -38,6 +39,7 @@ import org.junit.runner.RunWith; * Build/Install/Run: * atest WmTests:CombinationKeyTests */ +@Presubmit @MediumTest @RunWith(AndroidJUnit4.class) @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_KEY_GESTURES) diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java index ca3787ec4546..bccdd67d33ed 100644 --- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java @@ -19,6 +19,7 @@ package com.android.server.policy; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; import org.junit.Before; @@ -29,6 +30,7 @@ import org.junit.Test; * * <p>Build/Install/Run: atest WmTests:DeferredKeyActionExecutorTests */ +@Presubmit public final class DeferredKeyActionExecutorTests { private DeferredKeyActionExecutor mKeyActionExecutor; diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java index 8b5f68a1e974..a912c177c863 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java @@ -29,6 +29,7 @@ import static org.testng.Assert.assertTrue; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; import androidx.test.filters.SmallTest; @@ -45,7 +46,7 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest KeyCombinationManagerTests */ - +@Presubmit @SmallTest public class KeyCombinationManagerTests { private KeyCombinationManager mKeyCombinationManager; diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java index 82a5add407f4..d961a6acc9c1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -42,6 +42,7 @@ import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; @@ -64,7 +65,7 @@ import java.util.Collections; * Build/Install/Run: * atest ModifierShortcutManagerTests */ - +@Presubmit @SmallTest @EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR) public class ModifierShortcutManagerTests { @@ -127,7 +128,7 @@ public class ModifierShortcutManagerTests { // Total valid shortcuts. KeyboardShortcutGroup group = mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1); - assertEquals(13, group.getItems().size()); + assertEquals(11, group.getItems().size()); // Total valid shift shortcuts. assertEquals(3, group.getItems().stream() diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index af3dc1da4dcc..c73ce23fe6b5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -48,6 +48,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.hardware.input.InputManager; import android.os.PowerManager; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; @@ -72,6 +73,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:PhoneWindowManagerTests */ +@Presubmit @SmallTest public class PhoneWindowManagerTests { diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java index 33ccec3f785a..53e82bad818d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java @@ -28,6 +28,7 @@ import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Display; @@ -40,6 +41,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:PowerKeyGestureTests */ +@Presubmit public class PowerKeyGestureTests extends ShortcutKeyTestBase { @Before public void setUp() { diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index 08eb1451bd6f..6c6594220bad 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -39,6 +39,7 @@ import android.os.Process; import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; @@ -57,6 +58,7 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest WmTests:SingleKeyGestureTests */ +@Presubmit public class SingleKeyGestureTests { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index 3ea3235df0f4..833dd5d768e4 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -35,6 +35,7 @@ import android.app.ActivityTaskManager.RootTaskInfo; import android.content.ComponentName; import android.hardware.input.KeyGestureEvent; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Display; @@ -47,6 +48,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:StemKeyGestureTests */ +@Presubmit public class StemKeyGestureTests extends ShortcutKeyTestBase { private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity"; diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java index 3ca352cfa60d..9e59bced01f1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java @@ -57,6 +57,7 @@ import android.content.res.Resources; import android.os.PowerManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.view.Display; @@ -83,6 +84,7 @@ import java.util.function.BooleanSupplier; * * <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests */ +@Presubmit public final class WindowWakeUpPolicyTests { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); |