Merge changes from topic "dialog-refactor" into sc-v2-dev
* changes: Animate the cast dialog Refactor DialogLaunchAnimator and remove host dialog (2/2) Refactor DialogLaunchAnimator and remove host dialog (1/2)
This commit is contained in:
committed by
Android (Google) Code Review
commit
9f02d2fd20
@@ -151,6 +151,9 @@ public class Dialog implements DialogInterface, Window.Callback,
|
||||
|
||||
private final Runnable mDismissAction = this::dismissDialog;
|
||||
|
||||
/** A {@link Runnable} to run instead of dismissing when {@link #dismiss()} is called. */
|
||||
private Runnable mDismissOverride;
|
||||
|
||||
/**
|
||||
* Creates a dialog window that uses the default dialog theme.
|
||||
* <p>
|
||||
@@ -370,6 +373,11 @@ public class Dialog implements DialogInterface, Window.Callback,
|
||||
*/
|
||||
@Override
|
||||
public void dismiss() {
|
||||
if (mDismissOverride != null) {
|
||||
mDismissOverride.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Looper.myLooper() == mHandler.getLooper()) {
|
||||
dismissDialog();
|
||||
} else {
|
||||
@@ -1354,6 +1362,21 @@ public class Dialog implements DialogInterface, Window.Callback,
|
||||
mDismissMessage = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link Runnable} to run when this dialog is dismissed instead of directly dismissing
|
||||
* it. This allows to animate the dialog in its window before dismissing it.
|
||||
*
|
||||
* Note that {@code override} should always end up calling this method with {@code null}
|
||||
* followed by a call to {@link #dismiss() dismiss} to actually dismiss the dialog.
|
||||
*
|
||||
* @see #dismiss()
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setDismissOverride(@Nullable Runnable override) {
|
||||
mDismissOverride = override;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean takeCancelAndDismissListeners(@Nullable String msg,
|
||||
@Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) {
|
||||
|
||||
@@ -16,5 +16,4 @@
|
||||
-->
|
||||
<resources>
|
||||
<item type="id" name="launch_animation_running"/>
|
||||
<item type="id" name="dialog_content_parent" />
|
||||
</resources>
|
||||
@@ -15,15 +15,10 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<style name="HostDialogTheme">
|
||||
<item name="android:windowAnimationStyle">@style/Animation.HostDialog</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Animation.HostDialog" parent="@android:style/Animation">
|
||||
<item name="android:windowEnterAnimation">@anim/launch_host_dialog_enter</item>
|
||||
<item name="android:windowExitAnimation">@anim/launch_host_dialog_exit</item>
|
||||
<!-- An animation used by DialogLaunchAnimator to make a dialog appear instantly (to animate -->
|
||||
<!-- in-window) and disappear by fading out (when the exit into view is disabled). -->
|
||||
<style name="Animation.LaunchAnimation" parent="@android:style/Animation">
|
||||
<item name="android:windowEnterAnimation">@anim/launch_dialog_enter</item>
|
||||
<item name="android:windowExitAnimation">@anim/launch_dialog_exit</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -24,22 +24,19 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.os.Looper
|
||||
import android.service.dreams.IDreamManager
|
||||
import android.util.Log
|
||||
import android.util.MathUtils
|
||||
import android.view.GhostView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
|
||||
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
import android.view.WindowManagerPolicyConstants
|
||||
import android.widget.FrameLayout
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
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
|
||||
@@ -48,7 +45,7 @@ private val DIALOG_CONTENT_PARENT_ID = R.id.dialog_content_parent
|
||||
class DialogLaunchAnimator(
|
||||
private val context: Context,
|
||||
private val launchAnimator: LaunchAnimator,
|
||||
private val hostDialogProvider: HostDialogProvider
|
||||
private val dreamManager: IDreamManager
|
||||
) {
|
||||
private companion object {
|
||||
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
|
||||
@@ -62,41 +59,38 @@ class DialogLaunchAnimator(
|
||||
private val openedDialogs = hashSetOf<AnimatedDialog>()
|
||||
|
||||
/**
|
||||
* Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then
|
||||
* the background of the dialog will be animated when the dialog bounds change.
|
||||
* Show [dialog] by expanding it from [view]. If [view] is a view inside another dialog that was
|
||||
* shown using this method, then we will animate from that dialog instead.
|
||||
*
|
||||
* Caveats: When calling this function, the dialog content view will actually be stolen and
|
||||
* attached to a different dialog (and thus a different window) which means that the actual
|
||||
* dialog window will never be drawn. Moreover, unless [dialog] is a [ListenableDialog], you
|
||||
* must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually
|
||||
* dismiss, hide or show the dialog.
|
||||
* If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
|
||||
* animated when the dialog bounds change.
|
||||
*
|
||||
* Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
|
||||
* made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun showFromView(
|
||||
dialog: Dialog,
|
||||
view: View,
|
||||
animateBackgroundBoundsChange: Boolean = false
|
||||
): Dialog {
|
||||
) {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
throw IllegalStateException(
|
||||
"showFromView must be called from the main thread and dialog must be created in " +
|
||||
"the main thread")
|
||||
}
|
||||
|
||||
// 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
|
||||
// this case, we also animate the parent (which is the dialog background).
|
||||
val animatedParent = openedDialogs.firstOrNull {
|
||||
it.dialogContentWithBackground == view || it.dialogContentWithBackground == view.parent
|
||||
}
|
||||
val dialogContentWithBackground = animatedParent?.dialogContentWithBackground
|
||||
val animateFrom = dialogContentWithBackground ?: view
|
||||
// If the view we are launching from belongs to another dialog, then this means the caller
|
||||
// intent is to launch a dialog from another dialog.
|
||||
val animatedParent = openedDialogs
|
||||
.firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
|
||||
val animateFrom = animatedParent?.dialogContentWithBackground ?: view
|
||||
|
||||
// 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) {
|
||||
Log.e(TAG, "Not running dialog launch animation as there is already one running")
|
||||
dialog.show()
|
||||
return dialog
|
||||
return
|
||||
}
|
||||
|
||||
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
||||
@@ -104,82 +98,36 @@ class DialogLaunchAnimator(
|
||||
val animatedDialog = AnimatedDialog(
|
||||
context,
|
||||
launchAnimator,
|
||||
hostDialogProvider,
|
||||
dreamManager,
|
||||
animateFrom,
|
||||
onDialogDismissed = { openedDialogs.remove(it) },
|
||||
originalDialog = dialog,
|
||||
dialog = dialog,
|
||||
animateBackgroundBoundsChange,
|
||||
animatedParent
|
||||
)
|
||||
val hostDialog = animatedDialog.hostDialog
|
||||
|
||||
openedDialogs.add(animatedDialog)
|
||||
|
||||
// If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the
|
||||
// host dialog.
|
||||
if (dialog is ListenableDialog) {
|
||||
dialog.addListener(object : DialogListener {
|
||||
override fun onDismiss(reason: DialogListener.DismissReason) {
|
||||
dialog.removeListener(this)
|
||||
|
||||
// We disable the exit animation if we are dismissing the dialog because the
|
||||
// device is being locked, otherwise the animation looks bad if AOD is enabled.
|
||||
// If AOD is disabled the screen will directly becomes black and we won't see
|
||||
// the animation anyways.
|
||||
if (reason == DialogListener.DismissReason.DEVICE_LOCKED) {
|
||||
animatedDialog.exitAnimationDisabled = true
|
||||
}
|
||||
|
||||
hostDialog.dismiss()
|
||||
}
|
||||
|
||||
override fun onHide() {
|
||||
if (animatedDialog.ignoreNextCallToHide) {
|
||||
animatedDialog.ignoreNextCallToHide = false
|
||||
return
|
||||
}
|
||||
|
||||
hostDialog.hide()
|
||||
}
|
||||
|
||||
override fun onShow() {
|
||||
hostDialog.show()
|
||||
|
||||
// We don't actually want to show the original dialog, so hide it.
|
||||
animatedDialog.ignoreNextCallToHide = true
|
||||
dialog.hide()
|
||||
}
|
||||
|
||||
override fun onSizeChanged() {
|
||||
animatedDialog.onOriginalDialogSizeChanged()
|
||||
}
|
||||
|
||||
override fun prepareForStackDismiss() {
|
||||
animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
animatedDialog.start()
|
||||
return hostDialog
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch [dialog] from a [parentHostDialog] as returned by [showFromView]. This will allow
|
||||
* for dismissing the whole stack.
|
||||
* Launch [dialog] from [another dialog][animateFrom] that was shown using [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
|
||||
* @see dismissStack
|
||||
*/
|
||||
fun showFromDialog(
|
||||
dialog: Dialog,
|
||||
parentHostDialog: Dialog,
|
||||
animateFrom: 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)
|
||||
) {
|
||||
val view = openedDialogs
|
||||
.firstOrNull { it.dialog == animateFrom }
|
||||
?.dialogContentWithBackground
|
||||
?: throw IllegalStateException(
|
||||
"The animateFrom dialog was not animated using " +
|
||||
"DialogLaunchAnimator.showFrom(View|Dialog)")
|
||||
showFromView(dialog, view, animateBackgroundBoundsChange)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,69 +143,23 @@ class DialogLaunchAnimator(
|
||||
fun disableAllCurrentDialogsExitAnimations() {
|
||||
openedDialogs.forEach { it.exitAnimationDisabled = true }
|
||||
}
|
||||
}
|
||||
|
||||
interface HostDialogProvider {
|
||||
/**
|
||||
* Create a host dialog that will be used to host a launch animation. This host dialog must:
|
||||
* 1. call [onCreateCallback] in its onCreate() method, e.g. right after calling
|
||||
* super.onCreate().
|
||||
* 2. call [dismissOverride] instead of doing any dismissing logic. The actual dismissing
|
||||
* logic should instead be done inside the lambda passed to [dismissOverride], which will
|
||||
* be called after the exit animation.
|
||||
* 3. Be full screen, i.e. have a window matching its parent size.
|
||||
*
|
||||
* See SystemUIHostDialogProvider for an example of implementation.
|
||||
* Dismiss [dialog]. If it was launched from another dialog using [showFromView], also dismiss
|
||||
* the stack of dialogs, animating back to the original touchSurface.
|
||||
*/
|
||||
fun createHostDialog(
|
||||
context: Context,
|
||||
theme: Int,
|
||||
onCreateCallback: () -> Unit,
|
||||
dismissOverride: (() -> Unit) -> Unit
|
||||
): Dialog
|
||||
}
|
||||
|
||||
/** A dialog to/from which we can add/remove listeners. */
|
||||
interface ListenableDialog {
|
||||
/** Add [listener] to the listeners. */
|
||||
fun addListener(listener: DialogListener)
|
||||
|
||||
/** Remove [listener] from the listeners. */
|
||||
fun removeListener(listener: DialogListener)
|
||||
}
|
||||
|
||||
interface DialogListener {
|
||||
/** The reason why a dialog was dismissed. */
|
||||
enum class DismissReason {
|
||||
UNKNOWN,
|
||||
|
||||
/** The device was locked, which dismissed this dialog. */
|
||||
DEVICE_LOCKED,
|
||||
fun dismissStack(dialog: Dialog) {
|
||||
openedDialogs
|
||||
.firstOrNull { it.dialog == dialog }
|
||||
?.let { it.touchSurface = it.prepareForStackDismiss() }
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
/** Called when this dialog dismiss() is called. */
|
||||
fun onDismiss(reason: DismissReason)
|
||||
|
||||
/** Called when this dialog hide() is called. */
|
||||
fun onHide()
|
||||
|
||||
/** Called when this dialog show() is called. */
|
||||
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. */
|
||||
fun onSizeChanged()
|
||||
}
|
||||
|
||||
private class AnimatedDialog(
|
||||
private val context: Context,
|
||||
private val launchAnimator: LaunchAnimator,
|
||||
hostDialogProvider: HostDialogProvider,
|
||||
private val dreamManager: IDreamManager,
|
||||
|
||||
/** The view that triggered the dialog after being tapped. */
|
||||
var touchSurface: View,
|
||||
@@ -268,36 +170,33 @@ private class AnimatedDialog(
|
||||
*/
|
||||
private val onDialogDismissed: (AnimatedDialog) -> Unit,
|
||||
|
||||
/** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
|
||||
private val originalDialog: Dialog,
|
||||
/** The dialog to show and animate. */
|
||||
val dialog: Dialog,
|
||||
|
||||
/** Whether we should animate the dialog background when its bounds change. */
|
||||
private val animateBackgroundBoundsChange: Boolean,
|
||||
|
||||
/** Launch animation corresponding to the parent [hostDialog]. */
|
||||
/** Launch animation corresponding to the parent [AnimatedDialog]. */
|
||||
private val parentAnimatedDialog: AnimatedDialog? = null
|
||||
) {
|
||||
/**
|
||||
* The fullscreen dialog to which we will add the content view [originalDialogView] of
|
||||
* [originalDialog].
|
||||
*/
|
||||
val hostDialog = hostDialogProvider.createHostDialog(
|
||||
context, R.style.HostDialogTheme, this::onHostDialogCreated, this::onHostDialogDismissed)
|
||||
|
||||
/** The root content view of [hostDialog]. */
|
||||
private val hostDialogRoot = FrameLayout(context)
|
||||
* The DecorView of this dialog window.
|
||||
*
|
||||
* Note that we access this DecorView lazily to avoid accessing it before the dialog is created,
|
||||
* which can sometimes cause crashes (e.g. with the Cast dialog).
|
||||
*/
|
||||
private val decorView by lazy { dialog.window!!.decorView as ViewGroup }
|
||||
|
||||
/**
|
||||
* The dialog content with its background. When animating a fullscreen dialog, this is just the
|
||||
* first ViewGroup of the dialog that has a background. When animating a normal (not fullscreen)
|
||||
* dialog, this is an additional view that serves as a fake window that will have the same size
|
||||
* as the original dialog window and to which we will set the original dialog window background.
|
||||
* as the dialog window initially had and to which we will set the dialog window background.
|
||||
*/
|
||||
var dialogContentWithBackground: ViewGroup? = null
|
||||
|
||||
/**
|
||||
* The background color of [originalDialogView], taking into consideration the [originalDialog]
|
||||
* window background color.
|
||||
* The background color of [dialog], taking into consideration its window background color.
|
||||
*/
|
||||
private var originalDialogBackgroundColor = Color.BLACK
|
||||
|
||||
@@ -310,75 +209,182 @@ private class AnimatedDialog(
|
||||
private var isDismissing = false
|
||||
|
||||
private var dismissRequested = false
|
||||
var ignoreNextCallToHide = false
|
||||
var exitAnimationDisabled = false
|
||||
|
||||
private var isTouchSurfaceGhostDrawn = false
|
||||
private var isOriginalDialogViewLaidOut = false
|
||||
private var backgroundLayoutListener = if (animateBackgroundBoundsChange) {
|
||||
|
||||
/** A layout listener to animate the dialog height change. */
|
||||
private val backgroundLayoutListener = if (animateBackgroundBoundsChange) {
|
||||
AnimatedBoundsLayoutListener()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/*
|
||||
* A layout listener in case the dialog (window) size changes (for instance because of a
|
||||
* configuration change) to ensure that the dialog stays full width.
|
||||
*/
|
||||
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
|
||||
|
||||
fun start() {
|
||||
// Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
|
||||
hostDialog.show()
|
||||
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
|
||||
// content.
|
||||
dialog.create()
|
||||
|
||||
// Steal the dialog view. We do that by showing it but preventing it from drawing, then
|
||||
// hiding it as soon as its content is available.
|
||||
stealOriginalDialogContentView(then = this::showDialogFromView)
|
||||
}
|
||||
val window = dialog.window!!
|
||||
val isWindowFullScreen =
|
||||
window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
|
||||
val dialogContentWithBackground = if (isWindowFullScreen) {
|
||||
// If the dialog window is already fullscreen, then we look for the first ViewGroup that
|
||||
// has a background (and is not the DecorView, which always has a background) and
|
||||
// animate towards that ViewGroup given that this is probably what represents the actual
|
||||
// dialog view.
|
||||
var viewGroupWithBackground: ViewGroup? = null
|
||||
for (i in 0 until decorView.childCount) {
|
||||
viewGroupWithBackground = findFirstViewGroupWithBackground(decorView.getChildAt(i))
|
||||
if (viewGroupWithBackground != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private fun onHostDialogCreated() {
|
||||
// Make the dialog fullscreen with a transparent background.
|
||||
hostDialog.setContentView(
|
||||
hostDialogRoot,
|
||||
ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
// Animate that view with the background. Throw if we didn't find one, because otherwise
|
||||
// it's not clear what we should animate.
|
||||
viewGroupWithBackground
|
||||
?: throw IllegalStateException("Unable to find ViewGroup with background")
|
||||
} else {
|
||||
// We will make the dialog window (and therefore its DecorView) fullscreen to make it
|
||||
// possible to animate outside its bounds.
|
||||
//
|
||||
// Before that, we add a new View as a child of the DecorView with the same size and
|
||||
// gravity as that DecorView, then we add all original children of the DecorView to that
|
||||
// new View. Finally we remove the background of the DecorView and add it to the new
|
||||
// View, then we make the DecorView fullscreen. This new View now acts as a fake (non
|
||||
// fullscreen) window.
|
||||
//
|
||||
// On top of that, we also add a fullscreen transparent background between the DecorView
|
||||
// and the view that we added so that we can dismiss the dialog when this view is
|
||||
// clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
|
||||
// can't set the click listener directly on the (now fullscreen) DecorView.
|
||||
val fullscreenTransparentBackground = FrameLayout(context)
|
||||
decorView.addView(
|
||||
fullscreenTransparentBackground,
|
||||
0 /* index */,
|
||||
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
)
|
||||
)
|
||||
|
||||
val window = hostDialog.window
|
||||
?: throw IllegalStateException("There is no window associated to the host dialog")
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
val dialogContentWithBackground = FrameLayout(context)
|
||||
dialogContentWithBackground.background = decorView.background
|
||||
|
||||
// If we are using gesture navigation, then we can overlay the navigation/task bars with
|
||||
// the host dialog.
|
||||
val navigationMode = context.resources.getInteger(
|
||||
com.android.internal.R.integer.config_navBarInteractionMode)
|
||||
if (navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) {
|
||||
window.attributes.fitInsetsTypes = window.attributes.fitInsetsTypes and
|
||||
WindowInsets.Type.navigationBars().inv()
|
||||
window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_INSET_DECOR)
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
// Make the window background transparent. Note that setting the window (or DecorView)
|
||||
// background drawable to null leads to issues with background color (not being
|
||||
// transparent) or with insets that are not refreshed. Therefore we need to set it to
|
||||
// something not null, hence we are using android.R.color.transparent here.
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
|
||||
// Close the dialog when clicking outside of it.
|
||||
fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
|
||||
dialogContentWithBackground.isClickable = true
|
||||
|
||||
fullscreenTransparentBackground.addView(
|
||||
dialogContentWithBackground,
|
||||
FrameLayout.LayoutParams(
|
||||
window.attributes.width,
|
||||
window.attributes.height,
|
||||
window.attributes.gravity
|
||||
)
|
||||
)
|
||||
|
||||
// Move all original children of the DecorView to the new View we just added.
|
||||
for (i in 1 until decorView.childCount) {
|
||||
val view = decorView.getChildAt(1)
|
||||
decorView.removeViewAt(1)
|
||||
dialogContentWithBackground.addView(view)
|
||||
}
|
||||
|
||||
// Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
|
||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||
decorViewLayoutListener = View.OnLayoutChangeListener {
|
||||
v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
||||
if (window.attributes.width != MATCH_PARENT ||
|
||||
window.attributes.height != MATCH_PARENT) {
|
||||
// The dialog size changed, copy its size to dialogContentWithBackground and
|
||||
// make the dialog window full screen again.
|
||||
val layoutParams = dialogContentWithBackground.layoutParams
|
||||
layoutParams.width = window.attributes.width
|
||||
layoutParams.height = window.attributes.height
|
||||
dialogContentWithBackground.layoutParams = layoutParams
|
||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
}
|
||||
decorView.addOnLayoutChangeListener(decorViewLayoutListener)
|
||||
|
||||
dialogContentWithBackground
|
||||
}
|
||||
this.dialogContentWithBackground = dialogContentWithBackground
|
||||
|
||||
val background = dialogContentWithBackground.background
|
||||
originalDialogBackgroundColor =
|
||||
GhostedViewLaunchAnimatorController.findGradientDrawable(background)
|
||||
?.color
|
||||
?.defaultColor ?: Color.BLACK
|
||||
|
||||
// Make the background view invisible until we start the animation.
|
||||
dialogContentWithBackground.visibility = View.INVISIBLE
|
||||
|
||||
// Make sure the dialog is visible instantly and does not do any window animation.
|
||||
window.attributes.windowAnimations = R.style.Animation_LaunchAnimation
|
||||
|
||||
// Start the animation once the background view is properly laid out.
|
||||
dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
dialogContentWithBackground.removeOnLayoutChangeListener(this)
|
||||
|
||||
isOriginalDialogViewLaidOut = true
|
||||
maybeStartLaunchAnimation()
|
||||
}
|
||||
})
|
||||
|
||||
// Disable the dim. We will enable it once we start the animation.
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
|
||||
// Override the dialog dismiss() so that we can animate the exit before actually dismissing
|
||||
// the dialog.
|
||||
dialog.setDismissOverride(this::onDialogDismissed)
|
||||
|
||||
// Show the dialog.
|
||||
dialog.show()
|
||||
|
||||
// Add a temporary touch surface ghost as soon as the window is ready to draw. This
|
||||
// temporary ghost will be drawn together with the touch surface, but in the host dialog
|
||||
// temporary ghost will be drawn together with the touch surface, but in the dialog
|
||||
// window. Once it is drawn, we will make the touch surface invisible, and then start the
|
||||
// animation. We do all this synchronization to avoid flicker that would occur if we made
|
||||
// the touch surface invisible too early (before its ghost is drawn), leading to one or more
|
||||
// frames with a hole instead of the touch surface (or its ghost).
|
||||
hostDialogRoot.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
||||
decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
||||
override fun onPreDraw(): Boolean {
|
||||
hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
|
||||
decorView.viewTreeObserver.removeOnPreDrawListener(this)
|
||||
addTemporaryTouchSurfaceGhost()
|
||||
return true
|
||||
}
|
||||
})
|
||||
hostDialogRoot.invalidate()
|
||||
decorView.invalidate()
|
||||
}
|
||||
|
||||
private fun addTemporaryTouchSurfaceGhost() {
|
||||
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
|
||||
// it to the host dialog. We will wait for this ghost to be drawn before starting the
|
||||
// animation.
|
||||
val ghost = GhostView.addGhost(touchSurface, hostDialogRoot)
|
||||
// it to the dialog. We will wait for this ghost to be drawn before starting the animation.
|
||||
val ghost = GhostView.addGhost(touchSurface, decorView)
|
||||
|
||||
// The ghost of the touch surface was just created, so the touch surface was made invisible.
|
||||
// We make it visible again until the ghost is actually drawn.
|
||||
@@ -414,142 +420,6 @@ private class AnimatedDialog(
|
||||
touchSurface.invalidate()
|
||||
}
|
||||
|
||||
/** Get the content view of [originalDialog] and pass it to [then]. */
|
||||
private fun stealOriginalDialogContentView(then: (View) -> Unit) {
|
||||
// The original dialog content view will be attached to android.R.id.content when the dialog
|
||||
// is shown, so we show the dialog and add an observer to get the view but also prevents the
|
||||
// original dialog from being drawn.
|
||||
val androidContent = originalDialog.findViewById<ViewGroup>(android.R.id.content)
|
||||
?: throw IllegalStateException("Dialog does not have any android.R.id.content view")
|
||||
|
||||
androidContent.viewTreeObserver.addOnPreDrawListener(
|
||||
object : OnPreDrawListener {
|
||||
override fun onPreDraw(): Boolean {
|
||||
if (androidContent.childCount == 1) {
|
||||
androidContent.viewTreeObserver.removeOnPreDrawListener(this)
|
||||
|
||||
// Hide the animated dialog. Because of the dialog listener set up
|
||||
// earlier, this would also hide the host dialog, but in this case we
|
||||
// need to keep the host dialog visible.
|
||||
ignoreNextCallToHide = true
|
||||
originalDialog.hide()
|
||||
|
||||
then(androidContent.getChildAt(0))
|
||||
return false
|
||||
}
|
||||
|
||||
// Never draw the original dialog content.
|
||||
return false
|
||||
}
|
||||
})
|
||||
originalDialog.show()
|
||||
}
|
||||
|
||||
private fun showDialogFromView(dialogView: View) {
|
||||
// Close the dialog when clicking outside of it.
|
||||
hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
|
||||
dialogView.isClickable = true
|
||||
|
||||
// Remove the original dialog view from its parent.
|
||||
(dialogView.parent as? ViewGroup)?.removeView(dialogView)
|
||||
|
||||
val originalDialogWindow = originalDialog.window!!
|
||||
val isOriginalWindowFullScreen =
|
||||
originalDialogWindow.attributes.width == ViewGroup.LayoutParams.MATCH_PARENT &&
|
||||
originalDialogWindow.attributes.height == ViewGroup.LayoutParams.MATCH_PARENT
|
||||
if (isOriginalWindowFullScreen) {
|
||||
// If the original dialog window is fullscreen, then we look for the first ViewGroup
|
||||
// that has a background and animate towards that ViewGroup given that this is probably
|
||||
// what represents the actual dialog view.
|
||||
dialogContentWithBackground = findFirstViewGroupWithBackground(dialogView)
|
||||
?: throw IllegalStateException("Unable to find ViewGroup with background")
|
||||
|
||||
hostDialogRoot.addView(
|
||||
dialogView,
|
||||
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// Add a parent view to the original dialog view to which we will set the original
|
||||
// dialog window background. This View serves as a fake window with background, so that
|
||||
// we are sure that we don't override the original dialog content view paddings with the
|
||||
// window background that usually has insets.
|
||||
dialogContentWithBackground = FrameLayout(context).apply {
|
||||
id = DIALOG_CONTENT_PARENT_ID
|
||||
|
||||
// TODO(b/193634619): Support dialog windows without background.
|
||||
background = originalDialogWindow.decorView?.background
|
||||
?: throw IllegalStateException(
|
||||
"Dialogs with no backgrounds on window are not supported")
|
||||
|
||||
addView(
|
||||
dialogView,
|
||||
|
||||
// It should match its parent size, which is sized the same as the original
|
||||
// dialog window.
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Add the parent (that has the background) to the host window.
|
||||
hostDialogRoot.addView(
|
||||
dialogContentWithBackground,
|
||||
|
||||
// We give it the size and gravity of its original dialog window.
|
||||
FrameLayout.LayoutParams(
|
||||
originalDialogWindow.attributes.width,
|
||||
originalDialogWindow.attributes.height,
|
||||
originalDialogWindow.attributes.gravity
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val dialogContentWithBackground = this.dialogContentWithBackground!!
|
||||
|
||||
// Make the dialog and its background invisible for now, to make sure it's not drawn yet.
|
||||
dialogContentWithBackground.visibility = View.INVISIBLE
|
||||
|
||||
val background = dialogContentWithBackground.background!!
|
||||
originalDialogBackgroundColor =
|
||||
GhostedViewLaunchAnimatorController.findGradientDrawable(background)
|
||||
?.color
|
||||
?.defaultColor ?: Color.BLACK
|
||||
|
||||
if (isOriginalWindowFullScreen) {
|
||||
// If the original window is full screen, the ViewGroup with background might already be
|
||||
// correctly laid out. Make sure we relayout and that the layout listener below is still
|
||||
// called.
|
||||
dialogContentWithBackground.layout(0, 0, 0, 0)
|
||||
dialogContentWithBackground.requestLayout()
|
||||
}
|
||||
|
||||
// Start the animation when the dialog is laid out in the center of the host dialog.
|
||||
dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
view: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
dialogContentWithBackground.removeOnLayoutChangeListener(this)
|
||||
|
||||
isOriginalDialogViewLaidOut = true
|
||||
maybeStartLaunchAnimation()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
|
||||
if (view !is ViewGroup) {
|
||||
return null
|
||||
@@ -569,26 +439,13 @@ private class AnimatedDialog(
|
||||
return null
|
||||
}
|
||||
|
||||
fun onOriginalDialogSizeChanged() {
|
||||
// The dialog is the single child of the root.
|
||||
if (hostDialogRoot.childCount != 1) {
|
||||
return
|
||||
}
|
||||
|
||||
val dialogView = hostDialogRoot.getChildAt(0)
|
||||
val layoutParams = dialogView.layoutParams as? FrameLayout.LayoutParams ?: return
|
||||
layoutParams.width = originalDialog.window.attributes.width
|
||||
layoutParams.height = originalDialog.window.attributes.height
|
||||
dialogView.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
private fun maybeStartLaunchAnimation() {
|
||||
if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
|
||||
return
|
||||
}
|
||||
|
||||
// Show the background dim.
|
||||
hostDialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
|
||||
startAnimation(
|
||||
isLaunching = true,
|
||||
@@ -610,7 +467,7 @@ private class AnimatedDialog(
|
||||
// dismiss was called during the animation, dismiss again now to actually
|
||||
// dismiss.
|
||||
if (dismissRequested) {
|
||||
hostDialog.dismiss()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
// If necessary, we animate the dialog background when its bounds change. We do it
|
||||
@@ -624,9 +481,9 @@ private class AnimatedDialog(
|
||||
)
|
||||
}
|
||||
|
||||
private fun onHostDialogDismissed(actualDismiss: () -> Unit) {
|
||||
private fun onDialogDismissed() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
context.mainExecutor.execute { onHostDialogDismissed(actualDismiss) }
|
||||
context.mainExecutor.execute { onDialogDismissed() }
|
||||
return
|
||||
}
|
||||
|
||||
@@ -641,23 +498,29 @@ private class AnimatedDialog(
|
||||
}
|
||||
|
||||
isDismissing = true
|
||||
hideDialogIntoView { instantDismiss: Boolean ->
|
||||
if (instantDismiss) {
|
||||
originalDialog.hide()
|
||||
hostDialog.hide()
|
||||
hideDialogIntoView { animationRan: Boolean ->
|
||||
if (animationRan) {
|
||||
// Instantly dismiss the dialog if we ran the animation into view. If it was
|
||||
// skipped, dismiss() will run the window animation (which fades out the dialog).
|
||||
dialog.hide()
|
||||
}
|
||||
|
||||
originalDialog.dismiss()
|
||||
actualDismiss()
|
||||
dialog.setDismissOverride(null)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the dialog into the touch surface and call [dismissDialogs] when the animation is done
|
||||
* (passing instantDismiss=true) or if it's skipped (passing instantDismiss=false) to actually
|
||||
* dismiss the dialogs.
|
||||
* Hide the dialog into the touch surface and call [onAnimationFinished] when the animation is
|
||||
* done (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
|
||||
* dismiss the dialog.
|
||||
*/
|
||||
private fun hideDialogIntoView(dismissDialogs: (Boolean) -> Unit) {
|
||||
private fun hideDialogIntoView(onAnimationFinished: (Boolean) -> Unit) {
|
||||
// Remove the layout change listener we have added to the DecorView earlier.
|
||||
if (decorViewLayoutListener != null) {
|
||||
decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
|
||||
}
|
||||
|
||||
if (!shouldAnimateDialogIntoView()) {
|
||||
Log.i(TAG, "Skipping animation of dialog into the touch surface")
|
||||
|
||||
@@ -669,7 +532,7 @@ private class AnimatedDialog(
|
||||
touchSurface.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
dismissDialogs(false /* instantDismiss */)
|
||||
onAnimationFinished(false /* instantDismiss */)
|
||||
onDialogDismissed(this@AnimatedDialog)
|
||||
return
|
||||
}
|
||||
@@ -678,7 +541,7 @@ private class AnimatedDialog(
|
||||
isLaunching = false,
|
||||
onLaunchAnimationStart = {
|
||||
// Remove the dim background as soon as we start the animation.
|
||||
hostDialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
},
|
||||
onLaunchAnimationEnd = {
|
||||
// Make sure we allow the touch surface to change its visibility again.
|
||||
@@ -696,7 +559,7 @@ private class AnimatedDialog(
|
||||
// The animated ghost was just removed. We create a temporary ghost that will be
|
||||
// removed only once we draw the touch surface, to avoid flickering that would
|
||||
// happen when removing the ghost too early (before the touch surface is drawn).
|
||||
GhostView.addGhost(touchSurface, hostDialogRoot)
|
||||
GhostView.addGhost(touchSurface, decorView)
|
||||
|
||||
touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
||||
override fun onPreDraw(): Boolean {
|
||||
@@ -705,7 +568,7 @@ private class AnimatedDialog(
|
||||
// Now that the touch surface was drawn, we can remove the temporary ghost
|
||||
// and instantly dismiss the dialog.
|
||||
GhostView.removeGhost(touchSurface)
|
||||
dismissDialogs(true /* instantDismiss */)
|
||||
onAnimationFinished(true /* instantDismiss */)
|
||||
onDialogDismissed(this@AnimatedDialog)
|
||||
|
||||
return true
|
||||
@@ -721,14 +584,14 @@ private class AnimatedDialog(
|
||||
onLaunchAnimationStart: () -> Unit = {},
|
||||
onLaunchAnimationEnd: () -> Unit = {}
|
||||
) {
|
||||
// Create 2 ghost controllers to animate both the dialog and the touch surface in the host
|
||||
// Create 2 ghost controllers to animate both the dialog and the touch surface in the
|
||||
// dialog.
|
||||
val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
|
||||
val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
|
||||
val startViewController = GhostedViewLaunchAnimatorController(startView)
|
||||
val endViewController = GhostedViewLaunchAnimatorController(endView)
|
||||
startViewController.launchContainer = hostDialogRoot
|
||||
endViewController.launchContainer = hostDialogRoot
|
||||
startViewController.launchContainer = decorView
|
||||
endViewController.launchContainer = decorView
|
||||
|
||||
val endState = endViewController.createAnimatorState()
|
||||
val controller = object : LaunchAnimator.Controller {
|
||||
@@ -785,9 +648,15 @@ private class AnimatedDialog(
|
||||
}
|
||||
|
||||
private fun shouldAnimateDialogIntoView(): Boolean {
|
||||
// Don't animate if the dialog was previously hidden using hide() (either on the host dialog
|
||||
// or on the original dialog) or if we disabled the exit animation.
|
||||
if (exitAnimationDisabled || !hostDialog.isShowing) {
|
||||
// Don't animate if the dialog was previously hidden using hide() or if we disabled the exit
|
||||
// animation.
|
||||
if (exitAnimationDisabled || !dialog.isShowing) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we are dreaming, the dialog was probably closed because of that so we don't animate
|
||||
// into the touchSurface.
|
||||
if (dreamManager.isDreaming) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -888,9 +757,9 @@ private class AnimatedDialog(
|
||||
return touchSurface
|
||||
}
|
||||
parentAnimatedDialog.exitAnimationDisabled = true
|
||||
parentAnimatedDialog.originalDialog.hide()
|
||||
parentAnimatedDialog.dialog.hide()
|
||||
val view = parentAnimatedDialog.prepareForStackDismiss()
|
||||
parentAnimatedDialog.originalDialog.dismiss()
|
||||
parentAnimatedDialog.dialog.dismiss()
|
||||
// Make the touch surface invisible, so we end up animating to it when we actually
|
||||
// dismiss the stack
|
||||
view.visibility = View.INVISIBLE
|
||||
|
||||
@@ -217,14 +217,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (!hasFocus && isShowing()) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
void onHeaderIconClick() {
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnAttachStateChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -40,6 +39,7 @@ import com.android.internal.app.MediaRouteDialogPresenter;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
@@ -76,6 +76,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
|
||||
private final CastDetailAdapter mDetailAdapter;
|
||||
private final KeyguardStateController mKeyguard;
|
||||
private final NetworkController mNetworkController;
|
||||
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||
private final Callback mCallback = new Callback();
|
||||
private Dialog mDialog;
|
||||
private boolean mWifiConnected;
|
||||
@@ -94,7 +95,8 @@ public class CastTile extends QSTileImpl<BooleanState> {
|
||||
CastController castController,
|
||||
KeyguardStateController keyguardStateController,
|
||||
NetworkController networkController,
|
||||
HotspotController hotspotController
|
||||
HotspotController hotspotController,
|
||||
DialogLaunchAnimator dialogLaunchAnimator
|
||||
) {
|
||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||
statusBarStateController, activityStarter, qsLogger);
|
||||
@@ -102,6 +104,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
|
||||
mDetailAdapter = new CastDetailAdapter();
|
||||
mKeyguard = keyguardStateController;
|
||||
mNetworkController = networkController;
|
||||
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||
mController.observe(this, mCallback);
|
||||
mKeyguard.observe(this, mCallback);
|
||||
mNetworkController.observe(this, mSignalCallback);
|
||||
@@ -153,9 +156,15 @@ public class CastTile extends QSTileImpl<BooleanState> {
|
||||
|
||||
List<CastDevice> activeDevices = getActiveDevices();
|
||||
if (willPopDetail()) {
|
||||
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
|
||||
showDetail(true);
|
||||
});
|
||||
if (!mKeyguard.isShowing()) {
|
||||
showDetail(view);
|
||||
} else {
|
||||
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
|
||||
// Dismissing the keyguard will collapse the shade, so we don't animate from the
|
||||
// view here as it would not look good.
|
||||
showDetail(null /* view */);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
mController.stopCasting(activeDevices.get(0));
|
||||
}
|
||||
@@ -184,19 +193,29 @@ public class CastTile extends QSTileImpl<BooleanState> {
|
||||
|
||||
@Override
|
||||
public void showDetail(boolean show) {
|
||||
showDetail(null /* view */);
|
||||
}
|
||||
|
||||
private void showDetail(@Nullable View view) {
|
||||
mUiHandler.post(() -> {
|
||||
mDialog = MediaRouteDialogPresenter.createDialog(mContext, ROUTE_TYPE_REMOTE_DISPLAY,
|
||||
v -> {
|
||||
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
|
||||
mDialog.dismiss();
|
||||
mActivityStarter
|
||||
.postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
|
||||
});
|
||||
mDialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
|
||||
SystemUIDialog.setShowForAllUsers(mDialog, true);
|
||||
SystemUIDialog.registerDismissListener(mDialog);
|
||||
SystemUIDialog.setWindowOnTop(mDialog);
|
||||
mUiHandler.post(() -> mDialog.show());
|
||||
mHost.collapsePanels();
|
||||
|
||||
mUiHandler.post(() -> {
|
||||
if (view != null) {
|
||||
mDialogLaunchAnimator.showFromView(mDialog, view);
|
||||
} else {
|
||||
mDialog.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -190,14 +190,10 @@ public class DndTile extends QSTileImpl<BooleanState> {
|
||||
case Settings.Secure.ZEN_DURATION_PROMPT:
|
||||
mUiHandler.post(() -> {
|
||||
Dialog dialog = makeZenModeDialog();
|
||||
SystemUIDialog.registerDismissListener(dialog);
|
||||
if (view != null) {
|
||||
final Dialog hostDialog =
|
||||
mDialogLaunchAnimator.showFromView(dialog, view, false);
|
||||
setDialogListeners(dialog, hostDialog);
|
||||
mDialogLaunchAnimator.showFromView(dialog, view, false);
|
||||
} else {
|
||||
// If we are not launching with animator, register default
|
||||
// dismiss listener
|
||||
SystemUIDialog.registerDismissListener(dialog);
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
@@ -222,12 +218,6 @@ public class DndTile extends QSTileImpl<BooleanState> {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) {
|
||||
// Zen mode dialog is never hidden.
|
||||
SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss);
|
||||
zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleSecondaryClick(@Nullable View view) {
|
||||
if (mController.isVolumeRestricted()) {
|
||||
|
||||
@@ -94,24 +94,24 @@ class UserSwitchDialogController @VisibleForTesting constructor(
|
||||
|
||||
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
|
||||
|
||||
val hostDialog = dialogLaunchAnimator.showFromView(this, view)
|
||||
adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
|
||||
dialogLaunchAnimator.showFromView(this, view)
|
||||
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
|
||||
}
|
||||
}
|
||||
|
||||
private class DialogShowerImpl(
|
||||
private val hostDialog: Dialog,
|
||||
private val animateFrom: Dialog,
|
||||
private val dialogLaunchAnimator: DialogLaunchAnimator
|
||||
) : DialogInterface by hostDialog, DialogShower {
|
||||
override fun showDialog(dialog: Dialog): Dialog {
|
||||
return dialogLaunchAnimator.showFromDialog(
|
||||
) : DialogInterface by animateFrom, DialogShower {
|
||||
override fun showDialog(dialog: Dialog) {
|
||||
dialogLaunchAnimator.showFromDialog(
|
||||
dialog,
|
||||
parentHostDialog = hostDialog
|
||||
animateFrom = animateFrom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface DialogShower : DialogInterface {
|
||||
fun showDialog(dialog: Dialog): Dialog
|
||||
fun showDialog(dialog: Dialog)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.app.IActivityManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.service.dreams.IDreamManager;
|
||||
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||
@@ -67,13 +68,10 @@ import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
|
||||
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
|
||||
import com.android.systemui.statusbar.phone.SystemUIHostDialogProvider;
|
||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
|
||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
|
||||
import com.android.systemui.statusbar.policy.RemoteInputUriController;
|
||||
import com.android.systemui.statusbar.window.StatusBarWindowController;
|
||||
import com.android.systemui.statusbar.window.StatusBarWindowModule;
|
||||
import com.android.systemui.statusbar.window.StatusBarWindowView;
|
||||
import com.android.systemui.tracing.ProtoTracer;
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
@@ -320,7 +318,7 @@ public interface StatusBarDependenciesModule {
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
|
||||
LaunchAnimator launchAnimator) {
|
||||
return new DialogLaunchAnimator(context, launchAnimator, new SystemUIHostDialogProvider());
|
||||
LaunchAnimator launchAnimator, IDreamManager dreamManager) {
|
||||
return new DialogLaunchAnimator(context, launchAnimator, dreamManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,30 +40,21 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.DialogListener;
|
||||
import com.android.systemui.animation.DialogListener.DismissReason;
|
||||
import com.android.systemui.animation.ListenableDialog;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for dialogs that should appear over panels and keyguard.
|
||||
* The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast,
|
||||
* and dismisses itself when it receives the broadcast.
|
||||
*/
|
||||
public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
||||
ViewRootImpl.ConfigChangedCallback {
|
||||
public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigChangedCallback {
|
||||
// TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
|
||||
private static final String FLAG_TABLET_DIALOG_WIDTH =
|
||||
"persist.systemui.flag_tablet_dialog_width";
|
||||
|
||||
private final Context mContext;
|
||||
private final DismissReceiver mDismissReceiver;
|
||||
private final Set<DialogListener> mDialogListeners = new LinkedHashSet<>();
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
private int mLastWidth = Integer.MIN_VALUE;
|
||||
@@ -117,10 +108,6 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
||||
mLastWidth = width;
|
||||
mLastHeight = height;
|
||||
getWindow().setLayout(width, height);
|
||||
|
||||
for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
|
||||
listener.onSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,60 +184,6 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
||||
ViewRootImpl.removeConfigCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(DialogListener listener) {
|
||||
mDialogListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(DialogListener listener) {
|
||||
mDialogListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
dismiss(DismissReason.UNKNOWN);
|
||||
}
|
||||
|
||||
private void dismiss(DismissReason reason) {
|
||||
super.dismiss();
|
||||
|
||||
for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
|
||||
listener.onDismiss(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public void hide() {
|
||||
super.hide();
|
||||
|
||||
for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
|
||||
listener.onHide();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
|
||||
listener.onShow();
|
||||
}
|
||||
}
|
||||
|
||||
public void setShowForAllUsers(boolean show) {
|
||||
setShowForAllUsers(this, show);
|
||||
}
|
||||
@@ -364,11 +297,7 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mDialog instanceof SystemUIDialog) {
|
||||
((SystemUIDialog) mDialog).dismiss(DismissReason.DEVICE_LOCKED);
|
||||
} else {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
mDialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.android.systemui.statusbar.phone
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import com.android.systemui.animation.HostDialogProvider
|
||||
|
||||
/** An implementation of [HostDialogProvider] to be used when animating SysUI dialogs. */
|
||||
class SystemUIHostDialogProvider : HostDialogProvider {
|
||||
override fun createHostDialog(
|
||||
context: Context,
|
||||
theme: Int,
|
||||
onCreateCallback: () -> Unit,
|
||||
dismissOverride: (() -> Unit) -> Unit
|
||||
): Dialog {
|
||||
return SystemUIHostDialog(context, theme, onCreateCallback, dismissOverride)
|
||||
}
|
||||
|
||||
/**
|
||||
* This host dialog is a SystemUIDialog so that it's displayed above all SystemUI windows. Note
|
||||
* that it is not automatically dismissed when the device is locked, but only when the hosted
|
||||
* (original) dialog is dismissed. That way, the behavior of the dialog (dismissed when locking
|
||||
* or not) is consistent with when the dialog is shown with or without the dialog animator.
|
||||
*/
|
||||
private class SystemUIHostDialog(
|
||||
context: Context,
|
||||
theme: Int,
|
||||
private val onCreateCallback: () -> Unit,
|
||||
private val dismissOverride: (() -> Unit) -> Unit
|
||||
) : SystemUIDialog(context, theme, false /* dismissOnDeviceLock */) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
onCreateCallback()
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
dismissOverride {
|
||||
super.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWidth(): Int {
|
||||
return ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
|
||||
override fun getHeight(): Int {
|
||||
return ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ import com.android.systemui.Prefs;
|
||||
import com.android.systemui.Prefs.Key;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SystemUISecondaryUserService;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
@@ -133,6 +134,7 @@ public class UserSwitcherController implements Dumpable {
|
||||
private final IActivityTaskManager mActivityTaskManager;
|
||||
private final InteractionJankMonitor mInteractionJankMonitor;
|
||||
private final LatencyTracker mLatencyTracker;
|
||||
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||
|
||||
private ArrayList<UserRecord> mUsers = new ArrayList<>();
|
||||
@VisibleForTesting
|
||||
@@ -180,7 +182,8 @@ public class UserSwitcherController implements Dumpable {
|
||||
@Background Executor bgExecutor,
|
||||
InteractionJankMonitor interactionJankMonitor,
|
||||
LatencyTracker latencyTracker,
|
||||
DumpManager dumpManager) {
|
||||
DumpManager dumpManager,
|
||||
DialogLaunchAnimator dialogLaunchAnimator) {
|
||||
mContext = context;
|
||||
mActivityManager = activityManager;
|
||||
mUserTracker = userTracker;
|
||||
@@ -208,6 +211,8 @@ public class UserSwitcherController implements Dumpable {
|
||||
mHandler = handler;
|
||||
mActivityStarter = activityStarter;
|
||||
mUserManager = userManager;
|
||||
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_USER_ADDED);
|
||||
filter.addAction(Intent.ACTION_USER_REMOVED);
|
||||
@@ -1179,7 +1184,7 @@ public class UserSwitcherController implements Dumpable {
|
||||
cancel();
|
||||
} else {
|
||||
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
|
||||
dismissStack();
|
||||
mDialogLaunchAnimator.dismissStack(this);
|
||||
removeGuestUser(mGuestId, mTargetId);
|
||||
}
|
||||
}
|
||||
@@ -1210,7 +1215,7 @@ public class UserSwitcherController implements Dumpable {
|
||||
if (which == BUTTON_NEGATIVE) {
|
||||
cancel();
|
||||
} else {
|
||||
dismissStack();
|
||||
mDialogLaunchAnimator.dismissStack(this);
|
||||
if (ActivityManager.isUserAMonkey()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,35 +2,49 @@ package com.android.systemui.animation
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.service.dreams.IDreamManager
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.testing.ViewUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.WindowManager
|
||||
import android.widget.LinearLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.policy.DecorView
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.DialogListener.DismissReason
|
||||
import junit.framework.Assert.assertEquals
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertNotNull
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
private val launchAnimator = LaunchAnimator(context, isForTesting = true)
|
||||
private val hostDialogprovider = TestHostDialogProvider()
|
||||
private val dialogLaunchAnimator =
|
||||
DialogLaunchAnimator(context, launchAnimator, hostDialogprovider)
|
||||
|
||||
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||
private val attachedViews = mutableSetOf<View>()
|
||||
|
||||
@Mock lateinit var dreamManager: IDreamManager
|
||||
@get:Rule val rule = MockitoJUnit.rule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
runOnMainThreadAndWaitForIdleSync {
|
||||
@@ -44,76 +58,66 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
fun testShowDialogFromView() {
|
||||
// Show the dialog. showFromView() must be called on the main thread with a dialog created
|
||||
// on the main thread too.
|
||||
val (dialog, hostDialog) = createDialogAndHostDialog()
|
||||
val dialog = createAndShowDialog()
|
||||
|
||||
// Only the host dialog is actually showing.
|
||||
assertTrue(hostDialog.isShowing)
|
||||
assertFalse(dialog.isShowing)
|
||||
assertTrue(dialog.isShowing)
|
||||
|
||||
// The dialog onStart() method was called but not onStop().
|
||||
assertTrue(dialog.onStartCalled)
|
||||
assertFalse(dialog.onStopCalled)
|
||||
// The dialog is now fullscreen.
|
||||
val window = dialog.window
|
||||
val decorView = window.decorView as DecorView
|
||||
assertEquals(MATCH_PARENT, window.attributes.width)
|
||||
assertEquals(MATCH_PARENT, window.attributes.height)
|
||||
assertEquals(MATCH_PARENT, decorView.layoutParams.width)
|
||||
assertEquals(MATCH_PARENT, decorView.layoutParams.height)
|
||||
|
||||
// The dialog content has been stolen and is shown inside the host dialog.
|
||||
val hostDialogContent = hostDialog.findViewById<ViewGroup>(android.R.id.content)
|
||||
assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
|
||||
assertEquals(1, hostDialogContent.childCount)
|
||||
// The single DecorView child is a transparent fullscreen view that will dismiss the dialog
|
||||
// when clicked.
|
||||
assertEquals(1, decorView.childCount)
|
||||
val transparentBackground = decorView.getChildAt(0) as ViewGroup
|
||||
assertEquals(MATCH_PARENT, transparentBackground.layoutParams.width)
|
||||
assertEquals(MATCH_PARENT, transparentBackground.layoutParams.height)
|
||||
|
||||
// The original dialog content is added to another view that is the same size as the
|
||||
// original dialog window.
|
||||
val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
|
||||
assertEquals(1, hostDialogRoot.childCount)
|
||||
// The single transparent background child is a fake window with the same size and
|
||||
// background as the dialog initially had.
|
||||
assertEquals(1, transparentBackground.childCount)
|
||||
val dialogContentWithBackground = transparentBackground.getChildAt(0) as ViewGroup
|
||||
assertEquals(TestDialog.DIALOG_WIDTH, dialogContentWithBackground.layoutParams.width)
|
||||
assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentWithBackground.layoutParams.height)
|
||||
assertEquals(dialog.windowBackground, dialogContentWithBackground.background)
|
||||
|
||||
val dialogContentParent = hostDialogRoot.getChildAt(0) as ViewGroup
|
||||
assertEquals(1, dialogContentParent.childCount)
|
||||
assertEquals(TestDialog.DIALOG_WIDTH, dialogContentParent.layoutParams.width)
|
||||
assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentParent.layoutParams.height)
|
||||
// The dialog content is inside this fake window view.
|
||||
assertNotNull(
|
||||
dialogContentWithBackground.findViewByPredicate { it === dialog.contentView })
|
||||
|
||||
val dialogContent = dialogContentParent.getChildAt(0)
|
||||
assertEquals(dialog.contentView, dialogContent)
|
||||
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.width)
|
||||
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.height)
|
||||
|
||||
// Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that
|
||||
// it's a ListenableDialog.
|
||||
runOnMainThreadAndWaitForIdleSync { dialog.hide() }
|
||||
assertFalse(hostDialog.isShowing)
|
||||
assertFalse(dialog.isShowing)
|
||||
|
||||
runOnMainThreadAndWaitForIdleSync { dialog.show() }
|
||||
assertTrue(hostDialog.isShowing)
|
||||
assertFalse(dialog.isShowing)
|
||||
|
||||
assertFalse(dialog.onStopCalled)
|
||||
// Clicking the transparent background should dismiss the dialog.
|
||||
runOnMainThreadAndWaitForIdleSync {
|
||||
// TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and
|
||||
// make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in
|
||||
// Forrest.
|
||||
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
|
||||
|
||||
dialog.dismiss()
|
||||
transparentBackground.performClick()
|
||||
}
|
||||
assertFalse(hostDialog.isShowing)
|
||||
assertFalse(dialog.isShowing)
|
||||
assertTrue(hostDialog.wasDismissed)
|
||||
assertTrue(dialog.onStopCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStackedDialogsDismissesAll() {
|
||||
val (_, hostDialogFirst) = createDialogAndHostDialog()
|
||||
val (dialogSecond, hostDialogSecond) = createDialogAndHostDialogFromDialog(hostDialogFirst)
|
||||
val firstDialog = createAndShowDialog()
|
||||
val secondDialog = createDialogAndShowFromDialog(firstDialog)
|
||||
|
||||
assertTrue(firstDialog.isShowing)
|
||||
assertTrue(secondDialog.isShowing)
|
||||
runOnMainThreadAndWaitForIdleSync {
|
||||
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
|
||||
dialogSecond.dismissStack()
|
||||
dialogLaunchAnimator.dismissStack(secondDialog)
|
||||
}
|
||||
|
||||
assertTrue(hostDialogSecond.wasDismissed)
|
||||
assertTrue(hostDialogFirst.wasDismissed)
|
||||
assertFalse(firstDialog.isShowing)
|
||||
assertFalse(secondDialog.isShowing)
|
||||
}
|
||||
|
||||
private fun createDialogAndHostDialog(): Pair<TestDialog, TestHostDialog> {
|
||||
private fun createAndShowDialog(): TestDialog {
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val touchSurfaceRoot = LinearLayout(context)
|
||||
val touchSurface = View(context)
|
||||
@@ -125,22 +129,16 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
attachedViews.add(touchSurfaceRoot)
|
||||
|
||||
val dialog = TestDialog(context)
|
||||
val hostDialog =
|
||||
dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
|
||||
dialog to hostDialog
|
||||
dialogLaunchAnimator.showFromView(dialog, touchSurface)
|
||||
dialog
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDialogAndHostDialogFromDialog(
|
||||
hostParent: Dialog
|
||||
): Pair<TestDialog, TestHostDialog> {
|
||||
private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog {
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val dialog = TestDialog(context)
|
||||
val hostDialog = dialogLaunchAnimator.showFromDialog(
|
||||
dialog,
|
||||
hostParent
|
||||
) as TestHostDialog
|
||||
dialog to hostDialog
|
||||
dialogLaunchAnimator.showFromDialog(dialog, animateFrom)
|
||||
dialog
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,50 +151,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
return result
|
||||
}
|
||||
|
||||
private class TestHostDialogProvider : HostDialogProvider {
|
||||
override fun createHostDialog(
|
||||
context: Context,
|
||||
theme: Int,
|
||||
onCreateCallback: () -> Unit,
|
||||
dismissOverride: (() -> Unit) -> Unit
|
||||
): Dialog = TestHostDialog(context, onCreateCallback, dismissOverride)
|
||||
}
|
||||
|
||||
private class TestHostDialog(
|
||||
context: Context,
|
||||
private val onCreateCallback: () -> Unit,
|
||||
private val dismissOverride: (() -> Unit) -> Unit
|
||||
) : Dialog(context) {
|
||||
var wasDismissed = false
|
||||
|
||||
init {
|
||||
// We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
|
||||
window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
onCreateCallback()
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
dismissOverride {
|
||||
super.dismiss()
|
||||
wasDismissed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDialog(context: Context) : Dialog(context), ListenableDialog {
|
||||
private class TestDialog(context: Context) : Dialog(context) {
|
||||
companion object {
|
||||
const val DIALOG_WIDTH = 100
|
||||
const val DIALOG_HEIGHT = 200
|
||||
}
|
||||
|
||||
private val listeners = hashSetOf<DialogListener>()
|
||||
val contentView = View(context)
|
||||
var onStartCalled = false
|
||||
var onStopCalled = false
|
||||
val windowBackground = ColorDrawable(Color.RED)
|
||||
|
||||
init {
|
||||
// We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
|
||||
@@ -205,52 +167,10 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
|
||||
setContentView(contentView)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
onStartCalled = true
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStart()
|
||||
onStopCalled = true
|
||||
}
|
||||
|
||||
override fun addListener(listener: DialogListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: DialogListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
super.dismiss()
|
||||
notifyListeners { onDismiss(DismissReason.UNKNOWN) }
|
||||
}
|
||||
|
||||
override fun hide() {
|
||||
super.hide()
|
||||
notifyListeners { onHide() }
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
notifyListeners { onShow() }
|
||||
}
|
||||
|
||||
fun dismissStack() {
|
||||
notifyListeners { prepareForStackDismiss() }
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun notifyListeners(notify: DialogListener.() -> Unit) {
|
||||
for (listener in HashSet(listeners)) {
|
||||
listener.notify()
|
||||
}
|
||||
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
|
||||
window.setBackgroundDrawable(windowBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.classifier.FalsingManagerFake;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
@@ -90,6 +91,8 @@ public class CastTileTest extends SysuiTestCase {
|
||||
private HotspotController.Callback mHotspotCallback;
|
||||
@Mock
|
||||
private QSLogger mQSLogger;
|
||||
@Mock
|
||||
private DialogLaunchAnimator mDialogLaunchAnimator;
|
||||
|
||||
private TestableLooper mTestableLooper;
|
||||
private CastTile mCastTile;
|
||||
@@ -113,7 +116,8 @@ public class CastTileTest extends SysuiTestCase {
|
||||
mController,
|
||||
mKeyguard,
|
||||
mNetworkController,
|
||||
mHotspotController
|
||||
mHotspotController,
|
||||
mDialogLaunchAnimator
|
||||
);
|
||||
mCastTile.initialize();
|
||||
|
||||
@@ -241,6 +245,7 @@ public class CastTileTest extends SysuiTestCase {
|
||||
List<CastDevice> devices = new ArrayList<>();
|
||||
devices.add(device);
|
||||
when(mController.getCastDevices()).thenReturn(devices);
|
||||
when(mKeyguard.isShowing()).thenReturn(true);
|
||||
|
||||
enableWifiAndProcessMessages();
|
||||
mCastTile.handleClick(null /* view */);
|
||||
|
||||
@@ -96,8 +96,6 @@ class DndTileTest : SysuiTestCase() {
|
||||
|
||||
whenever(qsHost.userId).thenReturn(DEFAULT_USER)
|
||||
whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
|
||||
whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
|
||||
.thenReturn(hostDialog)
|
||||
|
||||
val wrappedContext = object : ContextWrapper(context) {
|
||||
override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.systemui.qs.user
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
@@ -31,7 +30,6 @@ import com.android.systemui.qs.PseudoGridView
|
||||
import com.android.systemui.qs.tiles.UserDetailView
|
||||
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import org.junit.Before
|
||||
@@ -42,7 +40,6 @@ import org.mockito.ArgumentMatcher
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.argThat
|
||||
import org.mockito.Mockito.never
|
||||
@@ -65,8 +62,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
|
||||
private lateinit var launchView: View
|
||||
@Mock
|
||||
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||
@Mock
|
||||
private lateinit var hostDialog: Dialog
|
||||
@Captor
|
||||
private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
|
||||
|
||||
@@ -78,8 +73,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
|
||||
|
||||
`when`(launchView.context).thenReturn(mContext)
|
||||
`when`(dialog.context).thenReturn(mContext)
|
||||
`when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
|
||||
.thenReturn(hostDialog)
|
||||
|
||||
controller = UserSwitchDialogController(
|
||||
{ userDetailViewAdapter },
|
||||
@@ -151,18 +144,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
|
||||
verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun callbackFromDialogShower_dismissesDialog() {
|
||||
val captor = argumentCaptor<UserSwitchDialogController.DialogShower>()
|
||||
|
||||
controller.showDialog(launchView)
|
||||
verify(userDetailViewAdapter).injectDialogShower(capture(captor))
|
||||
|
||||
captor.value.dismiss()
|
||||
|
||||
verify(hostDialog).dismiss()
|
||||
}
|
||||
|
||||
private class IntentMatcher(private val action: String) : ArgumentMatcher<Intent> {
|
||||
override fun matches(argument: Intent?): Boolean {
|
||||
return argument?.action == action
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.internal.util.UserIcons
|
||||
import com.android.systemui.GuestResumeSessionReceiver
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.DialogLaunchAnimator
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
@@ -94,6 +95,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
|
||||
@Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
|
||||
@Mock private lateinit var threadedRenderer: ThreadedRenderer
|
||||
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||
private lateinit var testableLooper: TestableLooper
|
||||
private lateinit var uiBgExecutor: FakeExecutor
|
||||
private lateinit var uiEventLogger: UiEventLoggerFake
|
||||
@@ -147,7 +149,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
|
||||
uiBgExecutor,
|
||||
interactionJankMonitor,
|
||||
latencyTracker,
|
||||
dumpManager)
|
||||
dumpManager,
|
||||
dialogLaunchAnimator)
|
||||
userSwitcherController.mPauseRefreshUsers = true
|
||||
|
||||
// Since userSwitcherController involves InteractionJankMonitor.
|
||||
|
||||
Reference in New Issue
Block a user