Animate power menu dialog from power button
This CL animates the power menu dialog from the power menu button instead of from the bottom. The animation has the same spec as the device default dialog animation (see http://ag/16769344), except for the direction of the translation. Note that this does not support devices that don't have the power button on the right. I will add support for that in a follow-up CL. See b/213872558#comment3 for a video. Bug: 213872558 Test: Manual Change-Id: I7d5f7d9f2ed2375814cb827275728ef9890a50ae
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
android:interpolator="@interpolator/standard"
|
||||
android:duration="@android:integer/config_activityDefaultDur" />
|
||||
<translate
|
||||
android:fromYDelta="20dp"
|
||||
android:fromYDelta="@android:dimen/popup_enter_animation_from_y_delta"
|
||||
android:toYDelta="0"
|
||||
android:interpolator="@interpolator/standard"
|
||||
android:duration="@android:integer/config_activityDefaultDur" />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
android:duration="@android:integer/config_activityShortDur" />
|
||||
<translate
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="-10dp"
|
||||
android:toYDelta="@android:dimen/popup_exit_animation_to_y_delta"
|
||||
android:interpolator="@interpolator/standard_accelerate"
|
||||
android:duration="@android:integer/config_activityShortDur" />
|
||||
</set>
|
||||
|
||||
@@ -994,4 +994,8 @@
|
||||
|
||||
<!-- Default size for user icons (a.k.a. avatar images) -->
|
||||
<dimen name="user_icon_size">190dp</dimen>
|
||||
|
||||
<!-- Dimensions for the translations of the default dialog animation. -->
|
||||
<dimen name="popup_enter_animation_from_y_delta">20dp</dimen>
|
||||
<dimen name="popup_exit_animation_to_y_delta">-10dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -2358,6 +2358,10 @@
|
||||
<java-symbol type="string" name="nas_upgrade_notification_learn_more_action" />
|
||||
<java-symbol type="string" name="nas_upgrade_notification_learn_more_content" />
|
||||
<java-symbol type="bool" name="config_settingsHelpLinksEnabled" />
|
||||
<java-symbol type="integer" name="config_activityDefaultDur" />
|
||||
<java-symbol type="integer" name="config_activityShortDur" />
|
||||
<java-symbol type="dimen" name="popup_enter_animation_from_y_delta" />
|
||||
<java-symbol type="dimen" name="popup_exit_animation_to_y_delta" />
|
||||
|
||||
<!-- ImfTest -->
|
||||
<java-symbol type="layout" name="auto_complete_list" />
|
||||
|
||||
@@ -399,6 +399,11 @@
|
||||
<!-- that would otherwise be intercepted by the Shade. -->
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
<!-- Empty enter/exit animation, we will animate in-window. Note that the implementation -->
|
||||
<!-- of ActionsDialogLite relies on this to be null (resource=0) to detect when to run -->
|
||||
<!-- the in-window animation. -->
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="QSBorderlessButton">
|
||||
|
||||
@@ -31,6 +31,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_G
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Dialog;
|
||||
@@ -76,8 +77,10 @@ import android.view.GestureDetector;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
@@ -109,6 +112,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.systemui.MultiListLayout;
|
||||
import com.android.systemui.MultiListLayout.MultiListAdapter;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
@@ -2170,6 +2174,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
|
||||
private Optional<StatusBar> mStatusBarOptional;
|
||||
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
private LockPatternUtils mLockPatternUtils;
|
||||
private float mWindowDimAmount;
|
||||
|
||||
protected ViewGroup mContainer;
|
||||
|
||||
@@ -2251,6 +2256,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initializeLayout();
|
||||
mWindowDimAmount = getWindow().getAttributes().dimAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2459,6 +2465,96 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
|
||||
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
|
||||
mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
|
||||
.commitUpdate(mContext.getDisplayId());
|
||||
|
||||
// By default this dialog windowAnimationStyle is null, and therefore windowAnimations
|
||||
// should be equal to 0 which means we need to animate the dialog in-window. If it's not
|
||||
// equal to 0, it means it has been overridden to animate (e.g. by the
|
||||
// DialogLaunchAnimator) so we don't run the animation.
|
||||
boolean shouldAnimateInWindow = getWindow().getAttributes().windowAnimations == 0;
|
||||
if (shouldAnimateInWindow) {
|
||||
startAnimation(true /* isEnter */, null /* then */);
|
||||
|
||||
// Override the dialog dismiss so that we can animate in-window before dismissing
|
||||
// the dialog.
|
||||
setDismissOverride(() -> {
|
||||
startAnimation(false /* isEnter */, /* then */ () -> {
|
||||
setDismissOverride(null);
|
||||
|
||||
// Hide then dismiss to instantly dismiss.
|
||||
hide();
|
||||
dismiss();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Run either the enter or exit animation, then run {@code then}. */
|
||||
private void startAnimation(boolean isEnter, @Nullable Runnable then) {
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
|
||||
|
||||
// Note: these specs should be the same as in popup_enter_material and
|
||||
// popup_exit_material.
|
||||
float translationPx;
|
||||
Resources resources = getContext().getResources();
|
||||
if (isEnter) {
|
||||
translationPx = resources.getDimension(R.dimen.popup_enter_animation_from_y_delta);
|
||||
animator.setInterpolator(Interpolators.STANDARD);
|
||||
animator.setDuration(resources.getInteger(R.integer.config_activityDefaultDur));
|
||||
} else {
|
||||
translationPx = resources.getDimension(R.dimen.popup_exit_animation_to_y_delta);
|
||||
animator.setInterpolator(Interpolators.STANDARD_ACCELERATE);
|
||||
animator.setDuration(resources.getInteger(R.integer.config_activityShortDur));
|
||||
}
|
||||
|
||||
Window window = getWindow();
|
||||
int rotation = window.getWindowManager().getDefaultDisplay().getRotation();
|
||||
|
||||
animator.addUpdateListener(valueAnimator -> {
|
||||
float progress = (float) valueAnimator.getAnimatedValue();
|
||||
|
||||
float alpha = isEnter ? progress : 1 - progress;
|
||||
mGlobalActionsLayout.setAlpha(alpha);
|
||||
window.setDimAmount(mWindowDimAmount * alpha);
|
||||
|
||||
// TODO(b/213872558): Support devices that don't have their power button on the
|
||||
// right.
|
||||
float translation =
|
||||
isEnter ? translationPx * (1 - progress) : translationPx * progress;
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0:
|
||||
mGlobalActionsLayout.setTranslationX(translation);
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
mGlobalActionsLayout.setTranslationY(-translation);
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
mGlobalActionsLayout.setTranslationX(-translation);
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
mGlobalActionsLayout.setTranslationY(translation);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
private int mPreviousLayerType;
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation, boolean isReverse) {
|
||||
mPreviousLayerType = mGlobalActionsLayout.getLayerType();
|
||||
mGlobalActionsLayout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mGlobalActionsLayout.setLayerType(mPreviousLayerType, null);
|
||||
if (then != null) {
|
||||
then.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,7 @@ import junit.framework.Assert.assertNotNull
|
||||
import junit.framework.Assert.assertNull
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -145,7 +146,31 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDialogAnimationIsChangedByAnimator() {
|
||||
// Important: the power menu animation relies on this behavior to know when to animate (see
|
||||
// http://ag/16774605).
|
||||
val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) }
|
||||
dialog.window.setWindowAnimations(0)
|
||||
assertEquals(0, dialog.window.attributes.windowAnimations)
|
||||
|
||||
val touchSurface = createTouchSurface()
|
||||
runOnMainThreadAndWaitForIdleSync {
|
||||
dialogLaunchAnimator.showFromView(dialog, touchSurface)
|
||||
}
|
||||
assertNotEquals(0, dialog.window.attributes.windowAnimations)
|
||||
}
|
||||
|
||||
private fun createAndShowDialog(): TestDialog {
|
||||
val touchSurface = createTouchSurface()
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val dialog = TestDialog(context)
|
||||
dialogLaunchAnimator.showFromView(dialog, touchSurface)
|
||||
dialog
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTouchSurface(): View {
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val touchSurfaceRoot = LinearLayout(context)
|
||||
val touchSurface = View(context)
|
||||
@@ -156,9 +181,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
ViewUtils.attachView(touchSurfaceRoot)
|
||||
attachedViews.add(touchSurfaceRoot)
|
||||
|
||||
val dialog = TestDialog(context)
|
||||
dialogLaunchAnimator.showFromView(dialog, touchSurface)
|
||||
dialog
|
||||
touchSurface
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user