diff options
20 files changed, 1019 insertions, 201 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 3e5d337bff9d..bb833df1ff69 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -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); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt index b55bedda2dc1..8aef9385fe3e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index 009595a6da8b..066c1853818f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 1c3a53cbf815..9ca5fad35ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -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() diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index ec1984d78cf9..9f44d984124f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -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, ) } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index ea4020861a09..db7315f311ac 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -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 - - /** A string that keeps track of wakelock reason once it is acquired till it gets released */ - private var wakeReasonAcquired: String? = null + internal val activeViews: MutableList<DisplayInfo> = mutableListOf() - /** - * 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 + + 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 + } + + 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) + } - // Only cancel timeout of the most recent view displayed, as it will be reset. - if (position == 0) { - cancelViewTimeout?.run() + 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() } - cancelViewTimeout = mainExecutor.executeDelayed( + 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 + logger.logViewRemoval(id, removalReason) - val removalPosition = findAndRemoveFromActiveViewsList(id) - if (removalPosition == null) { - logger.logViewRemovalIgnored(id, "view not found in the list") + val displayInfo = activeViews.firstOrNull { it.info.id == id } + if (displayInfo == null) { + logger.logViewRemovalIgnored(id, "View not found in list") return } - if (removalPosition != 0) { - logger.logViewRemovalIgnored(id, "most recent view is being displayed.") + + 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) + + // No need to time the view out since it's already gone + displayInfo.cancelViewTimeout?.run() + + if (displayInfo.view == null) { + logger.logViewRemovalIgnored(id, "No view to remove") return } - logger.logViewRemoval(id, removalReason) - val newViewToDisplay = if (activeViews.isEmpty()) { - null - } else { - activeViews[0].second + if (currentlyDisplayedView.info.id != id) { + logger.logViewRemovalIgnored(id, "View isn't the currently displayed view") + return } - val currentView = currentDisplayInfo.view - animateViewOut(currentView) { - windowManager.removeView(currentView) - wakeLock?.release(wakeReasonAcquired) - } + removeViewFromWindow(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() + // 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, - - /** The info currently being displayed. */ + inner class DisplayInfo( + /** + * The view currently being displayed. + * + * Null if this info isn't currently being displayed. + */ + var view: ViewGroup?, + + /** 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, diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt index df8396051dda..5596cf68b4bc 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt @@ -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, +} diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt index 133a384e7e17..ec6965a83b5a 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -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 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 }, - { "View added. window=$str1 id=$str2" } + { "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" + } ) } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index 4d91e35856dc..14ba63a2738f 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index a3eef8032b3b..dd4bd26e3bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt index e477cd68673a..fcfbe0aeedf6 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt @@ -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]. diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt index e009e8651f2a..0e7bf8d9d465 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -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 () { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt index cce3e369c0b8..561867f78e60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt index 4aa982ed1609..bad3f0374a31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 23f7cdb45026..ffa261a26279 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -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() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 311740e17310..b03a545f787f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -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() diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index 09f0d4a10410..82153d5610a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -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", ) ) @@ -263,19 +267,69 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } @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")) - underTest.removeView("id2", "test reason") verify(windowManager).removeView(any()) + verify(windowManager).addView(any(), any()) + reset(windowManager) - fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1) + underTest.removeView("id2", "test reason") + verify(windowManager).removeView(any()) + 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")) - underTest.removeView("id2", "test reason") verify(windowManager).removeView(any()) + verify(windowManager).addView(any(), any()) + reset(windowManager) - fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1) + underTest.removeView("id2", "test reason") + verify(windowManager).removeView(any()) + 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() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt index 116b8fe62b37..2e66b205bfd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -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") } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 7014f93fba4a..2e4d8e74ad6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -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, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt index beedf9f337bc..d5167b3890b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt @@ -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 |