Refactor DialogLaunchAnimator and remove host dialog (1/2)
This CL is a refactoring of the DialogLaunchAnimator and a major change in how it works internally. Before this CL, when DialogLaunchAnimator.show(originalDialog, ...) was called, it would create a fullscreen dialog (called hostDialog) to which we would add the content view stolen from the originalDialog (that would then be hidden). We would then run the launch animation in the host dialog and listen for show(), hide() or dismiss() calls to the originalDialog to know when we should show(), hide() or dismiss() the hostDialog. The main reason we did that was because there was no way to override the dismiss() behavior of the originalDialog, which we need to do given that we want to animate the dialog into the view that showed the dialog before actually dismissing it. This approach had multiple downsides: - We were showing the dialog content view inside a different window than the one in which it was created, therefore any change made to or any listener added to the originalDialog window was lost. - Code calling DialogLaunchAnimator.showFromView needed to know that their dialog content will be moved to another window, which is unexpected and can lead to subtle bugs. - We were waiting for 2 dialogs to be shown instead of 1 before being able to start the animation. This CL does what we should have done since the beginning: it adds a hidden API to Dialog so that we can override what happens when Dialog.dismiss() is called. This allows the DialogLaunchAnimator to run the exit animation into the view that triggered the dialog before actually dismissing it. The only modification that DialogLaunchAnimator.show(originalDialog, ...) now does to the originalDialog is making its window fullscreen and inserting two views between the originalDialog DecorView and its children. The first inserted view is a fullscreen transparent background used to dismiss the dialog when the user taps outside the dialog content. The second inserted view is a View that we size and position the same way that the DecorView was before we made it fullscreen, and to which we set the originalDialog background. This view now serves as a "fake window" with the same size, background and position as the original window (DecorView). This CL improves the time to start the launch animation by 20-25% (measured in a totally unrigorous logcat way). Bug: 193634619 Test: atest DialogLaunchAnimatorTest Change-Id: If09eda7b06e83b3ed7714cec97afef08b3d9fd3e
This commit is contained in:
@@ -151,6 +151,9 @@ public class Dialog implements DialogInterface, Window.Callback,
|
|||||||
|
|
||||||
private final Runnable mDismissAction = this::dismissDialog;
|
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.
|
* Creates a dialog window that uses the default dialog theme.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -370,6 +373,11 @@ public class Dialog implements DialogInterface, Window.Callback,
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void dismiss() {
|
public void dismiss() {
|
||||||
|
if (mDismissOverride != null) {
|
||||||
|
mDismissOverride.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Looper.myLooper() == mHandler.getLooper()) {
|
if (Looper.myLooper() == mHandler.getLooper()) {
|
||||||
dismissDialog();
|
dismissDialog();
|
||||||
} else {
|
} else {
|
||||||
@@ -1354,6 +1362,21 @@ public class Dialog implements DialogInterface, Window.Callback,
|
|||||||
mDismissMessage = msg;
|
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 */
|
/** @hide */
|
||||||
public boolean takeCancelAndDismissListeners(@Nullable String msg,
|
public boolean takeCancelAndDismissListeners(@Nullable String msg,
|
||||||
@Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) {
|
@Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) {
|
||||||
|
|||||||
@@ -16,5 +16,4 @@
|
|||||||
-->
|
-->
|
||||||
<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>
|
||||||
@@ -15,15 +15,10 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<style name="HostDialogTheme">
|
<!-- An animation used by DialogLaunchAnimator to make a dialog appear instantly (to animate -->
|
||||||
<item name="android:windowAnimationStyle">@style/Animation.HostDialog</item>
|
<!-- in-window) and disappear by fading out (when the exit into view is disabled). -->
|
||||||
<item name="android:windowIsFloating">false</item>
|
<style name="Animation.LaunchAnimation" parent="@android:style/Animation">
|
||||||
<item name="android:backgroundDimEnabled">true</item>
|
<item name="android:windowEnterAnimation">@anim/launch_dialog_enter</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:windowExitAnimation">@anim/launch_dialog_exit</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>
|
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -24,22 +24,19 @@ import android.content.Context
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.service.dreams.IDreamManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
import android.view.GhostView
|
import android.view.GhostView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewTreeObserver.OnPreDrawListener
|
import android.view.ViewTreeObserver.OnPreDrawListener
|
||||||
import android.view.WindowInsets
|
|
||||||
import android.view.WindowManager
|
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 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
|
||||||
@@ -48,7 +45,7 @@ private val DIALOG_CONTENT_PARENT_ID = R.id.dialog_content_parent
|
|||||||
class DialogLaunchAnimator(
|
class DialogLaunchAnimator(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val launchAnimator: LaunchAnimator,
|
private val launchAnimator: LaunchAnimator,
|
||||||
private val hostDialogProvider: HostDialogProvider
|
private val dreamManager: IDreamManager
|
||||||
) {
|
) {
|
||||||
private companion object {
|
private companion object {
|
||||||
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
|
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
|
||||||
@@ -62,41 +59,38 @@ class DialogLaunchAnimator(
|
|||||||
private val openedDialogs = hashSetOf<AnimatedDialog>()
|
private val openedDialogs = hashSetOf<AnimatedDialog>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then
|
* Show [dialog] by expanding it from [view]. If [view] is a view inside another dialog that was
|
||||||
* the background of the dialog will be animated when the dialog bounds change.
|
* 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
|
* If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
|
||||||
* attached to a different dialog (and thus a different window) which means that the actual
|
* animated when the dialog bounds change.
|
||||||
* 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
|
* Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
|
||||||
* dismiss, hide or show the dialog.
|
* made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun showFromView(
|
fun showFromView(
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
view: View,
|
view: View,
|
||||||
animateBackgroundBoundsChange: Boolean = false
|
animateBackgroundBoundsChange: Boolean = false
|
||||||
): Dialog {
|
) {
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
throw IllegalStateException(
|
throw IllegalStateException(
|
||||||
"showFromView must be called from the main thread and dialog must be created in " +
|
"showFromView must be called from the main thread and dialog must be created in " +
|
||||||
"the main thread")
|
"the main thread")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the parent of the view we are launching from is the background of some other animated
|
// If the view we are launching from belongs to another dialog, then this means the caller
|
||||||
// dialog, then this means the caller intent is to launch a dialog from another dialog. In
|
// intent is to launch a dialog from another dialog.
|
||||||
// this case, we also animate the parent (which is the dialog background).
|
val animatedParent = openedDialogs
|
||||||
val animatedParent = openedDialogs.firstOrNull {
|
.firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
|
||||||
it.dialogContentWithBackground == view || it.dialogContentWithBackground == view.parent
|
val animateFrom = animatedParent?.dialogContentWithBackground ?: view
|
||||||
}
|
|
||||||
val dialogContentWithBackground = animatedParent?.dialogContentWithBackground
|
|
||||||
val animateFrom = dialogContentWithBackground ?: 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) {
|
||||||
Log.e(TAG, "Not running dialog launch animation as there is already one running")
|
Log.e(TAG, "Not running dialog launch animation as there is already one running")
|
||||||
dialog.show()
|
dialog.show()
|
||||||
return dialog
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
||||||
@@ -104,82 +98,36 @@ class DialogLaunchAnimator(
|
|||||||
val animatedDialog = AnimatedDialog(
|
val animatedDialog = AnimatedDialog(
|
||||||
context,
|
context,
|
||||||
launchAnimator,
|
launchAnimator,
|
||||||
hostDialogProvider,
|
dreamManager,
|
||||||
animateFrom,
|
animateFrom,
|
||||||
onDialogDismissed = { openedDialogs.remove(it) },
|
onDialogDismissed = { openedDialogs.remove(it) },
|
||||||
originalDialog = dialog,
|
dialog = dialog,
|
||||||
animateBackgroundBoundsChange,
|
animateBackgroundBoundsChange,
|
||||||
animatedParent
|
animatedParent
|
||||||
)
|
)
|
||||||
val hostDialog = animatedDialog.hostDialog
|
|
||||||
openedDialogs.add(animatedDialog)
|
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()
|
animatedDialog.start()
|
||||||
return hostDialog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch [dialog] from a [parentHostDialog] as returned by [showFromView]. This will allow
|
* Launch [dialog] from [another dialog][animateFrom] that was shown using [showFromView]. This
|
||||||
* for dismissing the whole stack.
|
* will allow for dismissing the whole stack.
|
||||||
*
|
*
|
||||||
* This will return a new host dialog, with the same caveat as [showFromView].
|
* @see dismissStack
|
||||||
*
|
|
||||||
* @see DialogListener.prepareForStackDismiss
|
|
||||||
*/
|
*/
|
||||||
fun showFromDialog(
|
fun showFromDialog(
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
parentHostDialog: Dialog,
|
animateFrom: Dialog,
|
||||||
animateBackgroundBoundsChange: Boolean = false
|
animateBackgroundBoundsChange: Boolean = false
|
||||||
): Dialog {
|
) {
|
||||||
val view = parentHostDialog.findViewById<ViewGroup>(DIALOG_CONTENT_PARENT_ID)
|
val view = openedDialogs
|
||||||
?.getChildAt(0)
|
.firstOrNull { it.dialog == animateFrom }
|
||||||
?: throw IllegalStateException("No dialog content parent found in host dialog")
|
?.dialogContentWithBackground
|
||||||
return showFromView(dialog, view, animateBackgroundBoundsChange)
|
?: 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() {
|
fun disableAllCurrentDialogsExitAnimations() {
|
||||||
openedDialogs.forEach { it.exitAnimationDisabled = true }
|
openedDialogs.forEach { it.exitAnimationDisabled = true }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
interface HostDialogProvider {
|
|
||||||
/**
|
/**
|
||||||
* Create a host dialog that will be used to host a launch animation. This host dialog must:
|
* Dismiss [dialog]. If it was launched from another dialog using [showFromView], also dismiss
|
||||||
* 1. call [onCreateCallback] in its onCreate() method, e.g. right after calling
|
* the stack of dialogs, animating back to the original touchSurface.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
fun createHostDialog(
|
fun dismissStack(dialog: Dialog) {
|
||||||
context: Context,
|
openedDialogs
|
||||||
theme: Int,
|
.firstOrNull { it.dialog == dialog }
|
||||||
onCreateCallback: () -> Unit,
|
?.let { it.touchSurface = it.prepareForStackDismiss() }
|
||||||
dismissOverride: (() -> Unit) -> Unit
|
dialog.dismiss()
|
||||||
): 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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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 class AnimatedDialog(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val launchAnimator: LaunchAnimator,
|
private val launchAnimator: LaunchAnimator,
|
||||||
hostDialogProvider: HostDialogProvider,
|
private val dreamManager: IDreamManager,
|
||||||
|
|
||||||
/** The view that triggered the dialog after being tapped. */
|
/** The view that triggered the dialog after being tapped. */
|
||||||
var touchSurface: View,
|
var touchSurface: View,
|
||||||
@@ -268,36 +170,33 @@ private class AnimatedDialog(
|
|||||||
*/
|
*/
|
||||||
private val onDialogDismissed: (AnimatedDialog) -> Unit,
|
private val onDialogDismissed: (AnimatedDialog) -> Unit,
|
||||||
|
|
||||||
/** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
|
/** The dialog to show and animate. */
|
||||||
private val originalDialog: Dialog,
|
val dialog: 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]. */
|
/** Launch animation corresponding to the parent [AnimatedDialog]. */
|
||||||
private val parentAnimatedDialog: AnimatedDialog? = null
|
private val parentAnimatedDialog: AnimatedDialog? = null
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* The fullscreen dialog to which we will add the content view [originalDialogView] of
|
* The DecorView of this dialog window.
|
||||||
* [originalDialog].
|
*
|
||||||
|
* 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).
|
||||||
*/
|
*/
|
||||||
val hostDialog = hostDialogProvider.createHostDialog(
|
private val decorView by lazy { dialog.window!!.decorView as ViewGroup }
|
||||||
context, R.style.HostDialogTheme, this::onHostDialogCreated, this::onHostDialogDismissed)
|
|
||||||
|
|
||||||
/** The root content view of [hostDialog]. */
|
|
||||||
private val hostDialogRoot = FrameLayout(context)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dialog content with its background. When animating a fullscreen dialog, this is just the
|
* 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)
|
* 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
|
* 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
|
var dialogContentWithBackground: ViewGroup? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The background color of [originalDialogView], taking into consideration the [originalDialog]
|
* The background color of [dialog], taking into consideration its window background color.
|
||||||
* window background color.
|
|
||||||
*/
|
*/
|
||||||
private var originalDialogBackgroundColor = Color.BLACK
|
private var originalDialogBackgroundColor = Color.BLACK
|
||||||
|
|
||||||
@@ -310,75 +209,182 @@ private class AnimatedDialog(
|
|||||||
private var isDismissing = false
|
private var isDismissing = false
|
||||||
|
|
||||||
private var dismissRequested = false
|
private var dismissRequested = false
|
||||||
var ignoreNextCallToHide = false
|
|
||||||
var exitAnimationDisabled = false
|
var exitAnimationDisabled = false
|
||||||
|
|
||||||
private var isTouchSurfaceGhostDrawn = false
|
private var isTouchSurfaceGhostDrawn = false
|
||||||
private var isOriginalDialogViewLaidOut = 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()
|
AnimatedBoundsLayoutListener()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
/*
|
||||||
// Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
|
* A layout listener in case the dialog (window) size changes (for instance because of a
|
||||||
hostDialog.show()
|
* configuration change) to ensure that the dialog stays full width.
|
||||||
|
*/
|
||||||
|
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
|
||||||
|
|
||||||
// Steal the dialog view. We do that by showing it but preventing it from drawing, then
|
fun start() {
|
||||||
// hiding it as soon as its content is available.
|
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
|
||||||
stealOriginalDialogContentView(then = this::showDialogFromView)
|
// content.
|
||||||
|
dialog.create()
|
||||||
|
|
||||||
|
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() {
|
// Animate that view with the background. Throw if we didn't find one, because otherwise
|
||||||
// Make the dialog fullscreen with a transparent background.
|
// it's not clear what we should animate.
|
||||||
hostDialog.setContentView(
|
viewGroupWithBackground
|
||||||
hostDialogRoot,
|
?: throw IllegalStateException("Unable to find ViewGroup with background")
|
||||||
ViewGroup.LayoutParams(
|
} else {
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
// We will make the dialog window (and therefore its DecorView) fullscreen to make it
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
// 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
|
val dialogContentWithBackground = FrameLayout(context)
|
||||||
?: throw IllegalStateException("There is no window associated to the host dialog")
|
dialogContentWithBackground.background = decorView.background
|
||||||
|
|
||||||
|
// 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)
|
window.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
|
||||||
// If we are using gesture navigation, then we can overlay the navigation/task bars with
|
// Close the dialog when clicking outside of it.
|
||||||
// the host dialog.
|
fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
|
||||||
val navigationMode = context.resources.getInteger(
|
dialogContentWithBackground.isClickable = true
|
||||||
com.android.internal.R.integer.config_navBarInteractionMode)
|
|
||||||
if (navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) {
|
fullscreenTransparentBackground.addView(
|
||||||
window.attributes.fitInsetsTypes = window.attributes.fitInsetsTypes and
|
dialogContentWithBackground,
|
||||||
WindowInsets.Type.navigationBars().inv()
|
FrameLayout.LayoutParams(
|
||||||
window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_INSET_DECOR)
|
window.attributes.width,
|
||||||
window.setDecorFitsSystemWindows(false)
|
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.
|
// Disable the dim. We will enable it once we start the animation.
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
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
|
// 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
|
// 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
|
// 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
|
// 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).
|
// 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 {
|
override fun onPreDraw(): Boolean {
|
||||||
hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
|
decorView.viewTreeObserver.removeOnPreDrawListener(this)
|
||||||
addTemporaryTouchSurfaceGhost()
|
addTemporaryTouchSurfaceGhost()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
hostDialogRoot.invalidate()
|
decorView.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTemporaryTouchSurfaceGhost() {
|
private fun addTemporaryTouchSurfaceGhost() {
|
||||||
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
|
// 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
|
// it to the dialog. We will wait for this ghost to be drawn before starting the animation.
|
||||||
// animation.
|
val ghost = GhostView.addGhost(touchSurface, decorView)
|
||||||
val ghost = GhostView.addGhost(touchSurface, hostDialogRoot)
|
|
||||||
|
|
||||||
// The ghost of the touch surface was just created, so the touch surface was made invisible.
|
// 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.
|
// We make it visible again until the ghost is actually drawn.
|
||||||
@@ -414,142 +420,6 @@ private class AnimatedDialog(
|
|||||||
touchSurface.invalidate()
|
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? {
|
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
|
||||||
if (view !is ViewGroup) {
|
if (view !is ViewGroup) {
|
||||||
return null
|
return null
|
||||||
@@ -569,26 +439,13 @@ private class AnimatedDialog(
|
|||||||
return null
|
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() {
|
private fun maybeStartLaunchAnimation() {
|
||||||
if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
|
if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the background dim.
|
// Show the background dim.
|
||||||
hostDialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||||
|
|
||||||
startAnimation(
|
startAnimation(
|
||||||
isLaunching = true,
|
isLaunching = true,
|
||||||
@@ -610,7 +467,7 @@ private class AnimatedDialog(
|
|||||||
// dismiss was called during the animation, dismiss again now to actually
|
// dismiss was called during the animation, dismiss again now to actually
|
||||||
// dismiss.
|
// dismiss.
|
||||||
if (dismissRequested) {
|
if (dismissRequested) {
|
||||||
hostDialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If necessary, we animate the dialog background when its bounds change. We do it
|
// 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()) {
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
context.mainExecutor.execute { onHostDialogDismissed(actualDismiss) }
|
context.mainExecutor.execute { onDialogDismissed() }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,23 +498,29 @@ private class AnimatedDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDismissing = true
|
isDismissing = true
|
||||||
hideDialogIntoView { instantDismiss: Boolean ->
|
hideDialogIntoView { animationRan: Boolean ->
|
||||||
if (instantDismiss) {
|
if (animationRan) {
|
||||||
originalDialog.hide()
|
// Instantly dismiss the dialog if we ran the animation into view. If it was
|
||||||
hostDialog.hide()
|
// skipped, dismiss() will run the window animation (which fades out the dialog).
|
||||||
|
dialog.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
originalDialog.dismiss()
|
dialog.setDismissOverride(null)
|
||||||
actualDismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide the dialog into the touch surface and call [dismissDialogs] when the animation is done
|
* Hide the dialog into the touch surface and call [onAnimationFinished] when the animation is
|
||||||
* (passing instantDismiss=true) or if it's skipped (passing instantDismiss=false) to actually
|
* done (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
|
||||||
* dismiss the dialogs.
|
* 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()) {
|
if (!shouldAnimateDialogIntoView()) {
|
||||||
Log.i(TAG, "Skipping animation of dialog into the touch surface")
|
Log.i(TAG, "Skipping animation of dialog into the touch surface")
|
||||||
|
|
||||||
@@ -669,7 +532,7 @@ private class AnimatedDialog(
|
|||||||
touchSurface.visibility = View.VISIBLE
|
touchSurface.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissDialogs(false /* instantDismiss */)
|
onAnimationFinished(false /* instantDismiss */)
|
||||||
onDialogDismissed(this@AnimatedDialog)
|
onDialogDismissed(this@AnimatedDialog)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -678,7 +541,7 @@ private class AnimatedDialog(
|
|||||||
isLaunching = false,
|
isLaunching = false,
|
||||||
onLaunchAnimationStart = {
|
onLaunchAnimationStart = {
|
||||||
// Remove the dim background as soon as we start the animation.
|
// 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 = {
|
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.
|
||||||
@@ -696,7 +559,7 @@ private class AnimatedDialog(
|
|||||||
// The animated ghost was just removed. We create a temporary ghost that will be
|
// 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
|
// 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).
|
// 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 {
|
touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
||||||
override fun onPreDraw(): Boolean {
|
override fun onPreDraw(): Boolean {
|
||||||
@@ -705,7 +568,7 @@ private class AnimatedDialog(
|
|||||||
// Now that the touch surface was drawn, we can remove the temporary ghost
|
// Now that the touch surface was drawn, we can remove the temporary ghost
|
||||||
// and instantly dismiss the dialog.
|
// and instantly dismiss the dialog.
|
||||||
GhostView.removeGhost(touchSurface)
|
GhostView.removeGhost(touchSurface)
|
||||||
dismissDialogs(true /* instantDismiss */)
|
onAnimationFinished(true /* instantDismiss */)
|
||||||
onDialogDismissed(this@AnimatedDialog)
|
onDialogDismissed(this@AnimatedDialog)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -721,14 +584,14 @@ private class AnimatedDialog(
|
|||||||
onLaunchAnimationStart: () -> Unit = {},
|
onLaunchAnimationStart: () -> Unit = {},
|
||||||
onLaunchAnimationEnd: () -> 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.
|
// dialog.
|
||||||
val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
|
val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
|
||||||
val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
|
val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
|
||||||
val startViewController = GhostedViewLaunchAnimatorController(startView)
|
val startViewController = GhostedViewLaunchAnimatorController(startView)
|
||||||
val endViewController = GhostedViewLaunchAnimatorController(endView)
|
val endViewController = GhostedViewLaunchAnimatorController(endView)
|
||||||
startViewController.launchContainer = hostDialogRoot
|
startViewController.launchContainer = decorView
|
||||||
endViewController.launchContainer = hostDialogRoot
|
endViewController.launchContainer = decorView
|
||||||
|
|
||||||
val endState = endViewController.createAnimatorState()
|
val endState = endViewController.createAnimatorState()
|
||||||
val controller = object : LaunchAnimator.Controller {
|
val controller = object : LaunchAnimator.Controller {
|
||||||
@@ -785,9 +648,15 @@ private class AnimatedDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldAnimateDialogIntoView(): Boolean {
|
private fun shouldAnimateDialogIntoView(): Boolean {
|
||||||
// Don't animate if the dialog was previously hidden using hide() (either on the host dialog
|
// Don't animate if the dialog was previously hidden using hide() or if we disabled the exit
|
||||||
// or on the original dialog) or if we disabled the exit animation.
|
// animation.
|
||||||
if (exitAnimationDisabled || !hostDialog.isShowing) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,9 +757,9 @@ private class AnimatedDialog(
|
|||||||
return touchSurface
|
return touchSurface
|
||||||
}
|
}
|
||||||
parentAnimatedDialog.exitAnimationDisabled = true
|
parentAnimatedDialog.exitAnimationDisabled = true
|
||||||
parentAnimatedDialog.originalDialog.hide()
|
parentAnimatedDialog.dialog.hide()
|
||||||
val view = parentAnimatedDialog.prepareForStackDismiss()
|
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
|
// Make the touch surface invisible, so we end up animating to it when we actually
|
||||||
// dismiss the stack
|
// dismiss the stack
|
||||||
view.visibility = View.INVISIBLE
|
view.visibility = View.INVISIBLE
|
||||||
|
|||||||
@@ -217,14 +217,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
|
|||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
|
||||||
super.onWindowFocusChanged(hasFocus);
|
|
||||||
if (!hasFocus && isShowing()) {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onHeaderIconClick() {
|
void onHeaderIconClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,14 +190,10 @@ public class DndTile extends QSTileImpl<BooleanState> {
|
|||||||
case Settings.Secure.ZEN_DURATION_PROMPT:
|
case Settings.Secure.ZEN_DURATION_PROMPT:
|
||||||
mUiHandler.post(() -> {
|
mUiHandler.post(() -> {
|
||||||
Dialog dialog = makeZenModeDialog();
|
Dialog dialog = makeZenModeDialog();
|
||||||
if (view != null) {
|
|
||||||
final Dialog hostDialog =
|
|
||||||
mDialogLaunchAnimator.showFromView(dialog, view, false);
|
|
||||||
setDialogListeners(dialog, hostDialog);
|
|
||||||
} else {
|
|
||||||
// If we are not launching with animator, register default
|
|
||||||
// dismiss listener
|
|
||||||
SystemUIDialog.registerDismissListener(dialog);
|
SystemUIDialog.registerDismissListener(dialog);
|
||||||
|
if (view != null) {
|
||||||
|
mDialogLaunchAnimator.showFromView(dialog, view, false);
|
||||||
|
} else {
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -222,12 +218,6 @@ public class DndTile extends QSTileImpl<BooleanState> {
|
|||||||
return dialog;
|
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
|
@Override
|
||||||
protected void handleSecondaryClick(@Nullable View view) {
|
protected void handleSecondaryClick(@Nullable View view) {
|
||||||
if (mController.isVolumeRestricted()) {
|
if (mController.isVolumeRestricted()) {
|
||||||
|
|||||||
@@ -94,24 +94,24 @@ class UserSwitchDialogController @VisibleForTesting constructor(
|
|||||||
|
|
||||||
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
|
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
|
||||||
|
|
||||||
val hostDialog = dialogLaunchAnimator.showFromView(this, view)
|
dialogLaunchAnimator.showFromView(this, view)
|
||||||
adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
|
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DialogShowerImpl(
|
private class DialogShowerImpl(
|
||||||
private val hostDialog: Dialog,
|
private val animateFrom: Dialog,
|
||||||
private val dialogLaunchAnimator: DialogLaunchAnimator
|
private val dialogLaunchAnimator: DialogLaunchAnimator
|
||||||
) : DialogInterface by hostDialog, DialogShower {
|
) : DialogInterface by animateFrom, DialogShower {
|
||||||
override fun showDialog(dialog: Dialog): Dialog {
|
override fun showDialog(dialog: Dialog) {
|
||||||
return dialogLaunchAnimator.showFromDialog(
|
dialogLaunchAnimator.showFromDialog(
|
||||||
dialog,
|
dialog,
|
||||||
parentHostDialog = hostDialog
|
animateFrom = animateFrom
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DialogShower : DialogInterface {
|
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.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.service.dreams.IDreamManager;
|
||||||
|
|
||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
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.StatusBarIconController;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
|
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
|
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.OngoingCallController;
|
||||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
|
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
|
||||||
import com.android.systemui.statusbar.policy.RemoteInputUriController;
|
import com.android.systemui.statusbar.policy.RemoteInputUriController;
|
||||||
import com.android.systemui.statusbar.window.StatusBarWindowController;
|
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.tracing.ProtoTracer;
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||||
import com.android.systemui.util.time.SystemClock;
|
import com.android.systemui.util.time.SystemClock;
|
||||||
@@ -320,7 +318,7 @@ public interface StatusBarDependenciesModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
|
static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
|
||||||
LaunchAnimator launchAnimator) {
|
LaunchAnimator launchAnimator, IDreamManager dreamManager) {
|
||||||
return new DialogLaunchAnimator(context, launchAnimator, new SystemUIHostDialogProvider());
|
return new DialogLaunchAnimator(context, launchAnimator, dreamManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,30 +40,21 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.android.systemui.Dependency;
|
import com.android.systemui.Dependency;
|
||||||
import com.android.systemui.R;
|
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.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
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.
|
* Base class for dialogs that should appear over panels and keyguard.
|
||||||
* The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast,
|
* The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast,
|
||||||
* and dismisses itself when it receives the broadcast.
|
* and dismisses itself when it receives the broadcast.
|
||||||
*/
|
*/
|
||||||
public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigChangedCallback {
|
||||||
ViewRootImpl.ConfigChangedCallback {
|
|
||||||
// TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
|
// TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
|
||||||
private static final String FLAG_TABLET_DIALOG_WIDTH =
|
private static final String FLAG_TABLET_DIALOG_WIDTH =
|
||||||
"persist.systemui.flag_tablet_dialog_width";
|
"persist.systemui.flag_tablet_dialog_width";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final DismissReceiver mDismissReceiver;
|
private final DismissReceiver mDismissReceiver;
|
||||||
private final Set<DialogListener> mDialogListeners = new LinkedHashSet<>();
|
|
||||||
private final Handler mHandler = new Handler();
|
private final Handler mHandler = new Handler();
|
||||||
|
|
||||||
private int mLastWidth = Integer.MIN_VALUE;
|
private int mLastWidth = Integer.MIN_VALUE;
|
||||||
@@ -117,10 +108,6 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
|||||||
mLastWidth = width;
|
mLastWidth = width;
|
||||||
mLastHeight = height;
|
mLastHeight = height;
|
||||||
getWindow().setLayout(width, height);
|
getWindow().setLayout(width, height);
|
||||||
|
|
||||||
for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
|
|
||||||
listener.onSizeChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -197,60 +184,6 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
|||||||
ViewRootImpl.removeConfigCallback(this);
|
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) {
|
public void setShowForAllUsers(boolean show) {
|
||||||
setShowForAllUsers(this, show);
|
setShowForAllUsers(this, show);
|
||||||
}
|
}
|
||||||
@@ -364,11 +297,7 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
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.Prefs.Key;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.SystemUISecondaryUserService;
|
import com.android.systemui.SystemUISecondaryUserService;
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
@@ -133,6 +134,7 @@ public class UserSwitcherController implements Dumpable {
|
|||||||
private final IActivityTaskManager mActivityTaskManager;
|
private final IActivityTaskManager mActivityTaskManager;
|
||||||
private final InteractionJankMonitor mInteractionJankMonitor;
|
private final InteractionJankMonitor mInteractionJankMonitor;
|
||||||
private final LatencyTracker mLatencyTracker;
|
private final LatencyTracker mLatencyTracker;
|
||||||
|
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||||
|
|
||||||
private ArrayList<UserRecord> mUsers = new ArrayList<>();
|
private ArrayList<UserRecord> mUsers = new ArrayList<>();
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -180,7 +182,8 @@ public class UserSwitcherController implements Dumpable {
|
|||||||
@Background Executor bgExecutor,
|
@Background Executor bgExecutor,
|
||||||
InteractionJankMonitor interactionJankMonitor,
|
InteractionJankMonitor interactionJankMonitor,
|
||||||
LatencyTracker latencyTracker,
|
LatencyTracker latencyTracker,
|
||||||
DumpManager dumpManager) {
|
DumpManager dumpManager,
|
||||||
|
DialogLaunchAnimator dialogLaunchAnimator) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mActivityManager = activityManager;
|
mActivityManager = activityManager;
|
||||||
mUserTracker = userTracker;
|
mUserTracker = userTracker;
|
||||||
@@ -208,6 +211,8 @@ public class UserSwitcherController implements Dumpable {
|
|||||||
mHandler = handler;
|
mHandler = handler;
|
||||||
mActivityStarter = activityStarter;
|
mActivityStarter = activityStarter;
|
||||||
mUserManager = userManager;
|
mUserManager = userManager;
|
||||||
|
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(Intent.ACTION_USER_ADDED);
|
filter.addAction(Intent.ACTION_USER_ADDED);
|
||||||
filter.addAction(Intent.ACTION_USER_REMOVED);
|
filter.addAction(Intent.ACTION_USER_REMOVED);
|
||||||
@@ -1179,7 +1184,7 @@ public class UserSwitcherController implements Dumpable {
|
|||||||
cancel();
|
cancel();
|
||||||
} else {
|
} else {
|
||||||
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
|
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
|
||||||
dismissStack();
|
mDialogLaunchAnimator.dismissStack(this);
|
||||||
removeGuestUser(mGuestId, mTargetId);
|
removeGuestUser(mGuestId, mTargetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1210,7 +1215,7 @@ public class UserSwitcherController implements Dumpable {
|
|||||||
if (which == BUTTON_NEGATIVE) {
|
if (which == BUTTON_NEGATIVE) {
|
||||||
cancel();
|
cancel();
|
||||||
} else {
|
} else {
|
||||||
dismissStack();
|
mDialogLaunchAnimator.dismissStack(this);
|
||||||
if (ActivityManager.isUserAMonkey()) {
|
if (ActivityManager.isUserAMonkey()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,49 @@ package com.android.systemui.animation
|
|||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.service.dreams.IDreamManager
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.testing.ViewUtils
|
import android.testing.ViewUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.internal.policy.DecorView
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.animation.DialogListener.DismissReason
|
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
import junit.framework.Assert.assertFalse
|
import junit.framework.Assert.assertFalse
|
||||||
|
import junit.framework.Assert.assertNotNull
|
||||||
import junit.framework.Assert.assertTrue
|
import junit.framework.Assert.assertTrue
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
class DialogLaunchAnimatorTest : SysuiTestCase() {
|
class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||||
private val launchAnimator = LaunchAnimator(context, isForTesting = true)
|
private val launchAnimator = LaunchAnimator(context, isForTesting = true)
|
||||||
private val hostDialogprovider = TestHostDialogProvider()
|
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||||
private val dialogLaunchAnimator =
|
|
||||||
DialogLaunchAnimator(context, launchAnimator, hostDialogprovider)
|
|
||||||
|
|
||||||
private val attachedViews = mutableSetOf<View>()
|
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
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
runOnMainThreadAndWaitForIdleSync {
|
runOnMainThreadAndWaitForIdleSync {
|
||||||
@@ -44,76 +58,66 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
fun testShowDialogFromView() {
|
fun testShowDialogFromView() {
|
||||||
// Show the dialog. showFromView() must be called on the main thread with a dialog created
|
// Show the dialog. showFromView() must be called on the main thread with a dialog created
|
||||||
// on the main thread too.
|
// on the main thread too.
|
||||||
val (dialog, hostDialog) = createDialogAndHostDialog()
|
val dialog = createAndShowDialog()
|
||||||
|
|
||||||
// Only the host dialog is actually showing.
|
assertTrue(dialog.isShowing)
|
||||||
assertTrue(hostDialog.isShowing)
|
|
||||||
assertFalse(dialog.isShowing)
|
|
||||||
|
|
||||||
// The dialog onStart() method was called but not onStop().
|
// The dialog is now fullscreen.
|
||||||
assertTrue(dialog.onStartCalled)
|
val window = dialog.window
|
||||||
assertFalse(dialog.onStopCalled)
|
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.
|
// The single DecorView child is a transparent fullscreen view that will dismiss the dialog
|
||||||
val hostDialogContent = hostDialog.findViewById<ViewGroup>(android.R.id.content)
|
// when clicked.
|
||||||
assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
|
assertEquals(1, decorView.childCount)
|
||||||
assertEquals(1, hostDialogContent.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
|
// The single transparent background child is a fake window with the same size and
|
||||||
// original dialog window.
|
// background as the dialog initially had.
|
||||||
val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
|
assertEquals(1, transparentBackground.childCount)
|
||||||
assertEquals(1, hostDialogRoot.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
|
// The dialog content is inside this fake window view.
|
||||||
assertEquals(1, dialogContentParent.childCount)
|
assertNotNull(
|
||||||
assertEquals(TestDialog.DIALOG_WIDTH, dialogContentParent.layoutParams.width)
|
dialogContentWithBackground.findViewByPredicate { it === dialog.contentView })
|
||||||
assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentParent.layoutParams.height)
|
|
||||||
|
|
||||||
val dialogContent = dialogContentParent.getChildAt(0)
|
// Clicking the transparent background should dismiss the dialog.
|
||||||
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)
|
|
||||||
runOnMainThreadAndWaitForIdleSync {
|
runOnMainThreadAndWaitForIdleSync {
|
||||||
// TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and
|
// 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
|
// make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in
|
||||||
// Forrest.
|
// Forrest.
|
||||||
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
|
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
|
||||||
|
|
||||||
dialog.dismiss()
|
transparentBackground.performClick()
|
||||||
}
|
}
|
||||||
assertFalse(hostDialog.isShowing)
|
|
||||||
assertFalse(dialog.isShowing)
|
assertFalse(dialog.isShowing)
|
||||||
assertTrue(hostDialog.wasDismissed)
|
|
||||||
assertTrue(dialog.onStopCalled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStackedDialogsDismissesAll() {
|
fun testStackedDialogsDismissesAll() {
|
||||||
val (_, hostDialogFirst) = createDialogAndHostDialog()
|
val firstDialog = createAndShowDialog()
|
||||||
val (dialogSecond, hostDialogSecond) = createDialogAndHostDialogFromDialog(hostDialogFirst)
|
val secondDialog = createDialogAndShowFromDialog(firstDialog)
|
||||||
|
|
||||||
|
assertTrue(firstDialog.isShowing)
|
||||||
|
assertTrue(secondDialog.isShowing)
|
||||||
runOnMainThreadAndWaitForIdleSync {
|
runOnMainThreadAndWaitForIdleSync {
|
||||||
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
|
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
|
||||||
dialogSecond.dismissStack()
|
dialogLaunchAnimator.dismissStack(secondDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(hostDialogSecond.wasDismissed)
|
assertFalse(firstDialog.isShowing)
|
||||||
assertTrue(hostDialogFirst.wasDismissed)
|
assertFalse(secondDialog.isShowing)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDialogAndHostDialog(): Pair<TestDialog, TestHostDialog> {
|
private fun createAndShowDialog(): TestDialog {
|
||||||
return runOnMainThreadAndWaitForIdleSync {
|
return runOnMainThreadAndWaitForIdleSync {
|
||||||
val touchSurfaceRoot = LinearLayout(context)
|
val touchSurfaceRoot = LinearLayout(context)
|
||||||
val touchSurface = View(context)
|
val touchSurface = View(context)
|
||||||
@@ -125,22 +129,16 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
attachedViews.add(touchSurfaceRoot)
|
attachedViews.add(touchSurfaceRoot)
|
||||||
|
|
||||||
val dialog = TestDialog(context)
|
val dialog = TestDialog(context)
|
||||||
val hostDialog =
|
dialogLaunchAnimator.showFromView(dialog, touchSurface)
|
||||||
dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
|
dialog
|
||||||
dialog to hostDialog
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDialogAndHostDialogFromDialog(
|
private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog {
|
||||||
hostParent: Dialog
|
|
||||||
): Pair<TestDialog, TestHostDialog> {
|
|
||||||
return runOnMainThreadAndWaitForIdleSync {
|
return runOnMainThreadAndWaitForIdleSync {
|
||||||
val dialog = TestDialog(context)
|
val dialog = TestDialog(context)
|
||||||
val hostDialog = dialogLaunchAnimator.showFromDialog(
|
dialogLaunchAnimator.showFromDialog(dialog, animateFrom)
|
||||||
dialog,
|
dialog
|
||||||
hostParent
|
|
||||||
) as TestHostDialog
|
|
||||||
dialog to hostDialog
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,50 +151,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestHostDialogProvider : HostDialogProvider {
|
private class TestDialog(context: Context) : Dialog(context) {
|
||||||
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 {
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DIALOG_WIDTH = 100
|
const val DIALOG_WIDTH = 100
|
||||||
const val DIALOG_HEIGHT = 200
|
const val DIALOG_HEIGHT = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
private val listeners = hashSetOf<DialogListener>()
|
|
||||||
val contentView = View(context)
|
val contentView = View(context)
|
||||||
var onStartCalled = false
|
val windowBackground = ColorDrawable(Color.RED)
|
||||||
var onStopCalled = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
|
// 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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
|
|
||||||
setContentView(contentView)
|
setContentView(contentView)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
|
||||||
super.onStart()
|
window.setBackgroundDrawable(windowBackground)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.systemui.qs.user
|
package com.android.systemui.qs.user
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.provider.Settings
|
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.qs.tiles.UserDetailView
|
||||||
import com.android.systemui.statusbar.phone.SystemUIDialog
|
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||||
import com.android.systemui.util.mockito.any
|
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.capture
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -42,7 +40,6 @@ import org.mockito.ArgumentMatcher
|
|||||||
import org.mockito.Captor
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.Mockito.anyBoolean
|
|
||||||
import org.mockito.Mockito.anyInt
|
import org.mockito.Mockito.anyInt
|
||||||
import org.mockito.Mockito.argThat
|
import org.mockito.Mockito.argThat
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
@@ -65,8 +62,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
|
|||||||
private lateinit var launchView: View
|
private lateinit var launchView: View
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||||
@Mock
|
|
||||||
private lateinit var hostDialog: Dialog
|
|
||||||
@Captor
|
@Captor
|
||||||
private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
|
private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
|
||||||
|
|
||||||
@@ -78,8 +73,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
`when`(launchView.context).thenReturn(mContext)
|
`when`(launchView.context).thenReturn(mContext)
|
||||||
`when`(dialog.context).thenReturn(mContext)
|
`when`(dialog.context).thenReturn(mContext)
|
||||||
`when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
|
|
||||||
.thenReturn(hostDialog)
|
|
||||||
|
|
||||||
controller = UserSwitchDialogController(
|
controller = UserSwitchDialogController(
|
||||||
{ userDetailViewAdapter },
|
{ userDetailViewAdapter },
|
||||||
@@ -151,18 +144,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
|
|||||||
verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
|
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> {
|
private class IntentMatcher(private val action: String) : ArgumentMatcher<Intent> {
|
||||||
override fun matches(argument: Intent?): Boolean {
|
override fun matches(argument: Intent?): Boolean {
|
||||||
return argument?.action == action
|
return argument?.action == action
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.android.internal.util.UserIcons
|
|||||||
import com.android.systemui.GuestResumeSessionReceiver
|
import com.android.systemui.GuestResumeSessionReceiver
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
@@ -94,6 +95,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
|
@Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
|
||||||
@Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
|
@Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
|
||||||
@Mock private lateinit var threadedRenderer: ThreadedRenderer
|
@Mock private lateinit var threadedRenderer: ThreadedRenderer
|
||||||
|
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||||
private lateinit var testableLooper: TestableLooper
|
private lateinit var testableLooper: TestableLooper
|
||||||
private lateinit var uiBgExecutor: FakeExecutor
|
private lateinit var uiBgExecutor: FakeExecutor
|
||||||
private lateinit var uiEventLogger: UiEventLoggerFake
|
private lateinit var uiEventLogger: UiEventLoggerFake
|
||||||
@@ -147,7 +149,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
|
|||||||
uiBgExecutor,
|
uiBgExecutor,
|
||||||
interactionJankMonitor,
|
interactionJankMonitor,
|
||||||
latencyTracker,
|
latencyTracker,
|
||||||
dumpManager)
|
dumpManager,
|
||||||
|
dialogLaunchAnimator)
|
||||||
userSwitcherController.mPauseRefreshUsers = true
|
userSwitcherController.mPauseRefreshUsers = true
|
||||||
|
|
||||||
// Since userSwitcherController involves InteractionJankMonitor.
|
// Since userSwitcherController involves InteractionJankMonitor.
|
||||||
|
|||||||
Reference in New Issue
Block a user