Loading packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +33 −6 Original line number Original line Diff line number Diff line Loading @@ -56,7 +56,7 @@ import javax.inject.Inject * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. */ */ @SysUISingleton @SysUISingleton class MediaTttChipControllerReceiver @Inject constructor( open class MediaTttChipControllerReceiver @Inject constructor( private val commandQueue: CommandQueue, private val commandQueue: CommandQueue, context: Context, context: Context, @MediaTttReceiverLogger logger: MediaTttLogger, @MediaTttReceiverLogger logger: MediaTttLogger, Loading Loading @@ -183,15 +183,28 @@ class MediaTttChipControllerReceiver @Inject constructor( val appIconView = view.getAppIconView() val appIconView = view.getAppIconView() appIconView.animate() appIconView.animate() .translationYBy(-1 * getTranslationAmount().toFloat()) .translationYBy(-1 * getTranslationAmount().toFloat()) .setDuration(30.frames) .setDuration(ICON_TRANSLATION_ANIM_DURATION) .start() .start() appIconView.animate() appIconView.animate() .alpha(1f) .alpha(1f) .setDuration(5.frames) .setDuration(ICON_ALPHA_ANIM_DURATION) .start() .start() // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. appIconView.postOnAnimation { view.requestAccessibilityFocus() } appIconView.postOnAnimation { view.requestAccessibilityFocus() } startRipple(view.requireViewById(R.id.ripple)) expandRipple(view.requireViewById(R.id.ripple)) } override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { val appIconView = view.getAppIconView() appIconView.animate() .translationYBy(getTranslationAmount().toFloat()) .setDuration(ICON_TRANSLATION_ANIM_DURATION) .start() appIconView.animate() .alpha(0f) .setDuration(ICON_ALPHA_ANIM_DURATION) .start() (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd) } } override fun getTouchableRegion(view: View, outRect: Rect) { override fun getTouchableRegion(view: View, outRect: Rect) { Loading @@ -205,11 +218,22 @@ class MediaTttChipControllerReceiver @Inject constructor( return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) } } private fun startRipple(rippleView: ReceiverChipRippleView) { private fun expandRipple(rippleView: ReceiverChipRippleView) { if (rippleView.rippleInProgress()) { if (rippleView.rippleInProgress()) { // Skip if ripple is still playing // Skip if ripple is still playing return return } } // In case the device orientation changes, we need to reset the layout. rippleView.addOnLayoutChangeListener ( View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> if (v == null) return@OnLayoutChangeListener val layoutChangedRippleView = v as ReceiverChipRippleView layoutRipple(layoutChangedRippleView) layoutChangedRippleView.invalidate() } ) rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewDetachedFromWindow(view: View?) {} override fun onViewDetachedFromWindow(view: View?) {} Loading @@ -219,7 +243,7 @@ class MediaTttChipControllerReceiver @Inject constructor( } } val attachedRippleView = view as ReceiverChipRippleView val attachedRippleView = view as ReceiverChipRippleView layoutRipple(attachedRippleView) layoutRipple(attachedRippleView) attachedRippleView.startRipple() attachedRippleView.expandRipple() attachedRippleView.removeOnAttachStateChangeListener(this) attachedRippleView.removeOnAttachStateChangeListener(this) } } }) }) Loading @@ -242,6 +266,9 @@ class MediaTttChipControllerReceiver @Inject constructor( } } } } val ICON_TRANSLATION_ANIM_DURATION = 30.frames val ICON_ALPHA_ANIM_DURATION = 5.frames data class ChipReceiverInfo( data class ChipReceiverInfo( val routeInfo: MediaRoute2Info, val routeInfo: MediaRoute2Info, val appIconDrawableOverride: Drawable?, val appIconDrawableOverride: Drawable?, Loading packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +28 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.media.taptotransfer.receiver package com.android.systemui.media.taptotransfer.receiver import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.content.Context import android.util.AttributeSet import android.util.AttributeSet import com.android.systemui.surfaceeffects.ripple.RippleShader import com.android.systemui.surfaceeffects.ripple.RippleShader Loading @@ -25,10 +27,36 @@ import com.android.systemui.surfaceeffects.ripple.RippleView * An expanding ripple effect for the media tap-to-transfer receiver chip. * An expanding ripple effect for the media tap-to-transfer receiver chip. */ */ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { // Indicates whether the ripple started expanding. private var isStarted: Boolean init { init { setupShader(RippleShader.RippleShape.ELLIPSE) setupShader(RippleShader.RippleShape.ELLIPSE) setRippleFill(true) setRippleFill(true) setSparkleStrength(0f) setSparkleStrength(0f) duration = 3000L duration = 3000L isStarted = false } fun expandRipple(onAnimationEnd: Runnable? = null) { isStarted = true super.startRipple(onAnimationEnd) } /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */ fun collapseRipple(onAnimationEnd: Runnable? = null) { if (!isStarted) { return // Ignore if ripple is not started yet. } // Reset all listeners to animator. animator.removeAllListeners() animator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { onAnimationEnd?.run() isStarted = false } }) animator.reverse() } } } } packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -41,7 +41,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a private set private set private val ripplePaint = Paint() private val ripplePaint = Paint() private val animator = ValueAnimator.ofFloat(0f, 1f) protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) var duration: Long = 1750 var duration: Long = 1750 Loading packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt 0 → 100644 +67 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.media.taptotransfer.receiver import android.content.Context import android.os.Handler import android.os.PowerManager import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.view.ViewUtil import com.android.systemui.util.wakelock.WakeLock class FakeMediaTttChipControllerReceiver( commandQueue: CommandQueue, context: Context, logger: MediaTttLogger, windowManager: WindowManager, mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, powerManager: PowerManager, mainHandler: Handler, mediaTttFlags: MediaTttFlags, uiEventLogger: MediaTttReceiverUiEventLogger, viewUtil: ViewUtil, wakeLockBuilder: WakeLock.Builder, ) : MediaTttChipControllerReceiver( commandQueue, context, logger, windowManager, mainExecutor, accessibilityManager, configurationController, powerManager, mainHandler, mediaTttFlags, uiEventLogger, viewUtil, wakeLockBuilder, ) { override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { // Just bypass the animation in tests onAnimationEnd.run() } } packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -114,7 +114,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) controllerReceiver = MediaTttChipControllerReceiver( controllerReceiver = FakeMediaTttChipControllerReceiver( commandQueue, commandQueue, context, context, logger, logger, Loading Loading
packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +33 −6 Original line number Original line Diff line number Diff line Loading @@ -56,7 +56,7 @@ import javax.inject.Inject * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. */ */ @SysUISingleton @SysUISingleton class MediaTttChipControllerReceiver @Inject constructor( open class MediaTttChipControllerReceiver @Inject constructor( private val commandQueue: CommandQueue, private val commandQueue: CommandQueue, context: Context, context: Context, @MediaTttReceiverLogger logger: MediaTttLogger, @MediaTttReceiverLogger logger: MediaTttLogger, Loading Loading @@ -183,15 +183,28 @@ class MediaTttChipControllerReceiver @Inject constructor( val appIconView = view.getAppIconView() val appIconView = view.getAppIconView() appIconView.animate() appIconView.animate() .translationYBy(-1 * getTranslationAmount().toFloat()) .translationYBy(-1 * getTranslationAmount().toFloat()) .setDuration(30.frames) .setDuration(ICON_TRANSLATION_ANIM_DURATION) .start() .start() appIconView.animate() appIconView.animate() .alpha(1f) .alpha(1f) .setDuration(5.frames) .setDuration(ICON_ALPHA_ANIM_DURATION) .start() .start() // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. appIconView.postOnAnimation { view.requestAccessibilityFocus() } appIconView.postOnAnimation { view.requestAccessibilityFocus() } startRipple(view.requireViewById(R.id.ripple)) expandRipple(view.requireViewById(R.id.ripple)) } override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { val appIconView = view.getAppIconView() appIconView.animate() .translationYBy(getTranslationAmount().toFloat()) .setDuration(ICON_TRANSLATION_ANIM_DURATION) .start() appIconView.animate() .alpha(0f) .setDuration(ICON_ALPHA_ANIM_DURATION) .start() (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd) } } override fun getTouchableRegion(view: View, outRect: Rect) { override fun getTouchableRegion(view: View, outRect: Rect) { Loading @@ -205,11 +218,22 @@ class MediaTttChipControllerReceiver @Inject constructor( return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) } } private fun startRipple(rippleView: ReceiverChipRippleView) { private fun expandRipple(rippleView: ReceiverChipRippleView) { if (rippleView.rippleInProgress()) { if (rippleView.rippleInProgress()) { // Skip if ripple is still playing // Skip if ripple is still playing return return } } // In case the device orientation changes, we need to reset the layout. rippleView.addOnLayoutChangeListener ( View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> if (v == null) return@OnLayoutChangeListener val layoutChangedRippleView = v as ReceiverChipRippleView layoutRipple(layoutChangedRippleView) layoutChangedRippleView.invalidate() } ) rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewDetachedFromWindow(view: View?) {} override fun onViewDetachedFromWindow(view: View?) {} Loading @@ -219,7 +243,7 @@ class MediaTttChipControllerReceiver @Inject constructor( } } val attachedRippleView = view as ReceiverChipRippleView val attachedRippleView = view as ReceiverChipRippleView layoutRipple(attachedRippleView) layoutRipple(attachedRippleView) attachedRippleView.startRipple() attachedRippleView.expandRipple() attachedRippleView.removeOnAttachStateChangeListener(this) attachedRippleView.removeOnAttachStateChangeListener(this) } } }) }) Loading @@ -242,6 +266,9 @@ class MediaTttChipControllerReceiver @Inject constructor( } } } } val ICON_TRANSLATION_ANIM_DURATION = 30.frames val ICON_ALPHA_ANIM_DURATION = 5.frames data class ChipReceiverInfo( data class ChipReceiverInfo( val routeInfo: MediaRoute2Info, val routeInfo: MediaRoute2Info, val appIconDrawableOverride: Drawable?, val appIconDrawableOverride: Drawable?, Loading
packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +28 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.media.taptotransfer.receiver package com.android.systemui.media.taptotransfer.receiver import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.content.Context import android.util.AttributeSet import android.util.AttributeSet import com.android.systemui.surfaceeffects.ripple.RippleShader import com.android.systemui.surfaceeffects.ripple.RippleShader Loading @@ -25,10 +27,36 @@ import com.android.systemui.surfaceeffects.ripple.RippleView * An expanding ripple effect for the media tap-to-transfer receiver chip. * An expanding ripple effect for the media tap-to-transfer receiver chip. */ */ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { // Indicates whether the ripple started expanding. private var isStarted: Boolean init { init { setupShader(RippleShader.RippleShape.ELLIPSE) setupShader(RippleShader.RippleShape.ELLIPSE) setRippleFill(true) setRippleFill(true) setSparkleStrength(0f) setSparkleStrength(0f) duration = 3000L duration = 3000L isStarted = false } fun expandRipple(onAnimationEnd: Runnable? = null) { isStarted = true super.startRipple(onAnimationEnd) } /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */ fun collapseRipple(onAnimationEnd: Runnable? = null) { if (!isStarted) { return // Ignore if ripple is not started yet. } // Reset all listeners to animator. animator.removeAllListeners() animator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { onAnimationEnd?.run() isStarted = false } }) animator.reverse() } } } }
packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -41,7 +41,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a private set private set private val ripplePaint = Paint() private val ripplePaint = Paint() private val animator = ValueAnimator.ofFloat(0f, 1f) protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) var duration: Long = 1750 var duration: Long = 1750 Loading
packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt 0 → 100644 +67 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.media.taptotransfer.receiver import android.content.Context import android.os.Handler import android.os.PowerManager import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.view.ViewUtil import com.android.systemui.util.wakelock.WakeLock class FakeMediaTttChipControllerReceiver( commandQueue: CommandQueue, context: Context, logger: MediaTttLogger, windowManager: WindowManager, mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, powerManager: PowerManager, mainHandler: Handler, mediaTttFlags: MediaTttFlags, uiEventLogger: MediaTttReceiverUiEventLogger, viewUtil: ViewUtil, wakeLockBuilder: WakeLock.Builder, ) : MediaTttChipControllerReceiver( commandQueue, context, logger, windowManager, mainExecutor, accessibilityManager, configurationController, powerManager, mainHandler, mediaTttFlags, uiEventLogger, viewUtil, wakeLockBuilder, ) { override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { // Just bypass the animation in tests onAnimationEnd.run() } }
packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -114,7 +114,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) controllerReceiver = MediaTttChipControllerReceiver( controllerReceiver = FakeMediaTttChipControllerReceiver( commandQueue, commandQueue, context, context, logger, logger, Loading