Merge changes Ic5ef8de2,I9b43b526,Idce510fc into tm-qpr-dev am: 8de3d142a5
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20130998 Change-Id: Ied6913a0beb948d15d7c0864afae78a54c1de260 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user