Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68d18498 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Animate dialog stack and use in UserSwitcher"

parents a8d31ebb 6bdb2f1f
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -16,4 +16,5 @@
-->
-->
<resources>
<resources>
    <item type="id" name="launch_animation_running"/>
    <item type="id" name="launch_animation_running"/>
    <item type="id" name="dialog_content_parent" />
</resources>
</resources>
 No newline at end of file
+76 −27
Original line number Original line Diff line number Diff line
@@ -40,6 +40,7 @@ import android.widget.FrameLayout
import kotlin.math.roundToInt
import kotlin.math.roundToInt


private const val TAG = "DialogLaunchAnimator"
private const val TAG = "DialogLaunchAnimator"
private val DIALOG_CONTENT_PARENT_ID = R.id.dialog_content_parent


/**
/**
 * A class that allows dialogs to be started in a seamless way from a view that is transforming
 * A class that allows dialogs to be started in a seamless way from a view that is transforming
@@ -86,10 +87,10 @@ class DialogLaunchAnimator(
        // If the parent of the view we are launching from is the background of some other animated
        // If the parent of the view we are launching from is the background of some other animated
        // dialog, then this means the caller intent is to launch a dialog from another dialog. In
        // dialog, then this means the caller intent is to launch a dialog from another dialog. In
        // this case, we also animate the parent (which is the dialog background).
        // this case, we also animate the parent (which is the dialog background).
        val dialogContentParent = openedDialogs
        val animatedParent = openedDialogs
            .firstOrNull { it.dialogContentParent == view.parent }
            .firstOrNull { it.dialogContentParent == view.parent }
            ?.dialogContentParent
        val parentHostDialog = animatedParent?.hostDialog
        val animateFrom = dialogContentParent ?: view
        val animateFrom = animatedParent?.dialogContentParent ?: view


        // Make sure we don't run the launch animation from the same view twice at the same time.
        // Make sure we don't run the launch animation from the same view twice at the same time.
        if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
        if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
@@ -100,12 +101,18 @@ class DialogLaunchAnimator(


        animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
        animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)


        val launchAnimation = AnimatedDialog(
        val animatedDialog = AnimatedDialog(
            context, launchAnimator, hostDialogProvider, animateFrom,
                context,
            onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog,
                launchAnimator,
            animateBackgroundBoundsChange)
                hostDialogProvider,
        val hostDialog = launchAnimation.hostDialog
                animateFrom,
        openedDialogs.add(launchAnimation)
                onDialogDismissed = { openedDialogs.remove(it) },
                originalDialog = dialog,
                animateBackgroundBoundsChange,
                openedDialogs.firstOrNull { it.hostDialog == parentHostDialog }
        )
        val hostDialog = animatedDialog.hostDialog
        openedDialogs.add(animatedDialog)


        // If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the
        // If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the
        // host dialog.
        // host dialog.
@@ -119,15 +126,15 @@ class DialogLaunchAnimator(
                    // If AOD is disabled the screen will directly becomes black and we won't see
                    // If AOD is disabled the screen will directly becomes black and we won't see
                    // the animation anyways.
                    // the animation anyways.
                    if (reason == DialogListener.DismissReason.DEVICE_LOCKED) {
                    if (reason == DialogListener.DismissReason.DEVICE_LOCKED) {
                        launchAnimation.exitAnimationDisabled = true
                        animatedDialog.exitAnimationDisabled = true
                    }
                    }


                    hostDialog.dismiss()
                    hostDialog.dismiss()
                }
                }


                override fun onHide() {
                override fun onHide() {
                    if (launchAnimation.ignoreNextCallToHide) {
                    if (animatedDialog.ignoreNextCallToHide) {
                        launchAnimation.ignoreNextCallToHide = false
                        animatedDialog.ignoreNextCallToHide = false
                        return
                        return
                    }
                    }


@@ -138,20 +145,43 @@ class DialogLaunchAnimator(
                    hostDialog.show()
                    hostDialog.show()


                    // We don't actually want to show the original dialog, so hide it.
                    // We don't actually want to show the original dialog, so hide it.
                    launchAnimation.ignoreNextCallToHide = true
                    animatedDialog.ignoreNextCallToHide = true
                    dialog.hide()
                    dialog.hide()
                }
                }


                override fun onSizeChanged() {
                override fun onSizeChanged() {
                    launchAnimation.onOriginalDialogSizeChanged()
                    animatedDialog.onOriginalDialogSizeChanged()
                }

                override fun prepareForStackDismiss() {
                    animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
                }
                }
            })
            })
        }
        }


        launchAnimation.start()
        animatedDialog.start()
        return hostDialog
        return hostDialog
    }
    }


    /**
     * Launch [dialog] from a [parentHostDialog] as returned by [showFromView]. This will allow
     * for dismissing the whole stack.
     *
     * This will return a new host dialog, with the same caveat as [showFromView].
     *
     * @see DialogListener.prepareForStackDismiss
     */
    fun showFromDialog(
        dialog: Dialog,
        parentHostDialog: Dialog,
        animateBackgroundBoundsChange: Boolean = false
    ): Dialog {
        val view = parentHostDialog.findViewById<ViewGroup>(DIALOG_CONTENT_PARENT_ID)
                ?.getChildAt(0)
                ?: throw IllegalStateException("No dialog content parent found in host dialog")
        return showFromView(dialog, view, animateBackgroundBoundsChange)
    }

    /**
    /**
     * Ensure that all dialogs currently shown won't animate into their touch surface when
     * Ensure that all dialogs currently shown won't animate into their touch surface when
     * dismissed.
     * dismissed.
@@ -214,6 +244,12 @@ interface DialogListener {
    /** Called when this dialog show() is called. */
    /** Called when this dialog show() is called. */
    fun onShow()
    fun onShow()


    /**
     * Call before dismissing a stack of dialogs (dialogs launched from dialogs), so the topmost
     * can animate directly into the original `touchSurface`.
     */
    fun prepareForStackDismiss()

    /** Called when this dialog size might have changed, e.g. because of configuration changes. */
    /** Called when this dialog size might have changed, e.g. because of configuration changes. */
    fun onSizeChanged()
    fun onSizeChanged()
}
}
@@ -224,7 +260,7 @@ private class AnimatedDialog(
    hostDialogProvider: HostDialogProvider,
    hostDialogProvider: HostDialogProvider,


    /** The view that triggered the dialog after being tapped. */
    /** The view that triggered the dialog after being tapped. */
    private val touchSurface: View,
    var touchSurface: View,


    /**
    /**
     * A callback that will be called with this [AnimatedDialog] after the dialog was
     * A callback that will be called with this [AnimatedDialog] after the dialog was
@@ -236,7 +272,10 @@ private class AnimatedDialog(
    private val originalDialog: Dialog,
    private val originalDialog: Dialog,


    /** Whether we should animate the dialog background when its bounds change. */
    /** Whether we should animate the dialog background when its bounds change. */
    private val animateBackgroundBoundsChange: Boolean
    private val animateBackgroundBoundsChange: Boolean,

    /** Launch animation corresponding to the parent [hostDialog]. */
    private val parentAnimatedDialog: AnimatedDialog? = null
) {
) {
    /**
    /**
     * The fullscreen dialog to which we will add the content view [originalDialogView] of
     * The fullscreen dialog to which we will add the content view [originalDialogView] of
@@ -253,7 +292,9 @@ private class AnimatedDialog(
     * the same size as the original dialog window and to which we will set the original dialog
     * the same size as the original dialog window and to which we will set the original dialog
     * window background.
     * window background.
     */
     */
    val dialogContentParent = FrameLayout(context)
    val dialogContentParent = FrameLayout(context).apply {
        id = DIALOG_CONTENT_PARENT_ID
    }


    /**
    /**
     * The background color of [originalDialogView], taking into consideration the [originalDialog]
     * The background color of [originalDialogView], taking into consideration the [originalDialog]
@@ -359,9 +400,7 @@ private class AnimatedDialog(
        // Make the touch surface invisible and make sure that it stays invisible as long as the
        // Make the touch surface invisible and make sure that it stays invisible as long as the
        // dialog is shown or animating.
        // dialog is shown or animating.
        touchSurface.visibility = View.INVISIBLE
        touchSurface.visibility = View.INVISIBLE
        if (touchSurface is LaunchableView) {
        (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
            touchSurface.setShouldBlockVisibilityChanges(true)
        }


        // Add a pre draw listener to (maybe) start the animation once the touch surface is
        // Add a pre draw listener to (maybe) start the animation once the touch surface is
        // actually invisible.
        // actually invisible.
@@ -576,9 +615,7 @@ private class AnimatedDialog(
            Log.i(TAG, "Skipping animation of dialog into the touch surface")
            Log.i(TAG, "Skipping animation of dialog into the touch surface")


            // Make sure we allow the touch surface to change its visibility again.
            // Make sure we allow the touch surface to change its visibility again.
            if (touchSurface is LaunchableView) {
            (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
                touchSurface.setShouldBlockVisibilityChanges(false)
            }


            // If the view is invisible it's probably because of us, so we make it visible again.
            // If the view is invisible it's probably because of us, so we make it visible again.
            if (touchSurface.visibility == View.INVISIBLE) {
            if (touchSurface.visibility == View.INVISIBLE) {
@@ -598,9 +635,7 @@ private class AnimatedDialog(
            },
            },
            onLaunchAnimationEnd = {
            onLaunchAnimationEnd = {
                // Make sure we allow the touch surface to change its visibility again.
                // Make sure we allow the touch surface to change its visibility again.
                if (touchSurface is LaunchableView) {
                (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
                    touchSurface.setShouldBlockVisibilityChanges(false)
                }


                touchSurface.visibility = View.VISIBLE
                touchSurface.visibility = View.VISIBLE
                dialogContentParent.visibility = View.INVISIBLE
                dialogContentParent.visibility = View.INVISIBLE
@@ -796,4 +831,18 @@ private class AnimatedDialog(
            animator.start()
            animator.start()
        }
        }
    }
    }

    fun prepareForStackDismiss(): View {
        if (parentAnimatedDialog == null) {
            return touchSurface
        }
        parentAnimatedDialog.exitAnimationDisabled = true
        parentAnimatedDialog.originalDialog.hide()
        val view = parentAnimatedDialog.prepareForStackDismiss()
        parentAnimatedDialog.originalDialog.dismiss()
        // Make the touch surface invisible, so we end up animating to it when we actually
        // dismiss the stack
        view.visibility = View.INVISIBLE
        return view
    }
}
}
+17 −9
Original line number Original line Diff line number Diff line
@@ -29,6 +29,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup;


import androidx.annotation.Nullable;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -38,10 +40,10 @@ import com.android.systemui.R;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.UserSwitcherController;


import java.util.function.Consumer;

import javax.inject.Inject;
import javax.inject.Inject;


/**
/**
@@ -78,7 +80,7 @@ public class UserDetailView extends PseudoGridView {
        private View mCurrentUserView;
        private View mCurrentUserView;
        private final UiEventLogger mUiEventLogger;
        private final UiEventLogger mUiEventLogger;
        private final FalsingManager mFalsingManager;
        private final FalsingManager mFalsingManager;
        private Consumer<UserSwitcherController.UserRecord> mClickCallback;
        private @Nullable UserSwitchDialogController.DialogShower mDialogShower;


        @Inject
        @Inject
        public Adapter(Context context, UserSwitcherController controller,
        public Adapter(Context context, UserSwitcherController controller,
@@ -96,8 +98,17 @@ public class UserDetailView extends PseudoGridView {
            return createUserDetailItemView(convertView, parent, item);
            return createUserDetailItemView(convertView, parent, item);
        }
        }


        public void injectCallback(Consumer<UserSwitcherController.UserRecord> clickCallback) {
        /**
            mClickCallback = clickCallback;
         * If this adapter is inside a dialog, passing a
         * {@link UserSwitchDialogController.DialogShower} will help animate to and from the parent
         * dialog. This will also allow for dismissing the whole stack of dialogs in a single
         * animation.
         *
         * @param shower
         * @see SystemUIDialog#dismissStack()
         */
        public void injectDialogShower(UserSwitchDialogController.DialogShower shower) {
            mDialogShower = shower;
        }
        }


        public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
        public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
@@ -174,12 +185,9 @@ public class UserDetailView extends PseudoGridView {
                    }
                    }
                    view.setActivated(true);
                    view.setActivated(true);
                }
                }
                onUserListItemClicked(tag);
                onUserListItemClicked(tag, mDialogShower);
            }
            }
            Trace.endSection();
            Trace.endSection();
            if (mClickCallback != null) {
                mClickCallback.accept(tag);
            }
        }
        }


        public void linkToViewGroup(ViewGroup viewGroup) {
        public void linkToViewGroup(ViewGroup viewGroup) {
+20 −4
Original line number Original line Diff line number Diff line
@@ -16,7 +16,9 @@


package com.android.systemui.qs.user
package com.android.systemui.qs.user


import android.app.Dialog
import android.content.Context
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.Intent
import android.provider.Settings
import android.provider.Settings
import android.view.View
import android.view.View
@@ -84,12 +86,26 @@ class UserSwitchDialogController @VisibleForTesting constructor(
            doneButton.setOnClickListener { dismiss() }
            doneButton.setOnClickListener { dismiss() }


            val adapter = userDetailViewAdapterProvider.get()
            val adapter = userDetailViewAdapterProvider.get()
            adapter.injectCallback {
                dismiss()
            }
            adapter.linkToViewGroup(grid)
            adapter.linkToViewGroup(grid)


            dialogLaunchAnimator.showFromView(this, view)
            val hostDialog = dialogLaunchAnimator.showFromView(this, view)
            adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
        }
    }

    private class DialogShowerImpl(
        private val hostDialog: Dialog,
        private val dialogLaunchAnimator: DialogLaunchAnimator
    ) : DialogInterface by hostDialog, DialogShower {
        override fun showDialog(dialog: Dialog): Dialog {
            return dialogLaunchAnimator.showFromDialog(
                dialog,
                parentHostDialog = hostDialog
            )
        }
        }
    }
    }

    interface DialogShower : DialogInterface {
        fun showDialog(dialog: Dialog): Dialog
    }
}
}
 No newline at end of file
+13 −0
Original line number Original line Diff line number Diff line
@@ -218,6 +218,19 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
        }
        }
    }
    }


    /**
     * Dismiss this dialog. If it was launched from another dialog using
     * {@link com.android.systemui.animation.DialogLaunchAnimator#showFromView} with a
     * non-{@code null} {@code parentHostDialog} parameter, also dismisses the stack of dialogs,
     * animating back to the original touchSurface.
     */
    public void dismissStack() {
        for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
            listener.prepareForStackDismiss();
        }
        dismiss();
    }

    @Override
    @Override
    public void hide() {
    public void hide() {
        super.hide();
        super.hide();
Loading