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:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user