summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt171
1 files changed, 125 insertions, 46 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index dbb5831c9d47..669a054eaa2a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -21,10 +21,11 @@ import android.content.Context
import android.graphics.Color
import android.os.Looper
import android.util.Log
+import android.view.GhostView
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
-import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
@@ -225,10 +226,12 @@ private class DialogLaunchAnimation(
private var isDismissing = false
private var dismissRequested = false
- private var drawHostDialog = false
var ignoreNextCallToHide = false
var exitAnimationDisabled = false
+ private var isTouchSurfaceGhostDrawn = false
+ private var isOriginalDialogViewLaidOut = false
+
fun start() {
// Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
hostDialog.show()
@@ -267,19 +270,65 @@ private class DialogLaunchAnimation(
window.setDecorFitsSystemWindows(false)
}
- // Prevent the host dialog from drawing until the animation starts.
- hostDialogRoot.viewTreeObserver.addOnPreDrawListener(
- object : ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- if (drawHostDialog) {
- hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
- return true
- }
+ // Disable the dim. We will enable it once we start the animation.
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+
+ // 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
+ // 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 {
+ override fun onPreDraw(): Boolean {
+ hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
+ addTemporaryTouchSurfaceGhost()
+ return true
+ }
+ })
+ hostDialogRoot.invalidate()
+ }
- return false
- }
+ 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)
+
+ // 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.
+ touchSurface.visibility = View.VISIBLE
+
+ // Wait for the ghost to be drawn before continuing.
+ ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ ghost.viewTreeObserver.removeOnPreDrawListener(this)
+ onTouchSurfaceGhostDrawn()
+ return true
}
- )
+ })
+ ghost.invalidate()
+ }
+
+ private fun onTouchSurfaceGhostDrawn() {
+ // Make the touch surface invisible and make sure that it stays invisible as long as the
+ // dialog is shown or animating.
+ touchSurface.visibility = View.INVISIBLE
+ if (touchSurface is LaunchableView) {
+ touchSurface.setShouldBlockVisibilityChanges(true)
+ }
+
+ // Add a pre draw listener to (maybe) start the animation once the touch surface is
+ // actually invisible.
+ touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
+ isTouchSurfaceGhostDrawn = true
+ maybeStartLaunchAnimation()
+ return true
+ }
+ })
+ touchSurface.invalidate()
}
/** Get the content view of [originalDialog] and pass it to [then]. */
@@ -291,7 +340,7 @@ private class DialogLaunchAnimation(
?: throw IllegalStateException("Dialog does not have any android.R.id.content view")
androidContent.viewTreeObserver.addOnPreDrawListener(
- object : ViewTreeObserver.OnPreDrawListener {
+ object : OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (androidContent.childCount == 1) {
androidContent.viewTreeObserver.removeOnPreDrawListener(this)
@@ -369,38 +418,47 @@ private class DialogLaunchAnimation(
oldBottom: Int
) {
dialogView.removeOnLayoutChangeListener(this)
- startAnimation(
- isLaunching = true,
- onLaunchAnimationStart = {
- drawHostDialog = true
-
- // The ghost of the touch surface was just created, so the touch surface is
- // currently invisible. We need to make sure that it stays invisible as long
- // as the dialog is shown or animating.
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(true)
- }
- },
- onLaunchAnimationEnd = {
- touchSurface.setTag(R.id.launch_animation_running, null)
-
- // We hide the touch surface when the dialog is showing. We will make this
- // view visible again when dismissing the dialog.
- touchSurface.visibility = View.INVISIBLE
-
- isLaunching = false
-
- // dismiss was called during the animation, dismiss again now to actually
- // dismiss.
- if (dismissRequested) {
- hostDialog.dismiss()
- }
- }
- )
+
+ isOriginalDialogViewLaidOut = true
+ maybeStartLaunchAnimation()
}
})
}
+ private fun maybeStartLaunchAnimation() {
+ if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
+ return
+ }
+
+ // Show the background dim.
+ hostDialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+
+ startAnimation(
+ isLaunching = true,
+ onLaunchAnimationStart = {
+ // Remove the temporary ghost. Another ghost (that ghosts only the touch surface
+ // content, and not its background) will be added right after this and will be
+ // animated.
+ GhostView.removeGhost(touchSurface)
+ },
+ onLaunchAnimationEnd = {
+ touchSurface.setTag(R.id.launch_animation_running, null)
+
+ // We hide the touch surface when the dialog is showing. We will make this
+ // view visible again when dismissing the dialog.
+ touchSurface.visibility = View.INVISIBLE
+
+ isLaunching = false
+
+ // dismiss was called during the animation, dismiss again now to actually
+ // dismiss.
+ if (dismissRequested) {
+ hostDialog.dismiss()
+ }
+ }
+ )
+ }
+
private fun onHostDialogDismissed(actualDismiss: () -> Unit) {
if (Looper.myLooper() != Looper.getMainLooper()) {
context.mainExecutor.execute { onHostDialogDismissed(actualDismiss) }
@@ -467,8 +525,26 @@ private class DialogLaunchAnimation(
touchSurface.visibility = View.VISIBLE
originalDialogView!!.visibility = View.INVISIBLE
- dismissDialogs(true /* instantDismiss */)
- onDialogDismissed(this@DialogLaunchAnimation)
+
+ // 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)
+
+ touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
+
+ // Now that the touch surface was drawn, we can remove the temporary ghost
+ // and instantly dismiss the dialog.
+ GhostView.removeGhost(touchSurface)
+ dismissDialogs(true /* instantDismiss */)
+ onDialogDismissed(this@DialogLaunchAnimation)
+
+ return true
+ }
+ })
+ touchSurface.invalidate()
}
)
}
@@ -503,10 +579,13 @@ private class DialogLaunchAnimation(
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // During launch, onLaunchAnimationStart will be used to remove the temporary touch
+ // surface ghost so it is important to call this before calling
+ // onLaunchAnimationStart on the controller (which will create its own ghost).
+ onLaunchAnimationStart()
+
startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
-
- onLaunchAnimationStart()
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {