Merge "Add Predictive Back animation to all SysUI dialogs" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f58340245e
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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() },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user