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

Commit e0272690 authored by Matt Casey's avatar Matt Casey Committed by Android (Google) Code Review
Browse files

Merge changes If525186a,I01ff7558,I2c1efbf1,Ieff77331 into 24D1-dev

* changes:
  Update shelf screenshot UI entrance animation
  Fix back gesture/dismiss button in screenshot shelf UI
  Don't remove background task immediately on screenshot service end
  Add swipe dismissal for screenshot shelf view
parents 50c36bfd 1789b5c9
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -147,4 +147,11 @@
        <include layout="@layout/screenshot_work_profile_first_run" />
        <include layout="@layout/screenshot_detection_notice" />
    </FrameLayout>
    <ImageView
        android:id="@+id/screenshot_flash"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        android:elevation="12dp"
        android:src="@android:color/white"/>
</com.android.systemui.screenshot.ui.ScreenshotShelfView>
+1 −1
Original line number Diff line number Diff line
@@ -519,7 +519,7 @@ public class ScreenshotController {
        removeWindow();
        releaseMediaPlayer();
        releaseContext();
        mBgExecutor.shutdownNow();
        mBgExecutor.shutdown();
    }

    /**
+17 −3
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.view.WindowInsets
import android.view.WindowManager
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.core.animation.doOnEnd
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
@@ -77,7 +78,13 @@ constructor(
    private val animationController = ScreenshotAnimationController(view)

    init {
        ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context))
        ScreenshotShelfViewBinder.bind(
            view,
            viewModel,
            LayoutInflater.from(context),
            onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
            onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }
        )
        addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
        setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
        debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" }
@@ -103,7 +110,10 @@ constructor(
    override fun updateOrientation(insets: WindowInsets) {}

    override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
        return animationController.getEntranceAnimation()
        val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
        // reset the timeout when animation finishes
        entrance.doOnEnd { callbacks?.onUserInteraction() }
        return entrance
    }

    override fun addQuickShareChip(quickShareAction: Notification.Action) {}
@@ -111,6 +121,10 @@ constructor(
    override fun setChipIntents(imageData: SavedImageData) {}

    override fun requestDismissal(event: ScreenshotEvent?) {
        requestDismissal(event, null)
    }

    private fun requestDismissal(event: ScreenshotEvent?, velocity: Float?) {
        debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }

        // If we're already animating out, don't restart the animation
@@ -119,7 +133,7 @@ constructor(
            return
        }
        event?.let { logger.log(it, 0, packageName) }
        val animator = animationController.getExitAnimation()
        val animator = animationController.getSwipeDismissAnimation(velocity)
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
+140 −24
Original line number Diff line number Diff line
@@ -17,43 +17,103 @@
package com.android.systemui.screenshot.ui

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.graphics.PointF
import android.graphics.Rect
import android.util.MathUtils
import android.view.View
import android.view.animation.AnimationUtils
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import com.android.systemui.res.R
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.sign

class ScreenshotAnimationController(private val view: View) {
class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
    private var animator: Animator? = null
    private val screenshotPreview = view.requireViewById<View>(R.id.screenshot_preview)
    private val flashView = view.requireViewById<View>(R.id.screenshot_flash)
    private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)
    private val fastOutSlowIn =
        AnimationUtils.loadInterpolator(view.context, android.R.interpolator.fast_out_slow_in)
    private val staticUI =
        listOf<View>(
            view.requireViewById(R.id.screenshot_preview_border),
            view.requireViewById(R.id.actions_container_background),
            view.requireViewById(R.id.screenshot_badge),
            view.requireViewById(R.id.screenshot_dismiss_button)
        )

    fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator {
        val entranceAnimation = AnimatorSet()

    fun getEntranceAnimation(): Animator {
        val animator = ValueAnimator.ofFloat(0f, 1f)
        animator.addUpdateListener { view.alpha = it.animatedFraction }
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
                    view.alpha = 0f
        val previewAnimator = getPreviewAnimator(bounds)

        if (showFlash) {
            val flashInAnimator =
                ObjectAnimator.ofFloat(flashView, "alpha", 0f, 1f).apply {
                    duration = FLASH_IN_DURATION_MS
                    interpolator = fastOutSlowIn
                }
                override fun onAnimationEnd(animator: Animator) {
                    view.alpha = 1f
            val flashOutAnimator =
                ObjectAnimator.ofFloat(flashView, "alpha", 1f, 0f).apply {
                    duration = FLASH_OUT_DURATION_MS
                    interpolator = fastOutSlowIn
                }
            flashInAnimator.doOnStart { flashView.visibility = View.VISIBLE }
            flashOutAnimator.doOnEnd { flashView.visibility = View.GONE }
            entranceAnimation.play(flashOutAnimator).after(flashInAnimator)
            entranceAnimation.play(previewAnimator).with(flashOutAnimator)
            entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE }
        }
        )

        val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f)
        fadeInAnimator.addUpdateListener {
            for (child in staticUI) {
                child.alpha = it.animatedValue as Float
            }
        }
        entranceAnimation.play(fadeInAnimator).after(previewAnimator)
        entranceAnimation.doOnStart {
            for (child in staticUI) {
                child.alpha = 0f
            }
        }

        this.animator = entranceAnimation
        return entranceAnimation
    }

    fun getSwipeReturnAnimation(): Animator {
        animator?.cancel()
        val animator = ValueAnimator.ofFloat(view.translationX, 0f)
        animator.addUpdateListener { view.translationX = it.animatedValue as Float }
        this.animator = animator
        return animator
    }

    fun getExitAnimation(): Animator {
        val animator = ValueAnimator.ofFloat(1f, 0f)
        animator.addUpdateListener { view.alpha = it.animatedValue as Float }
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
                    view.alpha = 1f
                }
                override fun onAnimationEnd(animator: Animator) {
                    view.alpha = 0f
    fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator {
        val velocity = getAdjustedVelocity(requestedVelocity)
        val screenWidth = view.resources.displayMetrics.widthPixels
        // translation at which point the visible UI is fully off the screen (in the direction
        // according to velocity)
        val endX =
            if (velocity < 0) {
                -1f * actionContainer.right
            } else {
                (screenWidth - actionContainer.left).toFloat()
            }
        val distance = endX - view.translationX
        val animator = ValueAnimator.ofFloat(view.translationX, endX)
        animator.addUpdateListener {
            view.translationX = it.animatedValue as Float
            view.alpha = 1f - it.animatedFraction
        }
        )
        animator.duration = ((abs(distance / velocity))).toLong()

        this.animator = animator
        return animator
    }
@@ -61,4 +121,60 @@ class ScreenshotAnimationController(private val view: View) {
    fun cancel() {
        animator?.cancel()
    }

    private fun getPreviewAnimator(bounds: Rect): Animator {
        val targetPosition = Rect()
        screenshotPreview.getHitRect(targetPosition)
        val startXScale = bounds.width() / targetPosition.width().toFloat()
        val startYScale = bounds.height() / targetPosition.height().toFloat()
        val startPos = PointF(bounds.exactCenterX(), bounds.exactCenterY())
        val endPos = PointF(targetPosition.exactCenterX(), targetPosition.exactCenterY())

        val previewYAnimator =
            ValueAnimator.ofFloat(startPos.y, endPos.y).apply {
                duration = PREVIEW_Y_ANIMATION_DURATION_MS
                interpolator = fastOutSlowIn
            }
        previewYAnimator.addUpdateListener {
            val progress = it.animatedValue as Float
            screenshotPreview.y = progress - screenshotPreview.height / 2f
        }
        // scale animation starts/finishes at the same time as x placement
        val previewXAndScaleAnimator =
            ValueAnimator.ofFloat(0f, 1f).apply {
                duration = PREVIEW_X_ANIMATION_DURATION_MS
                interpolator = fastOutSlowIn
            }
        previewXAndScaleAnimator.addUpdateListener {
            val t = it.animatedFraction
            screenshotPreview.scaleX = MathUtils.lerp(startXScale, 1f, t)
            screenshotPreview.scaleY = MathUtils.lerp(startYScale, 1f, t)
            screenshotPreview.x =
                MathUtils.lerp(startPos.x, endPos.x, t) - screenshotPreview.width / 2f
        }

        val previewAnimator = AnimatorSet()
        previewAnimator.play(previewXAndScaleAnimator).with(previewYAnimator)

        previewAnimator.doOnStart { screenshotPreview.visibility = View.VISIBLE }
        return previewAnimator
    }

    private fun getAdjustedVelocity(requestedVelocity: Float?): Float {
        return if (requestedVelocity == null) {
            val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
            // dismiss to the left in LTR locales, to the right in RTL
            if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY
        } else {
            sign(requestedVelocity) * max(MINIMUM_VELOCITY, abs(requestedVelocity))
        }
    }

    companion object {
        private const val MINIMUM_VELOCITY = 1.5f // pixels per second
        private const val FLASH_IN_DURATION_MS: Long = 133
        private const val FLASH_OUT_DURATION_MS: Long = 217
        private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234
        private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.graphics.Insets
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
@@ -30,6 +31,7 @@ import com.android.systemui.screenshot.FloatingWindowUtil
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
    ConstraintLayout(context, attrs) {
    lateinit var screenshotPreview: ImageView
    var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null

    private val displayMetrics = context.resources.displayMetrics
    private val tmpRect = Rect()
@@ -38,6 +40,8 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :

    override fun onFinishInflate() {
        super.onFinishInflate()
        // Get focus so that the key events go to the layout.
        isFocusableInTouchMode = true
        screenshotPreview = requireViewById(R.id.screenshot_preview)
        actionsContainerBackground = requireViewById(R.id.actions_container_background)
        dismissButton = requireViewById(R.id.screenshot_dismiss_button)
@@ -83,4 +87,11 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
    companion object {
        private const val TOUCH_PADDING_DP = 12f
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (onTouchInterceptListener?.invoke(ev) == true) {
            return true
        }
        return super.onInterceptTouchEvent(ev)
    }
}
Loading