diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 5aa7769d5ace3..42a86363bf017 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -33,13 +33,9 @@ import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import android.widget.FrameLayout -import android.window.OnBackInvokedDispatcher import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CujType -import com.android.systemui.animation.back.BackAnimationSpec -import com.android.systemui.animation.back.applyTo -import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi -import com.android.systemui.animation.back.onBackAnimationCallbackFrom +import com.android.systemui.util.registerAnimationOnBackInvoked import java.lang.IllegalArgumentException import kotlin.math.roundToInt @@ -798,7 +794,7 @@ private class AnimatedDialog( if (featureFlags.isPredictiveBackQsDialogAnim) { // TODO(b/265923095) Improve animations for QS dialogs on configuration change - registerOnBackInvokedCallback(targetView = dialogContentWithBackground) + dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) } // Show the dialog. @@ -806,35 +802,6 @@ private class AnimatedDialog( moveSourceDrawingToDialog() } - private fun registerOnBackInvokedCallback(targetView: View) { - val metrics = targetView.resources.displayMetrics - - val onBackAnimationCallback = - onBackAnimationCallbackFrom( - backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(metrics), - displayMetrics = metrics, // TODO(b/265060720): We could remove this - onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) }, - onBackInvoked = { dialog.dismiss() }, - ) - - val dispatcher = dialog.onBackInvokedDispatcher - targetView.addOnAttachStateChangeListener( - object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - dispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, - onBackAnimationCallback - ) - } - - override fun onViewDetachedFromWindow(v: View) { - targetView.removeOnAttachStateChangeListener(this) - dispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback) - } - } - ) - } - private fun moveSourceDrawingToDialog() { if (decorView.viewRootImpl == null) { // Make sure that we have access to the dialog view root to move the drawing to the diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt index 33d14b14c702e..8740d14672965 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt @@ -16,15 +16,21 @@ package com.android.systemui.animation.back +import android.annotation.IntRange import android.util.DisplayMetrics +import android.view.View import android.window.BackEvent import android.window.OnBackAnimationCallback +import android.window.OnBackInvokedDispatcher +import android.window.OnBackInvokedDispatcher.Priority /** * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be * called on each update passing the current [BackTransformation]. * * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks. + * + * @sample com.android.systemui.util.registerAnimationOnBackInvoked */ fun onBackAnimationCallbackFrom( backAnimationSpec: BackAnimationSpec, @@ -64,3 +70,34 @@ fun onBackAnimationCallbackFrom( } } } + +/** + * Register [OnBackAnimationCallback] when View is attached and unregister it when View is detached + * + * @sample com.android.systemui.util.registerAnimationOnBackInvoked + */ +fun View.registerOnBackInvokedCallbackOnViewAttached( + onBackInvokedDispatcher: OnBackInvokedDispatcher, + onBackAnimationCallback: OnBackAnimationCallback, + @Priority @IntRange(from = 0) priority: Int = OnBackInvokedDispatcher.PRIORITY_DEFAULT, +) { + addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + onBackInvokedDispatcher.registerOnBackInvokedCallback( + priority, + onBackAnimationCallback + ) + } + + override fun onViewDetachedFromWindow(v: View) { + removeOnAttachStateChangeListener(this) + onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback) + } + } + ) + + if (isAttachedToWindow) { + onBackInvokedDispatcher.registerOnBackInvokedCallback(priority, onBackAnimationCallback) + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt new file mode 100644 index 0000000000000..428856dc5f300 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt @@ -0,0 +1,51 @@ +/* + * 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.systemui.util + +import android.app.Dialog +import android.view.View +import android.window.OnBackInvokedDispatcher +import com.android.systemui.animation.back.BackAnimationSpec +import com.android.systemui.animation.back.BackTransformation +import com.android.systemui.animation.back.applyTo +import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi +import com.android.systemui.animation.back.onBackAnimationCallbackFrom +import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached + +/** + * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec]. + * The [BackTransformation] will be applied on the [targetView]. + */ +@JvmOverloads +fun Dialog.registerAnimationOnBackInvoked( + targetView: View, + backAnimationSpec: BackAnimationSpec = + BackAnimationSpec.floatingSystemSurfacesForSysUi( + displayMetrics = targetView.resources.displayMetrics, + ), +) { + targetView.registerOnBackInvokedCallbackOnViewAttached( + onBackInvokedDispatcher = onBackInvokedDispatcher, + onBackAnimationCallback = + onBackAnimationCallbackFrom( + backAnimationSpec = backAnimationSpec, + displayMetrics = targetView.resources.displayMetrics, + onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) }, + onBackInvoked = { dismiss() }, + ), + ) +} 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 608bfa611ff02..924ae4a95fac6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -45,8 +45,11 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.util.DialogKt; import java.util.ArrayList; import java.util.List; @@ -68,6 +71,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true; private final Context mContext; + private final FeatureFlags mFeatureFlags; @Nullable private final DismissReceiver mDismissReceiver; private final Handler mHandler = new Handler(); private final SystemUIDialogManager mDialogManager; @@ -96,16 +100,23 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh // TODO(b/219008720): Remove those calls to Dependency.get by introducing a // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set // the content and attach listeners. - this(context, theme, dismissOnDeviceLock, Dependency.get(SystemUIDialogManager.class), - Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class), + this(context, theme, dismissOnDeviceLock, + Dependency.get(FeatureFlags.class), + Dependency.get(SystemUIDialogManager.class), + Dependency.get(SysUiState.class), + Dependency.get(BroadcastDispatcher.class), Dependency.get(DialogLaunchAnimator.class)); } public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, - SystemUIDialogManager dialogManager, SysUiState sysUiState, - BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) { + FeatureFlags featureFlags, + SystemUIDialogManager dialogManager, + SysUiState sysUiState, + BroadcastDispatcher broadcastDispatcher, + DialogLaunchAnimator dialogLaunchAnimator) { super(context, theme); mContext = context; + mFeatureFlags = featureFlags; applyFlags(this); WindowManager.LayoutParams attrs = getWindow().getAttributes(); @@ -130,6 +141,12 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh for (int i = 0; i < mOnCreateRunnables.size(); i++) { mOnCreateRunnables.get(i).run(); } + if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM)) { + DialogKt.registerAnimationOnBackInvoked( + /* dialog = */ this, + /* targetView = */ getWindow().getDecorView() + ); + } } private void updateWindowSize() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt index bc67df6507fc9..5f206b373610a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt @@ -13,8 +13,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @SmallTest -internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase) - : SysuiTestCase() { +internal class FloatingRotationButtonPositionCalculatorTest( + private val testCase: TestCase, +) : SysuiTestCase() { @Test fun calculatePosition() { @@ -34,11 +35,18 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase val expectedPosition: Position ) { override fun toString(): String = - "when calculator = $calculator, " + - "rotation = $rotation, " + - "taskbarVisible = $taskbarVisible, " + - "taskbarStashed = $taskbarStashed - " + - "expected $expectedPosition" + buildString { + append("when calculator = ") + append(when (calculator) { + posLeftCalculator -> "LEFT" + posRightCalculator -> "RIGHT" + else -> error("Unknown calculator: $calculator") + }) + append(", rotation = $rotation") + append(", taskbarVisible = $taskbarVisible") + append(", taskbarStashed = $taskbarStashed") + append(" - expected $expectedPosition") + } } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 6c83e9f88d63c..14a925a737b94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -19,8 +19,10 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeast; 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; @@ -32,6 +34,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -45,6 +49,8 @@ import org.mockito.MockitoAnnotations; @SmallTest public class SystemUIDialogTest extends SysuiTestCase { + @Mock + private FeatureFlags mFeatureFlags; @Mock private BroadcastDispatcher mBroadcastDispatcher; @@ -52,6 +58,7 @@ public class SystemUIDialogTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher); } @@ -86,4 +93,20 @@ public class SystemUIDialogTest extends SysuiTestCase { verify(mBroadcastDispatcher, never()).unregisterReceiver(any()); assertFalse(dialog.isShowing()); } + + @Test + public void usePredictiveBackAnimFlag() { + when(mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM)) + .thenReturn(true); + final SystemUIDialog dialog = new SystemUIDialog(mContext); + + dialog.show(); + + assertTrue(dialog.isShowing()); + verify(mFeatureFlags, atLeast(1)) + .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM); + + dialog.dismiss(); + assertFalse(dialog.isShowing()); + } }