Merge "Add Predictive Back animation to all SysUI dialogs" into tm-qpr-dev

This commit is contained in:
Omar Miatello
2023-02-11 15:51:36 +00:00
committed by Android (Google) Code Review
6 changed files with 149 additions and 46 deletions

View File

@@ -33,13 +33,9 @@ import android.view.WindowInsets
import android.view.WindowManager import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout import android.widget.FrameLayout
import android.window.OnBackInvokedDispatcher
import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CujType import com.android.internal.jank.InteractionJankMonitor.CujType
import com.android.systemui.animation.back.BackAnimationSpec import com.android.systemui.util.registerAnimationOnBackInvoked
import com.android.systemui.animation.back.applyTo
import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
import com.android.systemui.animation.back.onBackAnimationCallbackFrom
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -798,7 +794,7 @@ private class AnimatedDialog(
if (featureFlags.isPredictiveBackQsDialogAnim) { if (featureFlags.isPredictiveBackQsDialogAnim) {
// TODO(b/265923095) Improve animations for QS dialogs on configuration change // TODO(b/265923095) Improve animations for QS dialogs on configuration change
registerOnBackInvokedCallback(targetView = dialogContentWithBackground) dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground)
} }
// Show the dialog. // Show the dialog.
@@ -806,35 +802,6 @@ private class AnimatedDialog(
moveSourceDrawingToDialog() 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() { private fun moveSourceDrawingToDialog() {
if (decorView.viewRootImpl == null) { if (decorView.viewRootImpl == null) {
// Make sure that we have access to the dialog view root to move the drawing to the // Make sure that we have access to the dialog view root to move the drawing to the

View File

@@ -16,15 +16,21 @@
package com.android.systemui.animation.back package com.android.systemui.animation.back
import android.annotation.IntRange
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.View
import android.window.BackEvent import android.window.BackEvent
import android.window.OnBackAnimationCallback import android.window.OnBackAnimationCallback
import android.window.OnBackInvokedDispatcher
import android.window.OnBackInvokedDispatcher.Priority
/** /**
* Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be
* called on each update passing the current [BackTransformation]. * called on each update passing the current [BackTransformation].
* *
* Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks. * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks.
*
* @sample com.android.systemui.util.registerAnimationOnBackInvoked
*/ */
fun onBackAnimationCallbackFrom( fun onBackAnimationCallbackFrom(
backAnimationSpec: BackAnimationSpec, 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)
}
}

View File

@@ -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() },
),
)
}

View File

@@ -45,8 +45,11 @@ import com.android.systemui.Dependency;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher; 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.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.util.DialogKt;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
private final Context mContext; private final Context mContext;
private final FeatureFlags mFeatureFlags;
@Nullable private final DismissReceiver mDismissReceiver; @Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler(); private final Handler mHandler = new Handler();
private final SystemUIDialogManager mDialogManager; 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 // 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 // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
// the content and attach listeners. // the content and attach listeners.
this(context, theme, dismissOnDeviceLock, Dependency.get(SystemUIDialogManager.class), this(context, theme, dismissOnDeviceLock,
Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class), Dependency.get(FeatureFlags.class),
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
Dependency.get(DialogLaunchAnimator.class)); Dependency.get(DialogLaunchAnimator.class));
} }
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
SystemUIDialogManager dialogManager, SysUiState sysUiState, FeatureFlags featureFlags,
BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) { SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
super(context, theme); super(context, theme);
mContext = context; mContext = context;
mFeatureFlags = featureFlags;
applyFlags(this); applyFlags(this);
WindowManager.LayoutParams attrs = getWindow().getAttributes(); 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++) { for (int i = 0; i < mOnCreateRunnables.size(); i++) {
mOnCreateRunnables.get(i).run(); 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() { private void updateWindowSize() {

View File

@@ -13,8 +13,9 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class) @RunWith(Parameterized::class)
@SmallTest @SmallTest
internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase) internal class FloatingRotationButtonPositionCalculatorTest(
: SysuiTestCase() { private val testCase: TestCase,
) : SysuiTestCase() {
@Test @Test
fun calculatePosition() { fun calculatePosition() {
@@ -34,11 +35,18 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase
val expectedPosition: Position val expectedPosition: Position
) { ) {
override fun toString(): String = override fun toString(): String =
"when calculator = $calculator, " + buildString {
"rotation = $rotation, " + append("when calculator = ")
"taskbarVisible = $taskbarVisible, " + append(when (calculator) {
"taskbarStashed = $taskbarStashed - " + posLeftCalculator -> "LEFT"
"expected $expectedPosition" posRightCalculator -> "RIGHT"
else -> error("Unknown calculator: $calculator")
})
append(", rotation = $rotation")
append(", taskbarVisible = $taskbarVisible")
append(", taskbarStashed = $taskbarStashed")
append(" - expected $expectedPosition")
}
} }
companion object { companion object {

View File

@@ -19,8 +19,10 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Intent; import android.content.Intent;
@@ -32,6 +34,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher; 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.Before;
import org.junit.Test; import org.junit.Test;
@@ -45,6 +49,8 @@ import org.mockito.MockitoAnnotations;
@SmallTest @SmallTest
public class SystemUIDialogTest extends SysuiTestCase { public class SystemUIDialogTest extends SysuiTestCase {
@Mock
private FeatureFlags mFeatureFlags;
@Mock @Mock
private BroadcastDispatcher mBroadcastDispatcher; private BroadcastDispatcher mBroadcastDispatcher;
@@ -52,6 +58,7 @@ public class SystemUIDialogTest extends SysuiTestCase {
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher); mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
} }
@@ -86,4 +93,20 @@ public class SystemUIDialogTest extends SysuiTestCase {
verify(mBroadcastDispatcher, never()).unregisterReceiver(any()); verify(mBroadcastDispatcher, never()).unregisterReceiver(any());
assertFalse(dialog.isShowing()); 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());
}
} }