Merge "[Chipbar] Enable chipbar to handle chipbars with different priorities." into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
1970e1b92f
@@ -30,9 +30,11 @@ import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
|
||||
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
|
||||
import com.android.systemui.media.taptotransfer.MediaTttFlags;
|
||||
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
|
||||
import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo;
|
||||
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
|
||||
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
|
||||
import com.android.systemui.plugins.log.LogBuffer;
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -95,19 +97,19 @@ public interface MediaModule {
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@MediaTttSenderLogger
|
||||
static MediaTttLogger providesMediaTttSenderLogger(
|
||||
static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
|
||||
@MediaTttSenderLogBuffer LogBuffer buffer
|
||||
) {
|
||||
return new MediaTttLogger("Sender", buffer);
|
||||
return new MediaTttLogger<>("Sender", buffer);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@MediaTttReceiverLogger
|
||||
static MediaTttLogger providesMediaTttReceiverLogger(
|
||||
static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
|
||||
@MediaTttReceiverLogBuffer LogBuffer buffer
|
||||
) {
|
||||
return new MediaTttLogger("Receiver", buffer);
|
||||
return new MediaTttLogger<>("Receiver", buffer);
|
||||
}
|
||||
|
||||
/** */
|
||||
|
||||
@@ -18,17 +18,21 @@ package com.android.systemui.media.taptotransfer.common
|
||||
|
||||
import com.android.systemui.plugins.log.LogBuffer
|
||||
import com.android.systemui.plugins.log.LogLevel
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewLogger
|
||||
|
||||
/**
|
||||
* A logger for media tap-to-transfer events.
|
||||
*
|
||||
* @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
|
||||
*
|
||||
* TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly
|
||||
* different experiences.
|
||||
*/
|
||||
class MediaTttLogger(
|
||||
class MediaTttLogger<T : TemporaryViewInfo>(
|
||||
deviceTypeTag: String,
|
||||
buffer: LogBuffer
|
||||
) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) {
|
||||
) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
|
||||
/** Logs a change in the chip state for the given [mediaRouteId]. */
|
||||
fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
|
||||
buffer.log(
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.R
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.common.shared.model.TintedIcon
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||
|
||||
/** Utility methods for media tap-to-transfer. */
|
||||
class MediaTttUtils {
|
||||
@@ -47,7 +48,7 @@ class MediaTttUtils {
|
||||
fun getIconInfoFromPackageName(
|
||||
context: Context,
|
||||
appPackageName: String?,
|
||||
logger: MediaTttLogger
|
||||
logger: MediaTttLogger<out TemporaryViewInfo>
|
||||
): IconInfo {
|
||||
if (appPackageName != null) {
|
||||
val packageManager = context.packageManager
|
||||
|
||||
@@ -45,8 +45,10 @@ import com.android.systemui.statusbar.CommandQueue
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||
import com.android.systemui.temporarydisplay.ViewPriority
|
||||
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.android.systemui.util.view.ViewUtil
|
||||
import com.android.systemui.util.wakelock.WakeLock
|
||||
import javax.inject.Inject
|
||||
@@ -62,7 +64,7 @@ import javax.inject.Inject
|
||||
open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
private val commandQueue: CommandQueue,
|
||||
context: Context,
|
||||
@MediaTttReceiverLogger logger: MediaTttLogger,
|
||||
@MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
|
||||
windowManager: WindowManager,
|
||||
mainExecutor: DelayableExecutor,
|
||||
accessibilityManager: AccessibilityManager,
|
||||
@@ -73,7 +75,8 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
private val uiEventLogger: MediaTttReceiverUiEventLogger,
|
||||
private val viewUtil: ViewUtil,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
|
||||
systemClock: SystemClock,
|
||||
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
|
||||
context,
|
||||
logger,
|
||||
windowManager,
|
||||
@@ -83,6 +86,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
powerManager,
|
||||
R.layout.media_ttt_chip_receiver,
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
||||
override val windowLayoutParams = commonWindowLayoutParams.apply {
|
||||
@@ -290,4 +294,5 @@ data class ChipReceiverInfo(
|
||||
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
|
||||
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
|
||||
override val id: String,
|
||||
override val priority: ViewPriority = ViewPriority.NORMAL,
|
||||
) : TemporaryViewInfo()
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.systemui.media.taptotransfer.MediaTttFlags
|
||||
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
|
||||
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
|
||||
import com.android.systemui.statusbar.CommandQueue
|
||||
import com.android.systemui.temporarydisplay.ViewPriority
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
||||
@@ -46,7 +47,7 @@ constructor(
|
||||
private val chipbarCoordinator: ChipbarCoordinator,
|
||||
private val commandQueue: CommandQueue,
|
||||
private val context: Context,
|
||||
@MediaTttSenderLogger private val logger: MediaTttLogger,
|
||||
@MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
|
||||
private val mediaTttFlags: MediaTttFlags,
|
||||
private val uiEventLogger: MediaTttSenderUiEventLogger,
|
||||
) : CoreStartable {
|
||||
@@ -146,7 +147,7 @@ constructor(
|
||||
routeInfo: MediaRoute2Info,
|
||||
undoCallback: IUndoMediaTransferCallback?,
|
||||
context: Context,
|
||||
logger: MediaTttLogger,
|
||||
logger: MediaTttLogger<ChipbarInfo>,
|
||||
): ChipbarInfo {
|
||||
val packageName = routeInfo.clientPackageName
|
||||
val otherDeviceName = routeInfo.name.toString()
|
||||
@@ -180,6 +181,7 @@ constructor(
|
||||
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
|
||||
timeoutMs = chipStateSender.timeout,
|
||||
id = routeInfo.id,
|
||||
priority = ViewPriority.NORMAL,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.android.systemui.util.wakelock.WakeLock
|
||||
|
||||
/**
|
||||
@@ -44,8 +45,24 @@ import com.android.systemui.util.wakelock.WakeLock
|
||||
*
|
||||
* The generic type T is expected to contain all the information necessary for the subclasses to
|
||||
* display the view in a certain state, since they receive <T> in [updateView].
|
||||
*
|
||||
* Some information about display ordering:
|
||||
*
|
||||
* [ViewPriority] defines different priorities for the incoming views. The incoming view will be
|
||||
* displayed so long as its priority is equal to or greater than the currently displayed view.
|
||||
* (Concretely, this means that a [ViewPriority.NORMAL] won't be displayed if a
|
||||
* [ViewPriority.CRITICAL] is currently displayed. But otherwise, the incoming view will get
|
||||
* displayed and kick out the old view).
|
||||
*
|
||||
* Once the currently displayed view times out, we *may* display a previously requested view if it
|
||||
* still has enough time left before its own timeout. The same priority ordering applies.
|
||||
*
|
||||
* Note: [TemporaryViewInfo.id] is the identifier that we use to determine if a call to
|
||||
* [displayView] will just update the current view with new information, or display a completely new
|
||||
* view. This means that you *cannot* change the [TemporaryViewInfo.priority] or
|
||||
* [TemporaryViewInfo.windowTitle] while using the same ID.
|
||||
*/
|
||||
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
|
||||
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger<T>>(
|
||||
internal val context: Context,
|
||||
internal val logger: U,
|
||||
internal val windowManager: WindowManager,
|
||||
@@ -55,6 +72,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
private val powerManager: PowerManager,
|
||||
@LayoutRes private val viewLayoutRes: Int,
|
||||
private val wakeLockBuilder: WakeLock.Builder,
|
||||
private val systemClock: SystemClock,
|
||||
) : CoreStartable {
|
||||
/**
|
||||
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
|
||||
@@ -78,27 +96,18 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
*/
|
||||
internal abstract val windowLayoutParams: WindowManager.LayoutParams
|
||||
|
||||
/** 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
|
||||
|
||||
/**
|
||||
* A wakelock that is acquired when view is displayed and screen off,
|
||||
* then released when view is removed.
|
||||
* A list of the currently active views, ordered from highest priority in the beginning to
|
||||
* lowest priority at the end.
|
||||
*
|
||||
* Whenever the current view disappears, the next-priority view will be displayed if it's still
|
||||
* valid.
|
||||
*/
|
||||
private var wakeLock: WakeLock? = null
|
||||
internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
|
||||
|
||||
/** A string that keeps track of wakelock reason once it is acquired till it gets released */
|
||||
private var wakeReasonAcquired: String? = null
|
||||
|
||||
/**
|
||||
* A stack of pairs of device id and temporary view info. This is used when there may be
|
||||
* multiple devices in range, and we want to always display the chip for the most recently
|
||||
* active device.
|
||||
*/
|
||||
internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
|
||||
private fun getCurrentDisplayInfo(): DisplayInfo? {
|
||||
return activeViews.getOrNull(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the view with the provided [newInfo].
|
||||
@@ -107,94 +116,139 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
* display the correct information in the view.
|
||||
* @param onViewTimeout a runnable that runs after the view timeout.
|
||||
*/
|
||||
@Synchronized
|
||||
fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
|
||||
val currentDisplayInfo = displayInfo
|
||||
|
||||
// Update our list of active devices by removing it if necessary, then adding back at the
|
||||
// front of the list
|
||||
val id = newInfo.id
|
||||
val position = findAndRemoveFromActiveViewsList(id)
|
||||
activeViews.addFirst(Pair(id, newInfo))
|
||||
|
||||
if (currentDisplayInfo != null &&
|
||||
currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
|
||||
// We're already displaying information in the correctly-titled window, so we just need
|
||||
// to update the view.
|
||||
currentDisplayInfo.info = newInfo
|
||||
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
|
||||
} else {
|
||||
if (currentDisplayInfo != null) {
|
||||
// We're already displaying information but that information is under a different
|
||||
// window title. So, we need to remove the old window with the old title and add a
|
||||
// new window with the new title.
|
||||
removeView(
|
||||
id,
|
||||
removalReason = "New info has new window title: ${newInfo.windowTitle}"
|
||||
)
|
||||
}
|
||||
|
||||
// At this point, we're guaranteed to no longer be displaying a view.
|
||||
// So, set up all our callbacks and inflate the view.
|
||||
configurationController.addCallback(displayScaleListener)
|
||||
|
||||
wakeLock = if (!powerManager.isScreenOn) {
|
||||
// If the screen is off, fully wake it so the user can see the view.
|
||||
wakeLockBuilder
|
||||
.setTag(newInfo.windowTitle)
|
||||
.setLevelsAndFlags(
|
||||
PowerManager.FULL_WAKE_LOCK or
|
||||
PowerManager.ACQUIRE_CAUSES_WAKEUP
|
||||
)
|
||||
.build()
|
||||
} else {
|
||||
// Per b/239426653, we want the view to show over the dream state.
|
||||
// If the screen is on, using screen bright level will leave screen on the dream
|
||||
// state but ensure the screen will not go off before wake lock is released.
|
||||
wakeLockBuilder
|
||||
.setTag(newInfo.windowTitle)
|
||||
.setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
|
||||
.build()
|
||||
}
|
||||
wakeLock?.acquire(newInfo.wakeReason)
|
||||
wakeReasonAcquired = newInfo.wakeReason
|
||||
logger.logViewAddition(id, newInfo.windowTitle)
|
||||
inflateAndUpdateView(newInfo)
|
||||
}
|
||||
|
||||
// Cancel and re-set the view timeout each time we get a new state.
|
||||
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
|
||||
newInfo.timeoutMs,
|
||||
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
|
||||
// include it just to be safe.
|
||||
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
|
||||
)
|
||||
)
|
||||
val timeExpirationMillis = systemClock.currentTimeMillis() + timeout
|
||||
|
||||
// Only cancel timeout of the most recent view displayed, as it will be reset.
|
||||
if (position == 0) {
|
||||
cancelViewTimeout?.run()
|
||||
val currentDisplayInfo = getCurrentDisplayInfo()
|
||||
|
||||
// We're current displaying a chipbar with the same ID, we just need to update its info
|
||||
if (currentDisplayInfo != null && currentDisplayInfo.info.id == newInfo.id) {
|
||||
val view = checkNotNull(currentDisplayInfo.view) {
|
||||
"First item in activeViews list must have a valid view"
|
||||
}
|
||||
logger.logViewUpdate(newInfo)
|
||||
currentDisplayInfo.info = newInfo
|
||||
currentDisplayInfo.timeExpirationMillis = timeExpirationMillis
|
||||
updateTimeout(currentDisplayInfo, timeout, onViewTimeout)
|
||||
updateView(newInfo, view)
|
||||
return
|
||||
}
|
||||
cancelViewTimeout = mainExecutor.executeDelayed(
|
||||
|
||||
val newDisplayInfo = DisplayInfo(
|
||||
info = newInfo,
|
||||
onViewTimeout = onViewTimeout,
|
||||
timeExpirationMillis = timeExpirationMillis,
|
||||
// Null values will be updated to non-null if/when this view actually gets displayed
|
||||
view = null,
|
||||
wakeLock = null,
|
||||
cancelViewTimeout = null,
|
||||
)
|
||||
|
||||
// We're not displaying anything, so just render this new info
|
||||
if (currentDisplayInfo == null) {
|
||||
addCallbacks()
|
||||
activeViews.add(newDisplayInfo)
|
||||
showNewView(newDisplayInfo, timeout)
|
||||
return
|
||||
}
|
||||
|
||||
// The currently displayed info takes higher priority than the new one.
|
||||
// So, just store the new one in case the current one disappears.
|
||||
if (currentDisplayInfo.info.priority > newInfo.priority) {
|
||||
logger.logViewAdditionDelayed(newInfo)
|
||||
// Remove any old information for this id (if it exists) and re-add it to the list in
|
||||
// the right priority spot
|
||||
removeFromActivesIfNeeded(newInfo.id)
|
||||
var insertIndex = 0
|
||||
while (insertIndex < activeViews.size &&
|
||||
activeViews[insertIndex].info.priority > newInfo.priority) {
|
||||
insertIndex++
|
||||
}
|
||||
activeViews.add(insertIndex, newDisplayInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// Else: The newInfo should be displayed and the currentInfo should be hidden
|
||||
hideView(currentDisplayInfo)
|
||||
// Remove any old information for this id (if it exists) and put this info at the beginning
|
||||
removeFromActivesIfNeeded(newDisplayInfo.info.id)
|
||||
activeViews.add(0, newDisplayInfo)
|
||||
showNewView(newDisplayInfo, timeout)
|
||||
}
|
||||
|
||||
private fun showNewView(newDisplayInfo: DisplayInfo, timeout: Int) {
|
||||
logger.logViewAddition(newDisplayInfo.info)
|
||||
createAndAcquireWakeLock(newDisplayInfo)
|
||||
updateTimeout(newDisplayInfo, timeout, newDisplayInfo.onViewTimeout)
|
||||
inflateAndUpdateView(newDisplayInfo)
|
||||
}
|
||||
|
||||
private fun createAndAcquireWakeLock(displayInfo: DisplayInfo) {
|
||||
// TODO(b/262009503): Migrate off of isScrenOn, since it's deprecated.
|
||||
val newWakeLock = if (!powerManager.isScreenOn) {
|
||||
// If the screen is off, fully wake it so the user can see the view.
|
||||
wakeLockBuilder
|
||||
.setTag(displayInfo.info.windowTitle)
|
||||
.setLevelsAndFlags(
|
||||
PowerManager.FULL_WAKE_LOCK or
|
||||
PowerManager.ACQUIRE_CAUSES_WAKEUP
|
||||
)
|
||||
.build()
|
||||
} else {
|
||||
// Per b/239426653, we want the view to show over the dream state.
|
||||
// If the screen is on, using screen bright level will leave screen on the dream
|
||||
// state but ensure the screen will not go off before wake lock is released.
|
||||
wakeLockBuilder
|
||||
.setTag(displayInfo.info.windowTitle)
|
||||
.setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
|
||||
.build()
|
||||
}
|
||||
displayInfo.wakeLock = newWakeLock
|
||||
newWakeLock.acquire(displayInfo.info.wakeReason)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a runnable that will remove [displayInfo] in [timeout] ms from now.
|
||||
*
|
||||
* @param onViewTimeout an optional runnable that will be run if the view times out.
|
||||
* @return a runnable that, when run, will *cancel* the view's timeout.
|
||||
*/
|
||||
private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int, onViewTimeout: Runnable?) {
|
||||
val cancelViewTimeout = mainExecutor.executeDelayed(
|
||||
{
|
||||
removeView(id, REMOVAL_REASON_TIMEOUT)
|
||||
removeView(displayInfo.info.id, REMOVAL_REASON_TIMEOUT)
|
||||
onViewTimeout?.run()
|
||||
},
|
||||
timeout.toLong()
|
||||
)
|
||||
|
||||
displayInfo.onViewTimeout = onViewTimeout
|
||||
// Cancel old view timeout and re-set it.
|
||||
displayInfo.cancelViewTimeout?.run()
|
||||
displayInfo.cancelViewTimeout = cancelViewTimeout
|
||||
}
|
||||
|
||||
/** Inflates a new view, updates it with [newInfo], and adds the view to the window. */
|
||||
private fun inflateAndUpdateView(newInfo: T) {
|
||||
/** Inflates a new view, updates it with [DisplayInfo.info], and adds the view to the window. */
|
||||
private fun inflateAndUpdateView(displayInfo: DisplayInfo) {
|
||||
val newInfo = displayInfo.info
|
||||
val newView = LayoutInflater
|
||||
.from(context)
|
||||
.inflate(viewLayoutRes, null) as ViewGroup
|
||||
val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
|
||||
newViewController.init()
|
||||
displayInfo.view = newView
|
||||
|
||||
// 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)
|
||||
val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
|
||||
newViewController.init()
|
||||
|
||||
updateView(newInfo, newView)
|
||||
|
||||
val paramsWithTitle = WindowManager.LayoutParams().also {
|
||||
it.copyFrom(windowLayoutParams)
|
||||
@@ -206,11 +260,15 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
}
|
||||
|
||||
/** Removes then re-inflates the view. */
|
||||
@Synchronized
|
||||
private fun reinflateView() {
|
||||
val currentViewInfo = displayInfo ?: return
|
||||
val currentDisplayInfo = getCurrentDisplayInfo() ?: return
|
||||
|
||||
windowManager.removeView(currentViewInfo.view)
|
||||
inflateAndUpdateView(currentViewInfo.info)
|
||||
val view = checkNotNull(currentDisplayInfo.view) {
|
||||
"First item in activeViews list must have a valid view"
|
||||
}
|
||||
windowManager.removeView(view)
|
||||
inflateAndUpdateView(currentDisplayInfo)
|
||||
}
|
||||
|
||||
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
|
||||
@@ -219,68 +277,109 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
}
|
||||
}
|
||||
|
||||
private fun addCallbacks() {
|
||||
configurationController.addCallback(displayScaleListener)
|
||||
}
|
||||
|
||||
private fun removeCallbacks() {
|
||||
configurationController.removeCallback(displayScaleListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the view given its [id].
|
||||
* Completely removes the view for the given [id], both visually and from our internal store.
|
||||
*
|
||||
* @param id the id of the device responsible of displaying the temp view.
|
||||
* @param removalReason a short string describing why the view was removed (timeout, state
|
||||
* change, etc.)
|
||||
*/
|
||||
@Synchronized
|
||||
fun removeView(id: String, removalReason: String) {
|
||||
val currentDisplayInfo = displayInfo ?: return
|
||||
|
||||
val removalPosition = findAndRemoveFromActiveViewsList(id)
|
||||
if (removalPosition == null) {
|
||||
logger.logViewRemovalIgnored(id, "view not found in the list")
|
||||
return
|
||||
}
|
||||
if (removalPosition != 0) {
|
||||
logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
|
||||
return
|
||||
}
|
||||
logger.logViewRemoval(id, removalReason)
|
||||
|
||||
val newViewToDisplay = if (activeViews.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
activeViews[0].second
|
||||
val displayInfo = activeViews.firstOrNull { it.info.id == id }
|
||||
if (displayInfo == null) {
|
||||
logger.logViewRemovalIgnored(id, "View not found in list")
|
||||
return
|
||||
}
|
||||
|
||||
val currentView = currentDisplayInfo.view
|
||||
animateViewOut(currentView) {
|
||||
windowManager.removeView(currentView)
|
||||
wakeLock?.release(wakeReasonAcquired)
|
||||
}
|
||||
val currentlyDisplayedView = activeViews[0]
|
||||
// Remove 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.
|
||||
activeViews.remove(displayInfo)
|
||||
|
||||
configurationController.removeCallback(displayScaleListener)
|
||||
// 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.
|
||||
displayInfo = null
|
||||
// No need to time the view out since it's already gone
|
||||
cancelViewTimeout?.run()
|
||||
displayInfo.cancelViewTimeout?.run()
|
||||
|
||||
if (displayInfo.view == null) {
|
||||
logger.logViewRemovalIgnored(id, "No view to remove")
|
||||
return
|
||||
}
|
||||
|
||||
if (currentlyDisplayedView.info.id != id) {
|
||||
logger.logViewRemovalIgnored(id, "View isn't the currently displayed view")
|
||||
return
|
||||
}
|
||||
|
||||
removeViewFromWindow(displayInfo)
|
||||
|
||||
// Prune anything that's already timed out before determining if we should re-display a
|
||||
// different chipbar.
|
||||
removeTimedOutViews()
|
||||
val newViewToDisplay = getCurrentDisplayInfo()
|
||||
|
||||
if (newViewToDisplay != null) {
|
||||
mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
|
||||
val timeout = newViewToDisplay.timeExpirationMillis - systemClock.currentTimeMillis()
|
||||
// TODO(b/258019006): We may want to have a delay before showing the new view so
|
||||
// that the UI translation looks a bit smoother. But, we expect this to happen
|
||||
// rarely so it may not be worth the extra complexity.
|
||||
showNewView(newViewToDisplay, timeout.toInt())
|
||||
} else {
|
||||
removeCallbacks()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and removes the active view with the given [id] from the stack, or null if there is no
|
||||
* active view with that ID
|
||||
*
|
||||
* @param id that temporary view belonged to.
|
||||
*
|
||||
* @return index of the view in the stack , otherwise null.
|
||||
* Hides the view from the window, but keeps [displayInfo] around in [activeViews] in case it
|
||||
* should be re-displayed later.
|
||||
*/
|
||||
private fun findAndRemoveFromActiveViewsList(id: String): Int? {
|
||||
for (i in 0 until activeViews.size) {
|
||||
if (activeViews[i].first == id) {
|
||||
activeViews.removeAt(i)
|
||||
return i
|
||||
}
|
||||
private fun hideView(displayInfo: DisplayInfo) {
|
||||
logger.logViewHidden(displayInfo.info)
|
||||
removeViewFromWindow(displayInfo)
|
||||
}
|
||||
|
||||
private fun removeViewFromWindow(displayInfo: DisplayInfo) {
|
||||
val view = displayInfo.view
|
||||
if (view == null) {
|
||||
logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
|
||||
return
|
||||
}
|
||||
displayInfo.view = null // Need other places??
|
||||
animateViewOut(view) {
|
||||
windowManager.removeView(view)
|
||||
displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun removeTimedOutViews() {
|
||||
val invalidViews = activeViews
|
||||
.filter { it.timeExpirationMillis <
|
||||
systemClock.currentTimeMillis() + MIN_REQUIRED_TIME_FOR_REDISPLAY }
|
||||
|
||||
invalidViews.forEach {
|
||||
activeViews.remove(it)
|
||||
logger.logViewExpiration(it.info)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun removeFromActivesIfNeeded(id: String) {
|
||||
val toRemove = activeViews.find { it.info.id == id }
|
||||
toRemove?.let {
|
||||
it.cancelViewTimeout?.run()
|
||||
activeViews.remove(it)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,17 +410,47 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
}
|
||||
|
||||
/** A container for all the display-related state objects. */
|
||||
private inner class DisplayInfo(
|
||||
/** The view currently being displayed. */
|
||||
val view: ViewGroup,
|
||||
inner class DisplayInfo(
|
||||
/**
|
||||
* The view currently being displayed.
|
||||
*
|
||||
* Null if this info isn't currently being displayed.
|
||||
*/
|
||||
var view: ViewGroup?,
|
||||
|
||||
/** The info currently being displayed. */
|
||||
/** The info that should be displayed if/when this is the highest priority view. */
|
||||
var info: T,
|
||||
|
||||
/**
|
||||
* The system time at which this display info should expire and never be displayed again.
|
||||
*/
|
||||
var timeExpirationMillis: Long,
|
||||
|
||||
/**
|
||||
* The wake lock currently held by this view. Must be released when the view disappears.
|
||||
*
|
||||
* Null if this info isn't currently being displayed.
|
||||
*/
|
||||
var wakeLock: WakeLock?,
|
||||
|
||||
/**
|
||||
* See [displayView].
|
||||
*/
|
||||
var onViewTimeout: Runnable?,
|
||||
|
||||
/**
|
||||
* A runnable that, when run, will cancel this view's timeout.
|
||||
*
|
||||
* Null if this info isn't currently being displayed.
|
||||
*/
|
||||
var cancelViewTimeout: Runnable?,
|
||||
)
|
||||
|
||||
// TODO(b/258019006): Add a dump method that dumps the currently active views.
|
||||
}
|
||||
|
||||
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
|
||||
const val DISPLAY_VIEW_DELAY = 50L
|
||||
private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000
|
||||
|
||||
private data class IconInfo(
|
||||
val iconName: String,
|
||||
|
||||
@@ -42,6 +42,20 @@ abstract class TemporaryViewInfo {
|
||||
* The id of the temporary view.
|
||||
*/
|
||||
abstract val id: String
|
||||
|
||||
/** The priority for this view. */
|
||||
abstract val priority: ViewPriority
|
||||
}
|
||||
|
||||
const val DEFAULT_TIMEOUT_MILLIS = 10000
|
||||
|
||||
/**
|
||||
* The priority of the view being displayed.
|
||||
*
|
||||
* Must be ordered from lowest priority to highest priority. (CRITICAL is currently the highest
|
||||
* priority.)
|
||||
*/
|
||||
enum class ViewPriority {
|
||||
NORMAL,
|
||||
CRITICAL,
|
||||
}
|
||||
|
||||
@@ -20,20 +20,79 @@ import com.android.systemui.plugins.log.LogBuffer
|
||||
import com.android.systemui.plugins.log.LogLevel
|
||||
|
||||
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
|
||||
open class TemporaryViewLogger(
|
||||
open class TemporaryViewLogger<T : TemporaryViewInfo>(
|
||||
internal val buffer: LogBuffer,
|
||||
internal val tag: String,
|
||||
) {
|
||||
/** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
|
||||
fun logViewAddition(id: String, windowTitle: String) {
|
||||
fun logViewExpiration(info: T) {
|
||||
buffer.log(
|
||||
tag,
|
||||
LogLevel.DEBUG,
|
||||
{
|
||||
str1 = windowTitle
|
||||
str2 = id
|
||||
str1 = info.id
|
||||
str2 = info.windowTitle
|
||||
str3 = info.priority.name
|
||||
},
|
||||
{ "View added. window=$str1 id=$str2" }
|
||||
{ "View timeout has already expired; removing. id=$str1 window=$str2 priority=$str3" }
|
||||
)
|
||||
}
|
||||
|
||||
fun logViewUpdate(info: T) {
|
||||
buffer.log(
|
||||
tag,
|
||||
LogLevel.DEBUG,
|
||||
{
|
||||
str1 = info.id
|
||||
str2 = info.windowTitle
|
||||
str3 = info.priority.name
|
||||
},
|
||||
{ "Existing view updated with new data. id=$str1 window=$str2 priority=$str3" }
|
||||
)
|
||||
}
|
||||
|
||||
fun logViewAdditionDelayed(info: T) {
|
||||
buffer.log(
|
||||
tag,
|
||||
LogLevel.DEBUG,
|
||||
{
|
||||
str1 = info.id
|
||||
str2 = info.windowTitle
|
||||
str3 = info.priority.name
|
||||
},
|
||||
{
|
||||
"New view can't be displayed because higher priority view is currently " +
|
||||
"displayed. New view id=$str1 window=$str2 priority=$str3"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Logs that we added the view with the given information. */
|
||||
fun logViewAddition(info: T) {
|
||||
buffer.log(
|
||||
tag,
|
||||
LogLevel.DEBUG,
|
||||
{
|
||||
str1 = info.id
|
||||
str2 = info.windowTitle
|
||||
str3 = info.priority.name
|
||||
},
|
||||
{ "View added. id=$str1 window=$str2 priority=$str3" }
|
||||
)
|
||||
}
|
||||
|
||||
fun logViewHidden(info: T) {
|
||||
buffer.log(
|
||||
tag,
|
||||
LogLevel.DEBUG,
|
||||
{
|
||||
str1 = info.id
|
||||
str2 = info.windowTitle
|
||||
str3 = info.priority.name
|
||||
},
|
||||
{
|
||||
"View hidden in favor of newer view. " +
|
||||
"Hidden view id=$str1 window=$str2 priority=$str3"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.VibratorHelper
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.android.systemui.util.view.ViewUtil
|
||||
import com.android.systemui.util.wakelock.WakeLock
|
||||
import javax.inject.Inject
|
||||
@@ -77,6 +78,7 @@ open class ChipbarCoordinator @Inject constructor(
|
||||
private val viewUtil: ViewUtil,
|
||||
private val vibratorHelper: VibratorHelper,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
systemClock: SystemClock,
|
||||
) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
|
||||
context,
|
||||
logger,
|
||||
@@ -87,6 +89,7 @@ open class ChipbarCoordinator @Inject constructor(
|
||||
powerManager,
|
||||
R.layout.chipbar,
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
|
||||
private lateinit var parent: ChipbarRootView
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.annotation.AttrRes
|
||||
import com.android.systemui.common.shared.model.Text
|
||||
import com.android.systemui.common.shared.model.TintedIcon
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||
import com.android.systemui.temporarydisplay.ViewPriority
|
||||
|
||||
/**
|
||||
* A container for all the state needed to display a chipbar via [ChipbarCoordinator].
|
||||
@@ -42,6 +43,7 @@ data class ChipbarInfo(
|
||||
override val wakeReason: String,
|
||||
override val timeoutMs: Int,
|
||||
override val id: String,
|
||||
override val priority: ViewPriority,
|
||||
) : TemporaryViewInfo() {
|
||||
companion object {
|
||||
@AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
|
||||
|
||||
@@ -29,7 +29,7 @@ class ChipbarLogger
|
||||
@Inject
|
||||
constructor(
|
||||
@ChipbarLog buffer: LogBuffer,
|
||||
) : TemporaryViewLogger(buffer, "ChipbarLog") {
|
||||
) : TemporaryViewLogger<ChipbarInfo>(buffer, "ChipbarLog") {
|
||||
/**
|
||||
* Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
|
||||
* [endItemDesc].
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.log.LogBufferFactory
|
||||
import com.android.systemui.plugins.log.LogBuffer
|
||||
import com.android.systemui.plugins.log.LogcatEchoTracker
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
@@ -33,7 +34,7 @@ import org.mockito.Mockito.mock
|
||||
class MediaTttLoggerTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var buffer: LogBuffer
|
||||
private lateinit var logger: MediaTttLogger
|
||||
private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
|
||||
|
||||
@Before
|
||||
fun setUp () {
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
@@ -40,7 +41,7 @@ class MediaTttUtilsTest : SysuiTestCase() {
|
||||
private lateinit var appIconFromPackageName: Drawable
|
||||
@Mock private lateinit var packageManager: PackageManager
|
||||
@Mock private lateinit var applicationInfo: ApplicationInfo
|
||||
@Mock private lateinit var logger: MediaTttLogger
|
||||
@Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
|
||||
@@ -27,13 +27,14 @@ import com.android.systemui.media.taptotransfer.common.MediaTttLogger
|
||||
import com.android.systemui.statusbar.CommandQueue
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.android.systemui.util.view.ViewUtil
|
||||
import com.android.systemui.util.wakelock.WakeLock
|
||||
|
||||
class FakeMediaTttChipControllerReceiver(
|
||||
commandQueue: CommandQueue,
|
||||
context: Context,
|
||||
logger: MediaTttLogger,
|
||||
logger: MediaTttLogger<ChipReceiverInfo>,
|
||||
windowManager: WindowManager,
|
||||
mainExecutor: DelayableExecutor,
|
||||
accessibilityManager: AccessibilityManager,
|
||||
@@ -44,6 +45,7 @@ class FakeMediaTttChipControllerReceiver(
|
||||
uiEventLogger: MediaTttReceiverUiEventLogger,
|
||||
viewUtil: ViewUtil,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
systemClock: SystemClock,
|
||||
) :
|
||||
MediaTttChipControllerReceiver(
|
||||
commandQueue,
|
||||
@@ -59,6 +61,7 @@ class FakeMediaTttChipControllerReceiver(
|
||||
uiEventLogger,
|
||||
viewUtil,
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||
// Just bypass the animation in tests
|
||||
|
||||
@@ -67,7 +67,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var applicationInfo: ApplicationInfo
|
||||
@Mock
|
||||
private lateinit var logger: MediaTttLogger
|
||||
private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
|
||||
@Mock
|
||||
private lateinit var accessibilityManager: AccessibilityManager
|
||||
@Mock
|
||||
@@ -128,6 +128,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
||||
receiverUiEventLogger,
|
||||
viewUtil,
|
||||
fakeWakeLockBuilder,
|
||||
fakeClock,
|
||||
)
|
||||
controllerReceiver.start()
|
||||
|
||||
@@ -155,6 +156,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
||||
receiverUiEventLogger,
|
||||
viewUtil,
|
||||
fakeWakeLockBuilder,
|
||||
fakeClock,
|
||||
)
|
||||
controllerReceiver.start()
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
|
||||
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
@@ -83,7 +84,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
|
||||
@Mock private lateinit var falsingManager: FalsingManager
|
||||
@Mock private lateinit var falsingCollector: FalsingCollector
|
||||
@Mock private lateinit var chipbarLogger: ChipbarLogger
|
||||
@Mock private lateinit var logger: MediaTttLogger
|
||||
@Mock private lateinit var logger: MediaTttLogger<ChipbarInfo>
|
||||
@Mock private lateinit var mediaTttFlags: MediaTttFlags
|
||||
@Mock private lateinit var packageManager: PackageManager
|
||||
@Mock private lateinit var powerManager: PowerManager
|
||||
@@ -142,6 +143,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
|
||||
viewUtil,
|
||||
vibratorHelper,
|
||||
fakeWakeLockBuilder,
|
||||
fakeClock,
|
||||
)
|
||||
chipbarCoordinator.start()
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.android.systemui.util.wakelock.WakeLock
|
||||
import com.android.systemui.util.wakelock.WakeLockFake
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -59,7 +60,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
private lateinit var fakeWakeLock: WakeLockFake
|
||||
|
||||
@Mock
|
||||
private lateinit var logger: TemporaryViewLogger
|
||||
private lateinit var logger: TemporaryViewLogger<ViewInfo>
|
||||
@Mock
|
||||
private lateinit var accessibilityManager: AccessibilityManager
|
||||
@Mock
|
||||
@@ -74,7 +75,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
|
||||
.thenReturn(TIMEOUT_MS.toInt())
|
||||
.thenAnswer { it.arguments[0] }
|
||||
|
||||
fakeClock = FakeSystemClock()
|
||||
fakeExecutor = FakeExecutor(fakeClock)
|
||||
@@ -84,14 +85,15 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
|
||||
|
||||
underTest = TestController(
|
||||
context,
|
||||
logger,
|
||||
windowManager,
|
||||
fakeExecutor,
|
||||
accessibilityManager,
|
||||
configurationController,
|
||||
powerManager,
|
||||
fakeWakeLockBuilder,
|
||||
context,
|
||||
logger,
|
||||
windowManager,
|
||||
fakeExecutor,
|
||||
accessibilityManager,
|
||||
configurationController,
|
||||
powerManager,
|
||||
fakeWakeLockBuilder,
|
||||
fakeClock,
|
||||
)
|
||||
underTest.start()
|
||||
}
|
||||
@@ -112,14 +114,14 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun displayView_logged() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name",
|
||||
windowTitle = "Fake Window Title",
|
||||
)
|
||||
val info = ViewInfo(
|
||||
name = "name",
|
||||
windowTitle = "Fake Window Title",
|
||||
)
|
||||
|
||||
verify(logger).logViewAddition("id", "Fake Window Title")
|
||||
underTest.displayView(info)
|
||||
|
||||
verify(logger).logViewAddition(info)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -168,10 +170,11 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
|
||||
fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name",
|
||||
id = "First",
|
||||
windowTitle = "First Fake Window Title",
|
||||
)
|
||||
)
|
||||
@@ -179,6 +182,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name",
|
||||
id = "Second",
|
||||
windowTitle = "Second Fake Window Title",
|
||||
)
|
||||
)
|
||||
@@ -262,20 +266,70 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun viewUpdatedWithNewOnViewTimeoutRunnable_newRunnableUsed() {
|
||||
var runnable1Run = false
|
||||
underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
|
||||
runnable1Run = true
|
||||
}
|
||||
|
||||
var runnable2Run = false
|
||||
underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
|
||||
runnable2Run = true
|
||||
}
|
||||
|
||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||
|
||||
assertThat(runnable1Run).isFalse()
|
||||
assertThat(runnable2Run).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name",
|
||||
windowTitle = "First Fake Window Title",
|
||||
id = "id1"
|
||||
)
|
||||
)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name",
|
||||
windowTitle = "Second Fake Window Title",
|
||||
id = "id2"
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
|
||||
verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
|
||||
assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
|
||||
assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
|
||||
verify(windowManager).removeView(viewCaptor.allValues[0])
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
|
||||
underTest.displayView(ViewInfo("First name", id = "id1"))
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
verify(windowManager).addView(any(), any())
|
||||
reset(windowManager)
|
||||
|
||||
underTest.removeView("id2", "test reason")
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
|
||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
|
||||
|
||||
@@ -284,6 +338,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -291,19 +346,28 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
underTest.displayView(ViewInfo("First name", id = "id1"))
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
verify(windowManager).addView(any(), any())
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN an old view is removed
|
||||
underTest.removeView("id1", "test reason")
|
||||
|
||||
// THEN we don't update anything
|
||||
verify(windowManager, never()).removeView(any())
|
||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
|
||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
|
||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -312,33 +376,31 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||
underTest.displayView(ViewInfo("Third name", id = "id3"))
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
verify(windowManager, times(3)).addView(any(), any())
|
||||
verify(windowManager, times(2)).removeView(any())
|
||||
|
||||
reset(windowManager)
|
||||
underTest.removeView("id3", "test reason")
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
|
||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
||||
|
||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
|
||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
|
||||
reset(windowManager)
|
||||
underTest.removeView("id2", "test reason")
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
|
||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
||||
|
||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
|
||||
reset(windowManager)
|
||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -347,18 +409,21 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
underTest.displayView(ViewInfo("New name", id = "id1"))
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
verify(windowManager).addView(any(), any())
|
||||
reset(windowManager)
|
||||
|
||||
underTest.removeView("id2", "test reason")
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
|
||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
|
||||
assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
|
||||
assertThat(underTest.activeViews[0].info.name).isEqualTo("New name")
|
||||
|
||||
reset(windowManager)
|
||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||
@@ -368,19 +433,523 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
|
||||
underTest.displayView(ViewInfo("First name", id = "id1"))
|
||||
fakeClock.advanceTime(TIMEOUT_MS / 3)
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||
fakeClock.advanceTime(TIMEOUT_MS / 3)
|
||||
underTest.displayView(ViewInfo("Third name", id = "id3"))
|
||||
fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
|
||||
underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
|
||||
fakeClock.advanceTime(1000)
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
|
||||
fakeClock.advanceTime(1000)
|
||||
underTest.displayView(ViewInfo("Third name", id = "id3", timeoutMs = 20000))
|
||||
|
||||
reset(windowManager)
|
||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||
fakeClock.advanceTime(20000 + 1)
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() {
|
||||
underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
|
||||
fakeClock.advanceTime(1000)
|
||||
underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500))
|
||||
|
||||
reset(windowManager)
|
||||
fakeClock.advanceTime(2500 + 1)
|
||||
// At this point, 3501ms have passed, so id1 only has 499ms left which is not enough.
|
||||
// So, it shouldn't be displayed.
|
||||
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lowerThenHigherPriority_higherReplacesLower() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lowerThenHigherPriority_lowerPriorityRedisplayed() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 10000
|
||||
)
|
||||
)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
timeoutMs = 2000
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("Normal Window Title")
|
||||
assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Critical Window Title")
|
||||
verify(windowManager).removeView(viewCaptor.allValues[0])
|
||||
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN the critical's timeout has expired
|
||||
fakeClock.advanceTime(2000 + 1)
|
||||
|
||||
// THEN the normal view is re-displayed
|
||||
verify(windowManager).removeView(viewCaptor.allValues[1])
|
||||
verify(windowManager).addView(any(), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 1000
|
||||
)
|
||||
)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
timeoutMs = 2000
|
||||
)
|
||||
)
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN the critical's timeout has expired
|
||||
fakeClock.advanceTime(2000 + 1)
|
||||
|
||||
// THEN the normal view is not re-displayed since it already timed out
|
||||
verify(windowManager).removeView(any())
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews).isEmpty()
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun higherThenLowerPriority_higherStaysDisplayed() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager, never()).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun higherThenLowerPriority_lowerEventuallyDisplayed() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
timeoutMs = 3000,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 5000,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager, never()).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
|
||||
// WHEN the first critical view has timed out
|
||||
fakeClock.advanceTime(3000 + 1)
|
||||
|
||||
// THEN the second normal view is displayed
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(1)
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
timeoutMs = 3000,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 200,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager, never()).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN the first critical view has timed out
|
||||
fakeClock.advanceTime(3000 + 1)
|
||||
|
||||
// THEN the second normal view is not displayed because it's already timed out
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews).isEmpty()
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun criticalThenNewCritical_newCriticalDisplayed() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical 1",
|
||||
windowTitle = "Critical Window Title 1",
|
||||
id = "critical1",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 1")
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical 2",
|
||||
windowTitle = "Critical Window Title 2",
|
||||
id = "critical2",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun normalThenNewNormal_newNormalDisplayed() {
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal 1",
|
||||
windowTitle = "Normal Window Title 1",
|
||||
id = "normal1",
|
||||
priority = ViewPriority.NORMAL,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 1")
|
||||
reset(windowManager)
|
||||
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal 2",
|
||||
windowTitle = "Normal Window Title 2",
|
||||
id = "normal2",
|
||||
priority = ViewPriority.NORMAL,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
verify(configurationController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lowerPriorityViewUpdatedWhileHigherPriorityDisplayed_eventuallyDisplaysUpdated() {
|
||||
// First, display a lower priority view
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "normal",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
// At the end of the test, we'll verify that this information isn't re-displayed.
|
||||
// Use a super long timeout so that, when we verify it wasn't re-displayed, we know
|
||||
// that it wasn't because the view just timed out.
|
||||
timeoutMs = 100000,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
|
||||
reset(windowManager)
|
||||
|
||||
// Then, display a higher priority view
|
||||
fakeClock.advanceTime(1000)
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "critical",
|
||||
windowTitle = "Critical Window Title",
|
||||
id = "critical",
|
||||
priority = ViewPriority.CRITICAL,
|
||||
timeoutMs = 3000,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
reset(windowManager)
|
||||
|
||||
// While the higher priority view is displayed, update the lower priority view with new
|
||||
// information
|
||||
fakeClock.advanceTime(1000)
|
||||
val updatedViewInfo = ViewInfo(
|
||||
name = "normal with update",
|
||||
windowTitle = "Normal Window Title",
|
||||
id = "normal",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 4000,
|
||||
)
|
||||
underTest.displayView(updatedViewInfo)
|
||||
|
||||
verify(windowManager, never()).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN the higher priority view times out
|
||||
fakeClock.advanceTime(2001)
|
||||
|
||||
// THEN the higher priority view disappears and the lower priority view *with the updated
|
||||
// information* gets displayed.
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(1)
|
||||
assertThat(underTest.mostRecentViewInfo).isEqualTo(updatedViewInfo)
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN the updated view times out
|
||||
fakeClock.advanceTime(2001)
|
||||
|
||||
// THEN the old information is never displayed
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun oldViewUpdatedWhileNewViewDisplayed_eventuallyDisplaysUpdated() {
|
||||
// First, display id1 view
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name 1",
|
||||
windowTitle = "Name 1 Title",
|
||||
id = "id1",
|
||||
priority = ViewPriority.NORMAL,
|
||||
// At the end of the test, we'll verify that this information isn't re-displayed.
|
||||
// Use a super long timeout so that, when we verify it wasn't re-displayed, we know
|
||||
// that it wasn't because the view just timed out.
|
||||
timeoutMs = 100000,
|
||||
)
|
||||
)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
|
||||
reset(windowManager)
|
||||
|
||||
// Then, display a new id2 view
|
||||
fakeClock.advanceTime(1000)
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name 2",
|
||||
windowTitle = "Name 2 Title",
|
||||
id = "id2",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 3000,
|
||||
)
|
||||
)
|
||||
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Name 2 Title")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
reset(windowManager)
|
||||
|
||||
// While the id2 view is displayed, re-display the id1 view with new information
|
||||
fakeClock.advanceTime(1000)
|
||||
val updatedViewInfo = ViewInfo(
|
||||
name = "name 1 with update",
|
||||
windowTitle = "Name 1 Title",
|
||||
id = "id1",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 3000,
|
||||
)
|
||||
underTest.displayView(updatedViewInfo)
|
||||
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN the id1 view with new information times out
|
||||
fakeClock.advanceTime(3001)
|
||||
|
||||
// THEN the id1 view disappears and the old id1 information is never displayed
|
||||
verify(windowManager).removeView(viewCaptor.value)
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun oldViewUpdatedWhileNewViewDisplayed_usesNewTimeout() {
|
||||
// First, display id1 view
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name 1",
|
||||
windowTitle = "Name 1 Title",
|
||||
id = "id1",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 5000,
|
||||
)
|
||||
)
|
||||
|
||||
// Then, display a new id2 view
|
||||
fakeClock.advanceTime(1000)
|
||||
underTest.displayView(
|
||||
ViewInfo(
|
||||
name = "name 2",
|
||||
windowTitle = "Name 2 Title",
|
||||
id = "id2",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 3000,
|
||||
)
|
||||
)
|
||||
reset(windowManager)
|
||||
|
||||
// While the id2 view is displayed, re-display the id1 view with new information *and a
|
||||
// longer timeout*
|
||||
fakeClock.advanceTime(1000)
|
||||
val updatedViewInfo = ViewInfo(
|
||||
name = "name 1 with update",
|
||||
windowTitle = "Name 1 Title",
|
||||
id = "id1",
|
||||
priority = ViewPriority.NORMAL,
|
||||
timeoutMs = 30000,
|
||||
)
|
||||
underTest.displayView(updatedViewInfo)
|
||||
|
||||
val viewCaptor = argumentCaptor<View>()
|
||||
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
|
||||
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
|
||||
assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
|
||||
assertThat(underTest.activeViews.size).isEqualTo(2)
|
||||
reset(windowManager)
|
||||
|
||||
// WHEN id1's *old* timeout occurs
|
||||
fakeClock.advanceTime(3001)
|
||||
|
||||
// THEN id1 is still displayed because it was updated with a new timeout
|
||||
verify(windowManager, never()).removeView(viewCaptor.value)
|
||||
assertThat(underTest.activeViews.size).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -395,6 +964,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
|
||||
verify(windowManager).removeView(any())
|
||||
verify(logger).logViewRemoval(deviceId, reason)
|
||||
verify(configurationController).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -414,14 +984,15 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
|
||||
inner class TestController(
|
||||
context: Context,
|
||||
logger: TemporaryViewLogger,
|
||||
logger: TemporaryViewLogger<ViewInfo>,
|
||||
windowManager: WindowManager,
|
||||
@Main mainExecutor: DelayableExecutor,
|
||||
accessibilityManager: AccessibilityManager,
|
||||
configurationController: ConfigurationController,
|
||||
powerManager: PowerManager,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
|
||||
systemClock: SystemClock,
|
||||
) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>(
|
||||
context,
|
||||
logger,
|
||||
windowManager,
|
||||
@@ -431,6 +1002,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
powerManager,
|
||||
R.layout.chipbar,
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
var mostRecentViewInfo: ViewInfo? = null
|
||||
|
||||
@@ -447,12 +1019,13 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
||||
override fun start() {}
|
||||
}
|
||||
|
||||
inner class ViewInfo(
|
||||
data class ViewInfo(
|
||||
val name: String,
|
||||
override val windowTitle: String = "Window Title",
|
||||
override val wakeReason: String = "WAKE_REASON",
|
||||
override val timeoutMs: Int = 1,
|
||||
override val timeoutMs: Int = TIMEOUT_MS.toInt(),
|
||||
override val id: String = "id",
|
||||
override val priority: ViewPriority = ViewPriority.NORMAL,
|
||||
) : TemporaryViewInfo()
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.mockito.Mockito
|
||||
@SmallTest
|
||||
class TemporaryViewLoggerTest : SysuiTestCase() {
|
||||
private lateinit var buffer: LogBuffer
|
||||
private lateinit var logger: TemporaryViewLogger
|
||||
private lateinit var logger: TemporaryViewLogger<TemporaryViewInfo>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@@ -44,13 +44,22 @@ class TemporaryViewLoggerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun logViewAddition_bufferHasLog() {
|
||||
logger.logViewAddition("test id", "Test Window Title")
|
||||
val info =
|
||||
object : TemporaryViewInfo() {
|
||||
override val id: String = "test id"
|
||||
override val priority: ViewPriority = ViewPriority.CRITICAL
|
||||
override val windowTitle: String = "Test Window Title"
|
||||
override val wakeReason: String = "wake reason"
|
||||
}
|
||||
|
||||
logger.logViewAddition(info)
|
||||
|
||||
val stringWriter = StringWriter()
|
||||
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||
val actualString = stringWriter.toString()
|
||||
|
||||
assertThat(actualString).contains(TAG)
|
||||
assertThat(actualString).contains("test id")
|
||||
assertThat(actualString).contains("Test Window Title")
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.android.systemui.common.shared.model.TintedIcon
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.temporarydisplay.ViewPriority
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
@@ -105,6 +106,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
||||
viewUtil,
|
||||
vibratorHelper,
|
||||
fakeWakeLockBuilder,
|
||||
fakeClock,
|
||||
)
|
||||
underTest.start()
|
||||
}
|
||||
@@ -408,6 +410,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
||||
wakeReason = WAKE_REASON,
|
||||
timeoutMs = TIMEOUT,
|
||||
id = DEVICE_ID,
|
||||
priority = ViewPriority.NORMAL,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.android.systemui.util.view.ViewUtil
|
||||
import com.android.systemui.util.wakelock.WakeLock
|
||||
|
||||
@@ -43,6 +44,7 @@ class FakeChipbarCoordinator(
|
||||
viewUtil: ViewUtil,
|
||||
vibratorHelper: VibratorHelper,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
systemClock: SystemClock,
|
||||
) :
|
||||
ChipbarCoordinator(
|
||||
context,
|
||||
@@ -57,6 +59,7 @@ class FakeChipbarCoordinator(
|
||||
viewUtil,
|
||||
vibratorHelper,
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||
// Just bypass the animation in tests
|
||||
|
||||
Reference in New Issue
Block a user