diff options
| author | 2024-05-22 13:47:59 +0000 | |
|---|---|---|
| committer | 2024-05-23 12:10:38 +0000 | |
| commit | b69ff7b45cb33f85fc4327ddb1e13c9258c4079f (patch) | |
| tree | 6c442979a6366e6d3cc6395080d59cc295fde822 | |
| parent | c14c0eac058c6eabfdf5f968b5567ffa340fb995 (diff) | |
Add bottomsheet predictive back animation for SysUI
Bug: 340724858
Flag: com.android.systemui.predictiveBackAnimateDialogs
Test: atest BackAnimationSpecTest
Test: atest SystemUIDialogTest
Test: atest BackTransformationTest
Test: Manual, i.e. testing predictive back animation for volume panel bottomsheet on device
Change-Id: Ica27bb102e747dd83503eae70a93cfaad1906050
9 files changed, 186 insertions, 3 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt index b05729669f7c..536f2972abdd 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt @@ -73,3 +73,11 @@ fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi( maxMarginYdp = 8f, minScale = 0.9f, ) + +/** + * SysUI transitions - Bottomsheet (AT3) + * https://carbon.googleplex.com/predictive-back-for-apps/pages/at-3-bottom-sheets + */ +fun BackAnimationSpec.Companion.bottomSheetForSysUi( + displayMetricsProvider: () -> DisplayMetrics, +): BackAnimationSpec = BackAnimationSpec.createBottomsheetAnimationSpec(displayMetricsProvider) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt index 49d1fb423d2b..029f62c6e4c9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt @@ -26,12 +26,36 @@ data class BackTransformation( var translateX: Float = Float.NaN, var translateY: Float = Float.NaN, var scale: Float = Float.NaN, + var scalePivotPosition: ScalePivotPosition? = null, ) +/** Enum that describes the location of the scale pivot position */ +enum class ScalePivotPosition { + // more options may be added in the future + CENTER, + BOTTOM_CENTER; + + fun applyTo(view: View) { + val pivotX = + when (this) { + CENTER -> view.width / 2f + BOTTOM_CENTER -> view.width / 2f + } + val pivotY = + when (this) { + CENTER -> view.height / 2f + BOTTOM_CENTER -> view.height.toFloat() + } + view.pivotX = pivotX + view.pivotY = pivotY + } +} + /** Apply the transformation to the [targetView] */ fun BackTransformation.applyTo(targetView: View) { if (translateX.isFinite()) targetView.translationX = translateX if (translateY.isFinite()) targetView.translationY = translateY + scalePivotPosition?.applyTo(targetView) if (scale.isFinite()) { targetView.scaleX = scale targetView.scaleY = scale diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt new file mode 100644 index 000000000000..b1945a1c37db --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt @@ -0,0 +1,42 @@ +/* + * 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.animation.back + +import android.util.DisplayMetrics +import android.view.animation.Interpolator +import com.android.app.animation.Interpolators +import com.android.systemui.util.dpToPx + +private const val MAX_SCALE_DELTA_DP = 48 + +/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */ +fun BackAnimationSpec.Companion.createBottomsheetAnimationSpec( + displayMetricsProvider: () -> DisplayMetrics, + scaleEasing: Interpolator = Interpolators.BACK_GESTURE, +): BackAnimationSpec { + return BackAnimationSpec { backEvent, _, result -> + val displayMetrics = displayMetricsProvider() + val screenWidthPx = displayMetrics.widthPixels + val minScale = 1 - MAX_SCALE_DELTA_DP.dpToPx(displayMetrics) / screenWidthPx + val progressX = backEvent.progress + val ratioScale = scaleEasing.getInterpolation(progressX) + result.apply { + scale = 1f - ratioScale * (1f - minScale) + scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt index 55dfed407e7a..5fc78c07033e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt @@ -17,8 +17,11 @@ package com.android.systemui.statusbar.phone import android.os.Bundle +import android.util.DisplayMetrics import android.view.Gravity import android.view.WindowManager +import com.android.systemui.animation.back.BackAnimationSpec +import com.android.systemui.animation.back.bottomSheetForSysUi /** [DialogDelegate] that configures a dialog to be an edge-to-edge one. */ class EdgeToEdgeDialogDelegate : DialogDelegate<SystemUIDialog> { @@ -40,4 +43,10 @@ class EdgeToEdgeDialogDelegate : DialogDelegate<SystemUIDialog> { override fun getWidth(dialog: SystemUIDialog): Int = WindowManager.LayoutParams.MATCH_PARENT override fun getHeight(dialog: SystemUIDialog): Int = WindowManager.LayoutParams.MATCH_PARENT + + override fun getBackAnimationSpec( + displayMetricsProvider: () -> DisplayMetrics + ): BackAnimationSpec { + return BackAnimationSpec.bottomSheetForSysUi(displayMetricsProvider) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 1cdf8dc4a8e8..79b5cc37119f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -21,8 +21,10 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; import android.content.Intent; @@ -41,6 +43,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.back.BackAnimationSpec; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.model.SysUiState; @@ -78,6 +81,8 @@ public class SystemUIDialogTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher); + when(mDelegate.getBackAnimationSpec(ArgumentMatchers.any())) + .thenReturn(mock(BackAnimationSpec.class)); } @Test diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt index 25d1f05316db..2beb66b57672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt @@ -19,7 +19,10 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.res.Configuration import android.os.Bundle +import android.util.DisplayMetrics import android.view.ViewRootImpl +import com.android.systemui.animation.back.BackAnimationSpec +import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi /** * A delegate class that should be implemented in place of subclassing [Dialog]. @@ -49,4 +52,7 @@ interface DialogDelegate<T : Dialog> { fun getWidth(dialog: T): Int = SystemUIDialog.getDefaultDialogWidth(dialog) fun getHeight(dialog: T): Int = SystemUIDialog.getDefaultDialogHeight() + + fun getBackAnimationSpec(displayMetricsProvider: () -> DisplayMetrics): BackAnimationSpec = + BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetricsProvider) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index c74dde57b5f5..e01556f91fac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -269,9 +269,12 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mOnCreateRunnables.get(i).run(); } if (predictiveBackAnimateDialogs()) { + View targetView = getWindow().getDecorView(); DialogKt.registerAnimationOnBackInvoked( /* dialog = */ this, - /* targetView = */ getWindow().getDecorView() + /* targetView = */ targetView, + /* backAnimationSpec= */mDelegate.getBackAnimationSpec( + () -> targetView.getResources().getDisplayMetrics()) ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt index 190babdb22b0..0ed84ea2d183 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt @@ -4,7 +4,9 @@ import android.util.DisplayMetrics import android.window.BackEvent import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.util.dpToPx import com.google.common.truth.Truth.assertThat +import junit.framework.TestCase.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -60,6 +62,58 @@ class BackAnimationSpecTest : SysuiTestCase() { expected = BackTransformation(translateX = 0f, translateY = maxY, scale = 1f), ) } + + @Test + fun sysUi_bottomsheet_animationValues() { + val minScale = 1 - 48.dpToPx(displayMetrics) / displayMetrics.widthPixels + + val backAnimationSpec = BackAnimationSpec.bottomSheetForSysUi { displayMetrics } + + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 0f, progressY = 0f, edge = BackEvent.EDGE_LEFT), + expected = + BackTransformation( + translateX = Float.NaN, + translateY = Float.NaN, + scale = 1f, + scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER + ), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_LEFT), + expected = + BackTransformation( + translateX = Float.NaN, + translateY = Float.NaN, + scale = minScale, + scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER + ), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_RIGHT), + expected = + BackTransformation( + translateX = Float.NaN, + translateY = Float.NaN, + scale = minScale, + scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER + ), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 1f, progressY = 1f, edge = BackEvent.EDGE_LEFT), + expected = + BackTransformation( + translateX = Float.NaN, + translateY = Float.NaN, + scale = minScale, + scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER + ), + ) + } } private fun assertBackTransformation( @@ -81,7 +135,16 @@ private fun assertBackTransformation( ) val tolerance = 0f - assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX) - assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY) + if (expected.translateX.isNaN()) { + assertEquals(expected.translateX, actual.translateX) + } else { + assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX) + } + if (expected.translateY.isNaN()) { + assertEquals(expected.translateY, actual.translateY) + } else { + assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY) + } assertThat(actual.scale).isWithin(tolerance).of(expected.scale) + assertEquals(expected.scalePivotPosition, actual.scalePivotPosition) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt index 190b3d25d16b..44a546704953 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt @@ -5,17 +5,25 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.kotlin.whenever @SmallTest @RunWith(JUnit4::class) class BackTransformationTest : SysuiTestCase() { private val targetView: View = mock() + @Before + fun setup() { + whenever(targetView.width).thenReturn(TARGET_VIEW_WIDTH) + whenever(targetView.height).thenReturn(TARGET_VIEW_HEIGHT) + } + @Test fun defaultValue_noTransformation() { val transformation = BackTransformation() @@ -70,6 +78,16 @@ class BackTransformationTest : SysuiTestCase() { } @Test + fun applyTo_targetView_scale_pivot() { + val transformation = BackTransformation(scalePivotPosition = ScalePivotPosition.CENTER) + + transformation.applyTo(targetView = targetView) + + verify(targetView).pivotX = TARGET_VIEW_WIDTH / 2f + verify(targetView).pivotY = TARGET_VIEW_HEIGHT / 2f + } + + @Test fun applyTo_targetView_noTransformation() { val transformation = BackTransformation() @@ -77,4 +95,9 @@ class BackTransformationTest : SysuiTestCase() { verifyNoMoreInteractions(targetView) } + + companion object { + private const val TARGET_VIEW_WIDTH = 100 + private const val TARGET_VIEW_HEIGHT = 50 + } } |