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

View File

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

View File

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

View File

@@ -20,17 +20,18 @@ import android.annotation.LayoutRes
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.PowerManager import android.os.PowerManager
import android.os.SystemClock import android.os.SystemClock
import android.view.LayoutInflater import android.view.LayoutInflater
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 android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import androidx.annotation.CallSuper
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
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.DelayableExecutor
@@ -70,7 +71,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
width = WindowManager.LayoutParams.WRAP_CONTENT width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY 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 title = windowTitle
format = PixelFormat.TRANSLUCENT format = PixelFormat.TRANSLUCENT
setTrustedOverlay() setTrustedOverlay()
@@ -84,11 +86,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*/ */
internal abstract val windowLayoutParams: WindowManager.LayoutParams internal abstract val windowLayoutParams: WindowManager.LayoutParams
/** The view currently being displayed. Null if the view is not being displayed. */ /** A container for all the display-related objects. Null if the view is not being displayed. */
private var view: ViewGroup? = null private var displayInfo: DisplayInfo? = null
/** The info currently being displayed. Null if the view is not being displayed. */
internal var info: T? = null
/** A [Runnable] that, when run, will cancel the pending timeout of the view. */ /** A [Runnable] that, when run, will cancel the pending timeout of the view. */
private var cancelViewTimeout: Runnable? = null private var cancelViewTimeout: Runnable? = null
@@ -100,10 +99,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* display the correct information in the view. * display the correct information in the view.
*/ */
fun displayView(newInfo: T) { fun displayView(newInfo: T) {
val currentView = view val currentDisplayInfo = displayInfo
if (currentView != null) { if (currentDisplayInfo != null) {
updateView(newInfo, currentView) currentDisplayInfo.info = newInfo
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else { } else {
// The view is new, so set up all our callbacks and inflate the view // The view is new, so set up all our callbacks and inflate the view
configurationController.addCallback(displayScaleListener) configurationController.addCallback(displayScaleListener)
@@ -140,19 +140,24 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
val newView = LayoutInflater val newView = LayoutInflater
.from(context) .from(context)
.inflate(viewLayoutRes, null) as ViewGroup .inflate(viewLayoutRes, null) as ViewGroup
view = newView val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
updateView(newInfo, newView) 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) windowManager.addView(newView, windowLayoutParams)
animateViewIn(newView) animateViewIn(newView)
} }
/** Removes then re-inflates the view. */ /** Removes then re-inflates the view. */
private fun reinflateView() { private fun reinflateView() {
val currentInfo = info val currentViewInfo = displayInfo ?: return
if (view == null || currentInfo == null) { return }
windowManager.removeView(view) windowManager.removeView(currentViewInfo.view)
inflateAndUpdateView(currentInfo) inflateAndUpdateView(currentViewInfo.info)
} }
private val displayScaleListener = object : ConfigurationController.ConfigurationListener { private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -168,20 +173,20 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* change, etc.) * change, etc.)
*/ */
fun removeView(removalReason: String) { fun removeView(removalReason: String) {
if (shouldIgnoreViewRemoval(removalReason)) { val currentDisplayInfo = displayInfo ?: return
if (shouldIgnoreViewRemoval(currentDisplayInfo.info, removalReason)) {
return return
} }
val currentView = view ?: return
val currentView = currentDisplayInfo.view
animateViewOut(currentView) { windowManager.removeView(currentView) } animateViewOut(currentView) { windowManager.removeView(currentView) }
logger.logChipRemoval(removalReason) logger.logChipRemoval(removalReason)
configurationController.removeCallback(displayScaleListener) 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 // that if a new view event comes in while this view is animating out, we still display the
// new view appropriately. // new view appropriately.
view = null displayInfo = 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()
} }
@@ -191,15 +196,18 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* *
* Allows subclasses to keep the view visible for longer in certain circumstances. * 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]. * A method implemented by subclasses to update [currentView] based on [newInfo].
*/ */
@CallSuper abstract fun updateView(newInfo: T, currentView: ViewGroup)
open fun updateView(newInfo: T, currentView: ViewGroup) {
info = newInfo /**
} * 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 * 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) { internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
onAnimationEnd.run() 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 { 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 package com.android.systemui.util.view
import android.graphics.Rect
import android.view.View import android.view.View
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject import javax.inject.Inject
@@ -23,4 +40,22 @@ class ViewUtil @Inject constructor() {
top <= y && top <= y &&
y <= top + view.height 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.any
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -71,6 +72,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock @Mock
private lateinit var powerManager: PowerManager private lateinit var powerManager: PowerManager
@Mock @Mock
private lateinit var viewUtil: ViewUtil
@Mock
private lateinit var windowManager: WindowManager private lateinit var windowManager: WindowManager
@Mock @Mock
private lateinit var commandQueue: CommandQueue private lateinit var commandQueue: CommandQueue
@@ -104,7 +107,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
configurationController, configurationController,
powerManager, powerManager,
Handler.getMain(), Handler.getMain(),
receiverUiEventLogger receiverUiEventLogger,
viewUtil,
) )
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 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.any
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import dagger.Lazy import dagger.Lazy
import org.junit.Before import org.junit.Before
@@ -90,6 +91,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var lazyFalsingCollector: Lazy<FalsingCollector> private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
@Mock @Mock
private lateinit var falsingCollector: FalsingCollector private lateinit var falsingCollector: FalsingCollector
@Mock
private lateinit var viewUtil: ViewUtil
private lateinit var commandQueueCallback: CommandQueue.Callbacks private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable private lateinit var fakeAppIconDrawable: Drawable
private lateinit var fakeClock: FakeSystemClock private lateinit var fakeClock: FakeSystemClock
@@ -130,7 +133,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
powerManager, powerManager,
senderUiEventLogger, senderUiEventLogger,
lazyFalsingManager, lazyFalsingManager,
lazyFalsingCollector lazyFalsingCollector,
viewUtil,
) )
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -837,6 +841,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
uiEventLogger: MediaTttSenderUiEventLogger, uiEventLogger: MediaTttSenderUiEventLogger,
falsingManager: Lazy<FalsingManager>, falsingManager: Lazy<FalsingManager>,
falsingCollector: Lazy<FalsingCollector>, falsingCollector: Lazy<FalsingCollector>,
viewUtil: ViewUtil,
) : MediaTttChipControllerSender( ) : MediaTttChipControllerSender(
commandQueue, commandQueue,
context, context,
@@ -849,6 +854,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
uiEventLogger, uiEventLogger,
falsingManager, falsingManager,
falsingCollector, falsingCollector,
viewUtil,
) { ) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests // Just bypass the animation in tests

View File

@@ -17,7 +17,9 @@
package com.android.systemui.temporarydisplay package com.android.systemui.temporarydisplay
import android.content.Context import android.content.Context
import android.graphics.Rect
import android.os.PowerManager import android.os.PowerManager
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
@@ -259,13 +261,16 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
override val windowLayoutParams = commonWindowLayoutParams override val windowLayoutParams = commonWindowLayoutParams
override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) { override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo mostRecentViewInfo = newInfo
} }
override fun shouldIgnoreViewRemoval(removalReason: String): Boolean { override fun shouldIgnoreViewRemoval(info: ViewInfo, removalReason: String): Boolean {
return shouldIgnoreViewRemoval return shouldIgnoreViewRemoval
} }
override fun getTouchableRegion(view: View, outRect: Rect) {
outRect.setEmpty()
}
} }
inner class ViewInfo(val name: String) : TemporaryViewInfo { 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 package com.android.systemui.util.view
import android.graphics.Rect
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy import org.mockito.Mockito.spy
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
@@ -25,6 +44,12 @@ class ViewUtilTest : SysuiTestCase() {
location[0] = VIEW_LEFT location[0] = VIEW_LEFT
location[1] = VIEW_TOP location[1] = VIEW_TOP
`when`(view.locationOnScreen).thenReturn(location) `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 @Test
@@ -64,6 +89,18 @@ class ViewUtilTest : SysuiTestCase() {
fun touchIsWithinView_yTooLarge_returnsFalse() { fun touchIsWithinView_yTooLarge_returnsFalse() {
assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse() 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 private const val VIEW_LEFT = 30