Merge changes I61bfb975,I2cb907c5,I13c60b3b into tm-qpr-dev am: f16394779e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20079324

Change-Id: Id1739a629f6e93f6fde64fc0385ce61c1b51e967
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2022-10-07 14:06:15 +00:00
committed by Automerger Merge Worker
12 changed files with 249 additions and 130 deletions

View File

@@ -361,13 +361,17 @@ class ViewHierarchyAnimator {
* *
* The end state of the animation is controlled by [destination]. This value can be any of * The end state of the animation is controlled by [destination]. This value can be any of
* the four corners, any of the four edges, or the center of the view. * the four corners, any of the four edges, or the center of the view.
*
* @param onAnimationEnd an optional runnable that will be run once the animation finishes
* successfully. Will not be run if the animation is cancelled.
*/ */
@JvmOverloads @JvmOverloads
fun animateRemoval( fun animateRemoval(
rootView: View, rootView: View,
destination: Hotspot = Hotspot.CENTER, destination: Hotspot = Hotspot.CENTER,
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
duration: Long = DEFAULT_DURATION duration: Long = DEFAULT_DURATION,
onAnimationEnd: Runnable? = null,
): Boolean { ): Boolean {
if ( if (
!occupiesSpace( !occupiesSpace(
@@ -391,13 +395,28 @@ class ViewHierarchyAnimator {
addListener(child, listener, recursive = false) addListener(child, listener, recursive = false)
} }
// Remove the view so that a layout update is triggered for the siblings and they val viewHasSiblings = parent.childCount > 1
// animate to their next position while the view's removal is also animating. if (viewHasSiblings) {
parent.removeView(rootView) // Remove the view so that a layout update is triggered for the siblings and they
// By adding the view to the overlay, we can animate it while it isn't part of the view // animate to their next position while the view's removal is also animating.
// hierarchy. It is correctly positioned because we have its previous bounds, and we set parent.removeView(rootView)
// them manually during the animation. // By adding the view to the overlay, we can animate it while it isn't part of the
parent.overlay.add(rootView) // view hierarchy. It is correctly positioned because we have its previous bounds,
// and we set them manually during the animation.
parent.overlay.add(rootView)
}
// If this view has no siblings, the parent view may shrink to (0,0) size and mess
// up the animation if we immediately remove the view. So instead, we just leave the
// view in the real hierarchy until the animation finishes.
val endRunnable = Runnable {
if (viewHasSiblings) {
parent.overlay.remove(rootView)
} else {
parent.removeView(rootView)
}
onAnimationEnd?.run()
}
val startValues = val startValues =
mapOf( mapOf(
@@ -430,7 +449,8 @@ class ViewHierarchyAnimator {
endValues, endValues,
interpolator, interpolator,
duration, duration,
ephemeral = true ephemeral = true,
endRunnable,
) )
if (rootView is ViewGroup) { if (rootView is ViewGroup) {
@@ -463,7 +483,6 @@ class ViewHierarchyAnimator {
.alpha(0f) .alpha(0f)
.setInterpolator(Interpolators.ALPHA_OUT) .setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2) .setDuration(duration / 2)
.withEndAction { parent.overlay.remove(rootView) }
.start() .start()
} }
} }
@@ -477,7 +496,6 @@ class ViewHierarchyAnimator {
.setInterpolator(Interpolators.ALPHA_OUT) .setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2) .setDuration(duration / 2)
.setStartDelay(duration / 2) .setStartDelay(duration / 2)
.withEndAction { parent.overlay.remove(rootView) }
.start() .start()
} }

View File

@@ -29,6 +29,7 @@
<com.android.internal.widget.CachingIconView <com.android.internal.widget.CachingIconView
android:id="@+id/app_icon" android:id="@+id/app_icon"
android:background="@drawable/media_ttt_chip_background_receiver"
android:layout_width="@dimen/media_ttt_icon_size_receiver" android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver" android:layout_height="@dimen/media_ttt_icon_size_receiver"
android:layout_gravity="center|bottom" android:layout_gravity="center|bottom"

View File

@@ -1056,9 +1056,8 @@
<!-- Media tap-to-transfer chip for receiver device --> <!-- Media tap-to-transfer chip for receiver device -->
<dimen name="media_ttt_chip_size_receiver">100dp</dimen> <dimen name="media_ttt_chip_size_receiver">100dp</dimen>
<dimen name="media_ttt_icon_size_receiver">95dp</dimen> <dimen name="media_ttt_icon_size_receiver">95dp</dimen>
<!-- Since the generic icon isn't circular, we need to scale it down so it still fits within <!-- Add some padding for the generic icon so it doesn't go all the way to the border. -->
the circular chip. --> <dimen name="media_ttt_generic_icon_padding">12dp</dimen>
<dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
<dimen name="media_ttt_receiver_vert_translation">20dp</dimen> <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification --> <!-- Window magnification -->

View File

@@ -19,7 +19,6 @@ package com.android.systemui.media.taptotransfer.common
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.R import com.android.systemui.R
@@ -76,29 +75,6 @@ class MediaTttUtils {
isAppIcon = false isAppIcon = false
) )
} }
/**
* Sets an icon to be displayed by the given view.
*
* @param iconSize the size in pixels that the icon should be. If null, the size of
* [appIconView] will not be adjusted.
*/
fun setIcon(
appIconView: CachingIconView,
icon: Drawable,
iconContentDescription: CharSequence,
iconSize: Int? = null,
) {
iconSize?.let { size ->
val lp = appIconView.layoutParams
lp.width = size
lp.height = size
appIconView.layoutParams = lp
}
appIconView.contentDescription = iconContentDescription
appIconView.setImageDrawable(icon)
}
} }
} }

View File

@@ -30,6 +30,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
@@ -146,20 +147,17 @@ class MediaTttChipControllerReceiver @Inject constructor(
) )
val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
val iconSize = context.resources.getDimensionPixelSize( val iconPadding =
if (iconInfo.isAppIcon) { if (iconInfo.isAppIcon) {
R.dimen.media_ttt_icon_size_receiver 0
} else { } else {
R.dimen.media_ttt_generic_icon_size_receiver context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
} }
)
MediaTttUtils.setIcon( val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
currentView.requireViewById(R.id.app_icon), iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
iconDrawable, iconView.setImageDrawable(iconDrawable)
iconContentDescription, iconView.contentDescription = iconContentDescription
iconSize,
)
} }
override fun animateViewIn(view: ViewGroup) { override fun animateViewIn(view: ViewGroup) {

View File

@@ -29,6 +29,7 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import android.widget.TextView import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken import com.android.systemui.Gefingerpoken
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
@@ -53,7 +54,7 @@ import javax.inject.Inject
* chip is shown when a user is transferring media to/from this device and a receiver device. * chip is shown when a user is transferring media to/from this device and a receiver device.
*/ */
@SysUISingleton @SysUISingleton
class MediaTttChipControllerSender @Inject constructor( open class MediaTttChipControllerSender @Inject constructor(
commandQueue: CommandQueue, commandQueue: CommandQueue,
context: Context, context: Context,
@MediaTttSenderLogger logger: MediaTttLogger, @MediaTttSenderLogger logger: MediaTttLogger,
@@ -145,11 +146,9 @@ class MediaTttChipControllerSender @Inject constructor(
val iconInfo = MediaTttUtils.getIconInfoFromPackageName( val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger context, newInfo.routeInfo.clientPackageName, logger
) )
MediaTttUtils.setIcon( val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
currentView.requireViewById(R.id.app_icon), iconView.setImageDrawable(iconInfo.drawable)
iconInfo.drawable, iconView.contentDescription = iconInfo.contentDescription
iconInfo.contentDescription
)
// Text // Text
val otherDeviceName = newInfo.routeInfo.name.toString() val otherDeviceName = newInfo.routeInfo.name.toString()
@@ -196,7 +195,19 @@ class MediaTttChipControllerSender @Inject constructor(
) )
} }
override fun removeView(removalReason: String) { override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
ViewHierarchyAnimator.animateRemoval(
view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
ANIMATION_DURATION,
onAnimationEnd,
)
// TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the
// animateChipOut matches the animateChipIn.
}
override fun shouldIgnoreViewRemoval(removalReason: String): Boolean {
// Don't remove the chip if we're in progress or succeeded, since the user should still be // Don't remove the chip if we're in progress or succeeded, since the user should still be
// able to see the status of the transfer. (But do remove it if it's finally timed out.) // able to see the status of the transfer. (But do remove it if it's finally timed out.)
val transferStatus = info?.state?.transferStatus val transferStatus = info?.state?.transferStatus
@@ -208,9 +219,9 @@ class MediaTttChipControllerSender @Inject constructor(
logger.logRemovalBypass( logger.logRemovalBypass(
removalReason, bypassReason = "transferStatus=${transferStatus.name}" removalReason, bypassReason = "transferStatus=${transferStatus.name}"
) )
return return true
} }
super.removeView(removalReason) return false
} }
private fun Boolean.visibleIfTrue(): Int { private fun Boolean.visibleIfTrue(): Int {

View File

@@ -167,17 +167,32 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* @param removalReason a short string describing why the view was removed (timeout, state * @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.) * change, etc.)
*/ */
open fun removeView(removalReason: String) { fun removeView(removalReason: String) {
if (view == null) { return } if (shouldIgnoreViewRemoval(removalReason)) {
return
}
val currentView = view ?: return
animateViewOut(currentView) { windowManager.removeView(currentView) }
logger.logChipRemoval(removalReason) logger.logChipRemoval(removalReason)
configurationController.removeCallback(displayScaleListener) configurationController.removeCallback(displayScaleListener)
windowManager.removeView(view) // Re-set the view to null immediately (instead as part of the animation end runnable) so
// that if a new view event comes in while this view is animating out, we still display the
// new view appropriately.
view = null view = null
info = null info = null
// No need to time the view out since it's already gone // No need to time the view out since it's already gone
cancelViewTimeout?.run() cancelViewTimeout?.run()
} }
/**
* Returns true if a view removal request should be ignored and false otherwise.
*
* Allows subclasses to keep the view visible for longer in certain circumstances.
*/
open fun shouldIgnoreViewRemoval(removalReason: String): Boolean = false
/** /**
* A method implemented by subclasses to update [currentView] based on [newInfo]. * A method implemented by subclasses to update [currentView] based on [newInfo].
*/ */
@@ -190,7 +205,17 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* A method that can be implemented by subclasses to do custom animations for when the view * A method that can be implemented by subclasses to do custom animations for when the view
* appears. * appears.
*/ */
open fun animateViewIn(view: ViewGroup) {} internal open fun animateViewIn(view: ViewGroup) {}
/**
* A method that can be implemented by subclasses to do custom animations for when the view
* disappears.
*
* @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
*/
internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
onAnimationEnd.run()
}
} }
object TemporaryDisplayRemovalReason { object TemporaryDisplayRemovalReason {

View File

@@ -718,7 +718,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
} }
@Test @Test
fun animatesViewRemovalFromStartToEnd() { fun animatesViewRemovalFromStartToEnd_viewHasSiblings() {
setUpRootWithChildren() setUpRootWithChildren()
val child = rootView.getChildAt(0) val child = rootView.getChildAt(0)
@@ -741,6 +741,35 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
assertFalse(child in rootView.children) assertFalse(child in rootView.children)
} }
@Test
fun animatesViewRemovalFromStartToEnd_viewHasNoSiblings() {
rootView = LinearLayout(mContext)
(rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
(rootView as LinearLayout).weightSum = 1f
val onlyChild = View(mContext)
rootView.addView(onlyChild)
forceLayout()
val success = ViewHierarchyAnimator.animateRemoval(
onlyChild,
destination = ViewHierarchyAnimator.Hotspot.LEFT,
interpolator = Interpolators.LINEAR
)
assertTrue(success)
assertNotNull(onlyChild.getTag(R.id.tag_animator))
checkBounds(onlyChild, l = 0, t = 0, r = 200, b = 100)
advanceAnimation(onlyChild, 0.5f)
checkBounds(onlyChild, l = 0, t = 0, r = 100, b = 100)
advanceAnimation(onlyChild, 1.0f)
checkBounds(onlyChild, l = 0, t = 0, r = 0, b = 100)
endAnimation(rootView)
endAnimation(onlyChild)
assertEquals(0, rootView.childCount)
assertFalse(onlyChild in rootView.children)
}
@Test @Test
fun animatesViewRemovalRespectingDestination() { fun animatesViewRemovalRespectingDestination() {
// CENTER // CENTER
@@ -963,6 +992,60 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
assertNull(remainingChild.getTag(R.id.tag_animator)) assertNull(remainingChild.getTag(R.id.tag_animator))
} }
@Test
fun animateRemoval_runnableRunsWhenAnimationEnds() {
var runnableRun = false
val onAnimationEndRunnable = { runnableRun = true }
setUpRootWithChildren()
forceLayout()
val removedView = rootView.getChildAt(0)
ViewHierarchyAnimator.animateRemoval(
removedView,
onAnimationEnd = onAnimationEndRunnable
)
endAnimation(removedView)
assertEquals(true, runnableRun)
}
@Test
fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() {
var runnableRun = false
val onAnimationEndRunnable = { runnableRun = true }
setUpRootWithChildren()
forceLayout()
val removedView = rootView.getChildAt(0)
ViewHierarchyAnimator.animateRemoval(
removedView,
onAnimationEnd = onAnimationEndRunnable
)
cancelAnimation(removedView)
assertEquals(false, runnableRun)
}
@Test
fun animationRemoval_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
var runnableRun = false
val onAnimationEndRunnable = { runnableRun = true }
setUpRootWithChildren()
forceLayout()
val removedView = rootView.getChildAt(0)
ViewHierarchyAnimator.animateRemoval(
removedView,
onAnimationEnd = onAnimationEndRunnable
)
advanceAnimation(removedView, 0.5f)
assertEquals(false, runnableRun)
}
@Test @Test
fun cleansUpListenersCorrectly() { fun cleansUpListenersCorrectly() {
val firstChild = View(mContext) val firstChild = View(mContext)

View File

@@ -19,9 +19,7 @@ package com.android.systemui.media.taptotransfer.common
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.widget.FrameLayout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.internal.widget.CachingIconView
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
@@ -90,48 +88,6 @@ class MediaTttUtilsTest : SysuiTestCase() {
assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName) assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME) assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
} }
@Test
fun setIcon_viewHasIconAndContentDescription() {
val view = CachingIconView(context)
val icon = context.getDrawable(R.drawable.ic_celebration)!!
val contentDescription = "Happy birthday!"
MediaTttUtils.setIcon(view, icon, contentDescription)
assertThat(view.drawable).isEqualTo(icon)
assertThat(view.contentDescription).isEqualTo(contentDescription)
}
@Test
fun setIcon_iconSizeNull_viewSizeDoesNotChange() {
val view = CachingIconView(context)
val size = 456
view.layoutParams = FrameLayout.LayoutParams(size, size)
MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc")
assertThat(view.layoutParams.width).isEqualTo(size)
assertThat(view.layoutParams.height).isEqualTo(size)
}
@Test
fun setIcon_iconSizeProvided_viewSizeUpdates() {
val view = CachingIconView(context)
val size = 456
view.layoutParams = FrameLayout.LayoutParams(size, size)
val newSize = 40
MediaTttUtils.setIcon(
view,
context.getDrawable(R.drawable.ic_cake)!!,
"desc",
iconSize = newSize
)
assertThat(view.layoutParams.width).isEqualTo(newSize)
assertThat(view.layoutParams.height).isEqualTo(newSize)
}
} }
private const val PACKAGE_NAME = "com.android.systemui" private const val PACKAGE_NAME = "com.android.systemui"

View File

@@ -212,35 +212,27 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
} }
@Test @Test
fun updateView_isAppIcon_usesAppIconSize() { fun updateView_isAppIcon_usesAppIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME))
val chipView = getChipView() val chipView = getChipView()
assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(0)
chipView.measure( assertThat(chipView.getAppIconView().paddingRight).isEqualTo(0)
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), assertThat(chipView.getAppIconView().paddingTop).isEqualTo(0)
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(0)
)
val expectedSize =
context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
} }
@Test @Test
fun updateView_notAppIcon_usesGenericIconSize() { fun updateView_notAppIcon_usesGenericIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) controllerReceiver.displayView(getChipReceiverInfo(packageName = null))
val chipView = getChipView() val chipView = getChipView()
val expectedPadding =
chipView.measure( context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(expectedPadding)
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) assertThat(chipView.getAppIconView().paddingRight).isEqualTo(expectedPadding)
) assertThat(chipView.getAppIconView().paddingTop).isEqualTo(expectedPadding)
assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(expectedPadding)
val expectedSize =
context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver)
assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
} }
@Test @Test

View File

@@ -17,6 +17,7 @@
package com.android.systemui.media.taptotransfer.sender package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager import android.app.StatusBarManager
import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@@ -37,9 +38,11 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
@@ -61,7 +64,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
class MediaTttChipControllerSenderTest : SysuiTestCase() { class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var controllerSender: MediaTttChipControllerSender private lateinit var controllerSender: TestMediaTttChipControllerSender
@Mock @Mock
private lateinit var packageManager: PackageManager private lateinit var packageManager: PackageManager
@@ -116,7 +119,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
whenever(lazyFalsingManager.get()).thenReturn(falsingManager) whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector) whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
controllerSender = MediaTttChipControllerSender( controllerSender = TestMediaTttChipControllerSender(
commandQueue, commandQueue,
context, context,
logger, logger,
@@ -821,6 +824,37 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
/** Helper method providing default parameters to not clutter up the tests. */ /** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceFailed() = private fun transferToThisDeviceFailed() =
ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo) ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
private class TestMediaTttChipControllerSender(
commandQueue: CommandQueue,
context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
uiEventLogger: MediaTttSenderUiEventLogger,
falsingManager: Lazy<FalsingManager>,
falsingCollector: Lazy<FalsingCollector>,
) : MediaTttChipControllerSender(
commandQueue,
context,
logger,
windowManager,
mainExecutor,
accessibilityManager,
configurationController,
powerManager,
uiEventLogger,
falsingManager,
falsingCollector,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
onAnimationEnd.run()
}
}
} }
private const val APP_NAME = "Fake app name" private const val APP_NAME = "Fake app name"

View File

@@ -61,6 +61,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var powerManager: PowerManager private lateinit var powerManager: PowerManager
private var shouldIgnoreViewRemoval: Boolean = false
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
@@ -205,6 +207,26 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
verify(windowManager, never()).removeView(any()) verify(windowManager, never()).removeView(any())
} }
@Test
fun removeView_shouldIgnoreRemovalFalse_viewRemoved() {
shouldIgnoreViewRemoval = false
underTest.displayView(getState())
underTest.removeView("reason")
verify(windowManager).removeView(any())
}
@Test
fun removeView_shouldIgnoreRemovalTrue_viewNotRemoved() {
shouldIgnoreViewRemoval = true
underTest.displayView(getState())
underTest.removeView("reason")
verify(windowManager, never()).removeView(any())
}
private fun getState(name: String = "name") = ViewInfo(name) private fun getState(name: String = "name") = ViewInfo(name)
private fun getConfigurationListener(): ConfigurationListener { private fun getConfigurationListener(): ConfigurationListener {
@@ -240,6 +262,10 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
super.updateView(newInfo, currentView) super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo mostRecentViewInfo = newInfo
} }
override fun shouldIgnoreViewRemoval(removalReason: String): Boolean {
return shouldIgnoreViewRemoval
}
} }
inner class ViewInfo(val name: String) : TemporaryViewInfo { inner class ViewInfo(val name: String) : TemporaryViewInfo {