Merge changes Ic5ef8de2,I9b43b526,Idce510fc into tm-qpr-dev

* changes:
  [Media TTT] Receiver ripple tweaks: Use an ellipse and use no sparkle strength (so that it's a smooth ripple not a turbulent one).
  [Chipbar] Cleanup: Group the display-related variables into one object.
  [Media TTT] Set specific touchable regions for the media chips so that other touches will get correctly passed through to the window below.
This commit is contained in:
Caitlin Shkuratov
2022-10-11 14:09:29 +00:00
committed by Android (Google) Code Review
11 changed files with 303 additions and 46 deletions

View File

@@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer.receiver
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
@@ -44,6 +45,7 @@ import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
/**
@@ -63,6 +65,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
powerManager: PowerManager,
@Main private val mainHandler: Handler,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
context,
logger,
@@ -83,7 +86,6 @@ class MediaTttChipControllerReceiver @Inject constructor(
height = WindowManager.LayoutParams.MATCH_PARENT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
fitInsetsTypes = 0 // Ignore insets from all system bars
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
@@ -140,8 +142,6 @@ class MediaTttChipControllerReceiver @Inject constructor(
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
super.updateView(newInfo, currentView)
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
@@ -154,14 +154,14 @@ class MediaTttChipControllerReceiver @Inject constructor(
context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
}
val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
iconView.setImageDrawable(iconDrawable)
iconView.contentDescription = iconContentDescription
}
override fun animateViewIn(view: ViewGroup) {
val appIconView = view.requireViewById<View>(R.id.app_icon)
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat())
.setDuration(30.frames)
@@ -175,6 +175,12 @@ class MediaTttChipControllerReceiver @Inject constructor(
startRipple(view.requireViewById(R.id.ripple))
}
override fun getTouchableRegion(view: View, outRect: Rect) {
// Even though the app icon view isn't touchable, users might think it is. So, use it as the
// touchable region to ensure that touches don't get passed to the window below.
viewUtil.setRectToViewWindowLocation(view.getAppIconView(), outRect)
}
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Int {
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
@@ -202,16 +208,19 @@ class MediaTttChipControllerReceiver @Inject constructor(
private fun layoutRipple(rippleView: ReceiverChipRippleView) {
val windowBounds = windowManager.currentWindowMetrics.bounds
val height = windowBounds.height()
val width = windowBounds.width()
val height = windowBounds.height().toFloat()
val width = windowBounds.width().toFloat()
val maxDiameter = height / 2.5f
rippleView.setMaxSize(maxDiameter, maxDiameter)
rippleView.setMaxSize(width / 2f, height / 2f)
// Center the ripple on the bottom of the screen in the middle.
rippleView.setCenter(width * 0.5f, height.toFloat())
rippleView.setCenter(width * 0.5f, height)
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
rippleView.setColor(color, 70)
}
private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon)
}
}
data class ChipReceiverInfo(

View File

@@ -18,6 +18,7 @@ package com.android.systemui.media.taptotransfer.receiver
import android.content.Context
import android.util.AttributeSet
import com.android.systemui.ripple.RippleShader
import com.android.systemui.ripple.RippleView
/**
@@ -25,9 +26,9 @@ import com.android.systemui.ripple.RippleView
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
// TODO: use RippleShape#ELLIPSE when calling setupShader.
setupShader()
setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true)
setSparkleStrength(0f)
duration = 3000L
}
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
import android.content.Context
import android.graphics.Rect
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
@@ -46,6 +47,7 @@ import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import dagger.Lazy
import javax.inject.Inject
@@ -68,6 +70,7 @@ open class MediaTttChipControllerSender @Inject constructor(
// And overcome performance issue, check [b/247817628] for details.
private val falsingManager: Lazy<FalsingManager>,
private val falsingCollector: Lazy<FalsingCollector>,
private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
logger,
@@ -129,8 +132,6 @@ open class MediaTttChipControllerSender @Inject constructor(
newInfo: ChipSenderInfo,
currentView: ViewGroup
) {
super.updateView(newInfo, currentView)
val chipState = newInfo.state
// Detect falsing touches on the chip.
@@ -207,10 +208,10 @@ open class MediaTttChipControllerSender @Inject constructor(
// animateChipOut matches the animateChipIn.
}
override fun shouldIgnoreViewRemoval(removalReason: String): Boolean {
override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean {
// 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.)
val transferStatus = info?.state?.transferStatus
val transferStatus = info.state.transferStatus
if (
(transferStatus == TransferStatus.IN_PROGRESS ||
transferStatus == TransferStatus.SUCCEEDED) &&
@@ -224,6 +225,10 @@ open class MediaTttChipControllerSender @Inject constructor(
return false
}
override fun getTouchableRegion(view: View, outRect: Rect) {
viewUtil.setRectToViewWindowLocation(view, outRect)
}
private fun Boolean.visibleIfTrue(): Int {
return if (this) {
View.VISIBLE

View File

@@ -20,17 +20,18 @@ import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import androidx.annotation.CallSuper
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -70,7 +71,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
@@ -84,11 +86,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*/
internal abstract val windowLayoutParams: WindowManager.LayoutParams
/** The view currently being displayed. Null if the view is not being displayed. */
private var view: ViewGroup? = null
/** The info currently being displayed. Null if the view is not being displayed. */
internal var info: T? = null
/** A container for all the display-related objects. Null if the view is not being displayed. */
private var displayInfo: DisplayInfo? = null
/** A [Runnable] that, when run, will cancel the pending timeout of the view. */
private var cancelViewTimeout: Runnable? = null
@@ -100,10 +99,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* display the correct information in the view.
*/
fun displayView(newInfo: T) {
val currentView = view
val currentDisplayInfo = displayInfo
if (currentView != null) {
updateView(newInfo, currentView)
if (currentDisplayInfo != null) {
currentDisplayInfo.info = newInfo
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
// The view is new, so set up all our callbacks and inflate the view
configurationController.addCallback(displayScaleListener)
@@ -140,19 +140,24 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
val newView = LayoutInflater
.from(context)
.inflate(viewLayoutRes, null) as ViewGroup
view = newView
updateView(newInfo, newView)
val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
newViewController.init()
// We don't need to hold on to the view controller since we never set anything additional
// on it -- it will be automatically cleaned up when the view is detached.
val newDisplayInfo = DisplayInfo(newView, newInfo)
displayInfo = newDisplayInfo
updateView(newDisplayInfo.info, newDisplayInfo.view)
windowManager.addView(newView, windowLayoutParams)
animateViewIn(newView)
}
/** Removes then re-inflates the view. */
private fun reinflateView() {
val currentInfo = info
if (view == null || currentInfo == null) { return }
val currentViewInfo = displayInfo ?: return
windowManager.removeView(view)
inflateAndUpdateView(currentInfo)
windowManager.removeView(currentViewInfo.view)
inflateAndUpdateView(currentViewInfo.info)
}
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -168,20 +173,20 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* change, etc.)
*/
fun removeView(removalReason: String) {
if (shouldIgnoreViewRemoval(removalReason)) {
val currentDisplayInfo = displayInfo ?: return
if (shouldIgnoreViewRemoval(currentDisplayInfo.info, removalReason)) {
return
}
val currentView = view ?: return
val currentView = currentDisplayInfo.view
animateViewOut(currentView) { windowManager.removeView(currentView) }
logger.logChipRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
// Re-set the view to null immediately (instead as part of the animation end runnable) so
// Re-set 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
info = null
displayInfo = null
// No need to time the view out since it's already gone
cancelViewTimeout?.run()
}
@@ -191,15 +196,18 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*
* Allows subclasses to keep the view visible for longer in certain circumstances.
*/
open fun shouldIgnoreViewRemoval(removalReason: String): Boolean = false
open fun shouldIgnoreViewRemoval(info: T, removalReason: String): Boolean = false
/**
* A method implemented by subclasses to update [currentView] based on [newInfo].
*/
@CallSuper
open fun updateView(newInfo: T, currentView: ViewGroup) {
info = newInfo
}
abstract fun updateView(newInfo: T, currentView: ViewGroup)
/**
* Fills [outRect] with the touchable region of this view. This will be used by WindowManager
* to decide which touch events go to the view.
*/
abstract fun getTouchableRegion(view: View, outRect: Rect)
/**
* A method that can be implemented by subclasses to do custom animations for when the view
@@ -216,6 +224,15 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
onAnimationEnd.run()
}
/** A container for all the display-related state objects. */
private inner class DisplayInfo(
/** The view currently being displayed. */
val view: ViewGroup,
/** The info currently being displayed. */
var info: T,
)
}
object TemporaryDisplayRemovalReason {

View File

@@ -0,0 +1,57 @@
/*
* 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.temporarydisplay
import android.graphics.Rect
import android.view.View
import android.view.ViewTreeObserver
import com.android.systemui.util.ViewController
/**
* A view controller that will notify the [ViewTreeObserver] about the touchable region for this
* view. This will be used by WindowManager to decide which touch events go to the view and which
* pass through to the window below.
*
* @param touchableRegionSetter a function that, given the view and an out rect, fills the rect with
* the touchable region of this view.
*/
class TouchableRegionViewController(
view: View,
touchableRegionSetter: (View, Rect) -> Unit,
) : ViewController<View>(view) {
private val tempRect = Rect()
private val internalInsetsListener =
ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo ->
inoutInfo.setTouchableInsets(
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
)
tempRect.setEmpty()
touchableRegionSetter.invoke(mView, tempRect)
inoutInfo.touchableRegion.set(tempRect)
}
public override fun onViewAttached() {
mView.viewTreeObserver.addOnComputeInternalInsetsListener(internalInsetsListener)
}
public override fun onViewDetached() {
mView.viewTreeObserver.removeOnComputeInternalInsetsListener(internalInsetsListener)
}
}

View File

@@ -1,5 +1,22 @@
/*
* 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.util.view
import android.graphics.Rect
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -23,4 +40,22 @@ class ViewUtil @Inject constructor() {
top <= y &&
y <= top + view.height
}
/**
* Sets [outRect] to be the view's location within its window.
*/
fun setRectToViewWindowLocation(view: View, outRect: Rect) {
val locInWindow = IntArray(2)
view.getLocationInWindow(locInWindow)
val x = locInWindow[0]
val y = locInWindow[1]
outRect.set(
x,
y,
x + view.width,
y + view.height,
)
}
}

View File

@@ -41,6 +41,7 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -71,6 +72,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var viewUtil: ViewUtil
@Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
@@ -104,7 +107,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
configurationController,
powerManager,
Handler.getMain(),
receiverUiEventLogger
receiverUiEventLogger,
viewUtil,
)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)

View File

@@ -47,6 +47,7 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import org.junit.Before
@@ -90,6 +91,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
@Mock
private lateinit var falsingCollector: FalsingCollector
@Mock
private lateinit var viewUtil: ViewUtil
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var fakeClock: FakeSystemClock
@@ -130,7 +133,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
powerManager,
senderUiEventLogger,
lazyFalsingManager,
lazyFalsingCollector
lazyFalsingCollector,
viewUtil,
)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -837,6 +841,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
uiEventLogger: MediaTttSenderUiEventLogger,
falsingManager: Lazy<FalsingManager>,
falsingCollector: Lazy<FalsingCollector>,
viewUtil: ViewUtil,
) : MediaTttChipControllerSender(
commandQueue,
context,
@@ -849,6 +854,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
uiEventLogger,
falsingManager,
falsingCollector,
viewUtil,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests

View File

@@ -17,7 +17,9 @@
package com.android.systemui.temporarydisplay
import android.content.Context
import android.graphics.Rect
import android.os.PowerManager
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
@@ -259,13 +261,16 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
override val windowLayoutParams = commonWindowLayoutParams
override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo
}
override fun shouldIgnoreViewRemoval(removalReason: String): Boolean {
override fun shouldIgnoreViewRemoval(info: ViewInfo, removalReason: String): Boolean {
return shouldIgnoreViewRemoval
}
override fun getTouchableRegion(view: View, outRect: Rect) {
outRect.setEmpty()
}
}
inner class ViewInfo(val name: String) : TemporaryViewInfo {

View File

@@ -0,0 +1,81 @@
/*
* 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.temporarydisplay
import android.graphics.Rect
import android.view.View
import android.view.ViewTreeObserver
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
class TouchableRegionViewControllerTest : SysuiTestCase() {
@Mock private lateinit var view: View
@Mock private lateinit var viewTreeObserver: ViewTreeObserver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
}
@Test
fun viewAttached_listenerAdded() {
val controller = TouchableRegionViewController(view) { _, _ -> }
controller.onViewAttached()
verify(viewTreeObserver).addOnComputeInternalInsetsListener(any())
}
@Test
fun viewDetached_listenerRemoved() {
val controller = TouchableRegionViewController(view) { _, _ -> }
controller.onViewDetached()
verify(viewTreeObserver).removeOnComputeInternalInsetsListener(any())
}
@Test
fun listener_usesPassedInFunction() {
val controller =
TouchableRegionViewController(view) { _, outRect -> outRect.set(1, 2, 3, 4) }
controller.onViewAttached()
val captor =
ArgumentCaptor.forClass(ViewTreeObserver.OnComputeInternalInsetsListener::class.java)
verify(viewTreeObserver).addOnComputeInternalInsetsListener(captor.capture())
val listener = captor.value!!
val inoutInfo = ViewTreeObserver.InternalInsetsInfo()
listener.onComputeInternalInsets(inoutInfo)
assertThat(inoutInfo.touchableRegion.bounds).isEqualTo(Rect(1, 2, 3, 4))
}
}

View File

@@ -1,12 +1,31 @@
/*
* 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.util.view
import android.graphics.Rect
import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
@@ -25,6 +44,12 @@ class ViewUtilTest : SysuiTestCase() {
location[0] = VIEW_LEFT
location[1] = VIEW_TOP
`when`(view.locationOnScreen).thenReturn(location)
doAnswer { invocation ->
val pos = invocation.arguments[0] as IntArray
pos[0] = VIEW_LEFT
pos[1] = VIEW_TOP
null
}.`when`(view).getLocationInWindow(any())
}
@Test
@@ -64,6 +89,18 @@ class ViewUtilTest : SysuiTestCase() {
fun touchIsWithinView_yTooLarge_returnsFalse() {
assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse()
}
@Test
fun setRectToViewWindowLocation_rectHasLocation() {
val outRect = Rect()
viewUtil.setRectToViewWindowLocation(view, outRect)
assertThat(outRect.left).isEqualTo(VIEW_LEFT)
assertThat(outRect.right).isEqualTo(VIEW_RIGHT)
assertThat(outRect.top).isEqualTo(VIEW_TOP)
assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM)
}
}
private const val VIEW_LEFT = 30