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.MediaTttCommandLineHelper;
|
||||||
import com.android.systemui.media.taptotransfer.MediaTttFlags;
|
import com.android.systemui.media.taptotransfer.MediaTttFlags;
|
||||||
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
|
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.receiver.MediaTttReceiverLogger;
|
||||||
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
|
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
|
||||||
import com.android.systemui.plugins.log.LogBuffer;
|
import com.android.systemui.plugins.log.LogBuffer;
|
||||||
|
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -95,19 +97,19 @@ public interface MediaModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@MediaTttSenderLogger
|
@MediaTttSenderLogger
|
||||||
static MediaTttLogger providesMediaTttSenderLogger(
|
static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
|
||||||
@MediaTttSenderLogBuffer LogBuffer buffer
|
@MediaTttSenderLogBuffer LogBuffer buffer
|
||||||
) {
|
) {
|
||||||
return new MediaTttLogger("Sender", buffer);
|
return new MediaTttLogger<>("Sender", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@MediaTttReceiverLogger
|
@MediaTttReceiverLogger
|
||||||
static MediaTttLogger providesMediaTttReceiverLogger(
|
static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
|
||||||
@MediaTttReceiverLogBuffer LogBuffer buffer
|
@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.LogBuffer
|
||||||
import com.android.systemui.plugins.log.LogLevel
|
import com.android.systemui.plugins.log.LogLevel
|
||||||
|
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||||
import com.android.systemui.temporarydisplay.TemporaryViewLogger
|
import com.android.systemui.temporarydisplay.TemporaryViewLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logger for media tap-to-transfer events.
|
* A logger for media tap-to-transfer events.
|
||||||
*
|
*
|
||||||
* @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
|
* @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,
|
deviceTypeTag: String,
|
||||||
buffer: LogBuffer
|
buffer: LogBuffer
|
||||||
) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) {
|
) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
|
||||||
/** Logs a change in the chip state for the given [mediaRouteId]. */
|
/** Logs a change in the chip state for the given [mediaRouteId]. */
|
||||||
fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
|
fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
|
||||||
buffer.log(
|
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.ContentDescription
|
||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
import com.android.systemui.common.shared.model.TintedIcon
|
import com.android.systemui.common.shared.model.TintedIcon
|
||||||
|
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||||
|
|
||||||
/** Utility methods for media tap-to-transfer. */
|
/** Utility methods for media tap-to-transfer. */
|
||||||
class MediaTttUtils {
|
class MediaTttUtils {
|
||||||
@@ -47,7 +48,7 @@ class MediaTttUtils {
|
|||||||
fun getIconInfoFromPackageName(
|
fun getIconInfoFromPackageName(
|
||||||
context: Context,
|
context: Context,
|
||||||
appPackageName: String?,
|
appPackageName: String?,
|
||||||
logger: MediaTttLogger
|
logger: MediaTttLogger<out TemporaryViewInfo>
|
||||||
): IconInfo {
|
): IconInfo {
|
||||||
if (appPackageName != null) {
|
if (appPackageName != null) {
|
||||||
val packageManager = context.packageManager
|
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.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
|
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
|
||||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||||
|
import com.android.systemui.temporarydisplay.ViewPriority
|
||||||
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
|
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.time.SystemClock
|
||||||
import com.android.systemui.util.view.ViewUtil
|
import com.android.systemui.util.view.ViewUtil
|
||||||
import com.android.systemui.util.wakelock.WakeLock
|
import com.android.systemui.util.wakelock.WakeLock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -62,7 +64,7 @@ import javax.inject.Inject
|
|||||||
open class MediaTttChipControllerReceiver @Inject constructor(
|
open class MediaTttChipControllerReceiver @Inject constructor(
|
||||||
private val commandQueue: CommandQueue,
|
private val commandQueue: CommandQueue,
|
||||||
context: Context,
|
context: Context,
|
||||||
@MediaTttReceiverLogger logger: MediaTttLogger,
|
@MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
|
||||||
windowManager: WindowManager,
|
windowManager: WindowManager,
|
||||||
mainExecutor: DelayableExecutor,
|
mainExecutor: DelayableExecutor,
|
||||||
accessibilityManager: AccessibilityManager,
|
accessibilityManager: AccessibilityManager,
|
||||||
@@ -73,7 +75,8 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
|||||||
private val uiEventLogger: MediaTttReceiverUiEventLogger,
|
private val uiEventLogger: MediaTttReceiverUiEventLogger,
|
||||||
private val viewUtil: ViewUtil,
|
private val viewUtil: ViewUtil,
|
||||||
wakeLockBuilder: WakeLock.Builder,
|
wakeLockBuilder: WakeLock.Builder,
|
||||||
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
|
systemClock: SystemClock,
|
||||||
|
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
|
||||||
context,
|
context,
|
||||||
logger,
|
logger,
|
||||||
windowManager,
|
windowManager,
|
||||||
@@ -83,6 +86,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
|||||||
powerManager,
|
powerManager,
|
||||||
R.layout.media_ttt_chip_receiver,
|
R.layout.media_ttt_chip_receiver,
|
||||||
wakeLockBuilder,
|
wakeLockBuilder,
|
||||||
|
systemClock,
|
||||||
) {
|
) {
|
||||||
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
||||||
override val windowLayoutParams = commonWindowLayoutParams.apply {
|
override val windowLayoutParams = commonWindowLayoutParams.apply {
|
||||||
@@ -290,4 +294,5 @@ data class ChipReceiverInfo(
|
|||||||
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
|
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
|
||||||
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
|
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
|
||||||
override val id: String,
|
override val id: String,
|
||||||
|
override val priority: ViewPriority = ViewPriority.NORMAL,
|
||||||
) : TemporaryViewInfo()
|
) : 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.MediaTttLogger
|
||||||
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
|
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
|
||||||
import com.android.systemui.statusbar.CommandQueue
|
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.ChipbarCoordinator
|
||||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
|
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
|
||||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
||||||
@@ -46,7 +47,7 @@ constructor(
|
|||||||
private val chipbarCoordinator: ChipbarCoordinator,
|
private val chipbarCoordinator: ChipbarCoordinator,
|
||||||
private val commandQueue: CommandQueue,
|
private val commandQueue: CommandQueue,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@MediaTttSenderLogger private val logger: MediaTttLogger,
|
@MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
|
||||||
private val mediaTttFlags: MediaTttFlags,
|
private val mediaTttFlags: MediaTttFlags,
|
||||||
private val uiEventLogger: MediaTttSenderUiEventLogger,
|
private val uiEventLogger: MediaTttSenderUiEventLogger,
|
||||||
) : CoreStartable {
|
) : CoreStartable {
|
||||||
@@ -146,7 +147,7 @@ constructor(
|
|||||||
routeInfo: MediaRoute2Info,
|
routeInfo: MediaRoute2Info,
|
||||||
undoCallback: IUndoMediaTransferCallback?,
|
undoCallback: IUndoMediaTransferCallback?,
|
||||||
context: Context,
|
context: Context,
|
||||||
logger: MediaTttLogger,
|
logger: MediaTttLogger<ChipbarInfo>,
|
||||||
): ChipbarInfo {
|
): ChipbarInfo {
|
||||||
val packageName = routeInfo.clientPackageName
|
val packageName = routeInfo.clientPackageName
|
||||||
val otherDeviceName = routeInfo.name.toString()
|
val otherDeviceName = routeInfo.name.toString()
|
||||||
@@ -180,6 +181,7 @@ constructor(
|
|||||||
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
|
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
|
||||||
timeoutMs = chipStateSender.timeout,
|
timeoutMs = chipStateSender.timeout,
|
||||||
id = routeInfo.id,
|
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.dagger.qualifiers.Main
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.time.SystemClock
|
||||||
import com.android.systemui.util.wakelock.WakeLock
|
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
|
* 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].
|
* 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 context: Context,
|
||||||
internal val logger: U,
|
internal val logger: U,
|
||||||
internal val windowManager: WindowManager,
|
internal val windowManager: WindowManager,
|
||||||
@@ -55,6 +72,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
|||||||
private val powerManager: PowerManager,
|
private val powerManager: PowerManager,
|
||||||
@LayoutRes private val viewLayoutRes: Int,
|
@LayoutRes private val viewLayoutRes: Int,
|
||||||
private val wakeLockBuilder: WakeLock.Builder,
|
private val wakeLockBuilder: WakeLock.Builder,
|
||||||
|
private val systemClock: SystemClock,
|
||||||
) : CoreStartable {
|
) : CoreStartable {
|
||||||
/**
|
/**
|
||||||
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
|
* 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
|
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,
|
* A list of the currently active views, ordered from highest priority in the beginning to
|
||||||
* then released when view is removed.
|
* 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 fun getCurrentDisplayInfo(): DisplayInfo? {
|
||||||
private var wakeReasonAcquired: String? = null
|
return activeViews.getOrNull(0)
|
||||||
|
}
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the view with the provided [newInfo].
|
* 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.
|
* display the correct information in the view.
|
||||||
* @param onViewTimeout a runnable that runs after the view timeout.
|
* @param onViewTimeout a runnable that runs after the view timeout.
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
|
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(
|
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
|
||||||
newInfo.timeoutMs,
|
newInfo.timeoutMs,
|
||||||
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
|
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
|
||||||
// include it just to be safe.
|
// include it just to be safe.
|
||||||
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
|
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.
|
val currentDisplayInfo = getCurrentDisplayInfo()
|
||||||
if (position == 0) {
|
|
||||||
cancelViewTimeout?.run()
|
// 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()
|
onViewTimeout?.run()
|
||||||
},
|
},
|
||||||
timeout.toLong()
|
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. */
|
/** Inflates a new view, updates it with [DisplayInfo.info], and adds the view to the window. */
|
||||||
private fun inflateAndUpdateView(newInfo: T) {
|
private fun inflateAndUpdateView(displayInfo: DisplayInfo) {
|
||||||
|
val newInfo = displayInfo.info
|
||||||
val newView = LayoutInflater
|
val newView = LayoutInflater
|
||||||
.from(context)
|
.from(context)
|
||||||
.inflate(viewLayoutRes, null) as ViewGroup
|
.inflate(viewLayoutRes, null) as ViewGroup
|
||||||
val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
|
displayInfo.view = newView
|
||||||
newViewController.init()
|
|
||||||
|
|
||||||
// We don't need to hold on to the view controller since we never set anything additional
|
// 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.
|
// on it -- it will be automatically cleaned up when the view is detached.
|
||||||
val newDisplayInfo = DisplayInfo(newView, newInfo)
|
val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
|
||||||
displayInfo = newDisplayInfo
|
newViewController.init()
|
||||||
updateView(newDisplayInfo.info, newDisplayInfo.view)
|
|
||||||
|
updateView(newInfo, newView)
|
||||||
|
|
||||||
val paramsWithTitle = WindowManager.LayoutParams().also {
|
val paramsWithTitle = WindowManager.LayoutParams().also {
|
||||||
it.copyFrom(windowLayoutParams)
|
it.copyFrom(windowLayoutParams)
|
||||||
@@ -206,11 +260,15 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Removes then re-inflates the view. */
|
/** Removes then re-inflates the view. */
|
||||||
|
@Synchronized
|
||||||
private fun reinflateView() {
|
private fun reinflateView() {
|
||||||
val currentViewInfo = displayInfo ?: return
|
val currentDisplayInfo = getCurrentDisplayInfo() ?: return
|
||||||
|
|
||||||
windowManager.removeView(currentViewInfo.view)
|
val view = checkNotNull(currentDisplayInfo.view) {
|
||||||
inflateAndUpdateView(currentViewInfo.info)
|
"First item in activeViews list must have a valid view"
|
||||||
|
}
|
||||||
|
windowManager.removeView(view)
|
||||||
|
inflateAndUpdateView(currentDisplayInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
|
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 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
|
* @param removalReason a short string describing why the view was removed (timeout, state
|
||||||
* change, etc.)
|
* change, etc.)
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun removeView(id: String, removalReason: String) {
|
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)
|
logger.logViewRemoval(id, removalReason)
|
||||||
|
|
||||||
val newViewToDisplay = if (activeViews.isEmpty()) {
|
val displayInfo = activeViews.firstOrNull { it.info.id == id }
|
||||||
null
|
if (displayInfo == null) {
|
||||||
} else {
|
logger.logViewRemovalIgnored(id, "View not found in list")
|
||||||
activeViews[0].second
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentView = currentDisplayInfo.view
|
val currentlyDisplayedView = activeViews[0]
|
||||||
animateViewOut(currentView) {
|
// Remove immediately (instead as part of the animation end runnable) so that if a new view
|
||||||
windowManager.removeView(currentView)
|
// event comes in while this view is animating out, we still display the new view
|
||||||
wakeLock?.release(wakeReasonAcquired)
|
// 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
|
// 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) {
|
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
|
* Hides the view from the window, but keeps [displayInfo] around in [activeViews] in case it
|
||||||
* active view with that ID
|
* should be re-displayed later.
|
||||||
*
|
|
||||||
* @param id that temporary view belonged to.
|
|
||||||
*
|
|
||||||
* @return index of the view in the stack , otherwise null.
|
|
||||||
*/
|
*/
|
||||||
private fun findAndRemoveFromActiveViewsList(id: String): Int? {
|
private fun hideView(displayInfo: DisplayInfo) {
|
||||||
for (i in 0 until activeViews.size) {
|
logger.logViewHidden(displayInfo.info)
|
||||||
if (activeViews[i].first == id) {
|
removeViewFromWindow(displayInfo)
|
||||||
activeViews.removeAt(i)
|
}
|
||||||
return i
|
|
||||||
}
|
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. */
|
/** A container for all the display-related state objects. */
|
||||||
private inner class DisplayInfo(
|
inner class DisplayInfo(
|
||||||
/** The view currently being displayed. */
|
/**
|
||||||
val view: ViewGroup,
|
* 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,
|
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"
|
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(
|
private data class IconInfo(
|
||||||
val iconName: String,
|
val iconName: String,
|
||||||
|
|||||||
@@ -42,6 +42,20 @@ abstract class TemporaryViewInfo {
|
|||||||
* The id of the temporary view.
|
* The id of the temporary view.
|
||||||
*/
|
*/
|
||||||
abstract val id: String
|
abstract val id: String
|
||||||
|
|
||||||
|
/** The priority for this view. */
|
||||||
|
abstract val priority: ViewPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
const val DEFAULT_TIMEOUT_MILLIS = 10000
|
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
|
import com.android.systemui.plugins.log.LogLevel
|
||||||
|
|
||||||
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
|
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
|
||||||
open class TemporaryViewLogger(
|
open class TemporaryViewLogger<T : TemporaryViewInfo>(
|
||||||
internal val buffer: LogBuffer,
|
internal val buffer: LogBuffer,
|
||||||
internal val tag: String,
|
internal val tag: String,
|
||||||
) {
|
) {
|
||||||
/** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
|
fun logViewExpiration(info: T) {
|
||||||
fun logViewAddition(id: String, windowTitle: String) {
|
|
||||||
buffer.log(
|
buffer.log(
|
||||||
tag,
|
tag,
|
||||||
LogLevel.DEBUG,
|
LogLevel.DEBUG,
|
||||||
{
|
{
|
||||||
str1 = windowTitle
|
str1 = info.id
|
||||||
str2 = 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.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
|
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
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.view.ViewUtil
|
||||||
import com.android.systemui.util.wakelock.WakeLock
|
import com.android.systemui.util.wakelock.WakeLock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -77,6 +78,7 @@ open class ChipbarCoordinator @Inject constructor(
|
|||||||
private val viewUtil: ViewUtil,
|
private val viewUtil: ViewUtil,
|
||||||
private val vibratorHelper: VibratorHelper,
|
private val vibratorHelper: VibratorHelper,
|
||||||
wakeLockBuilder: WakeLock.Builder,
|
wakeLockBuilder: WakeLock.Builder,
|
||||||
|
systemClock: SystemClock,
|
||||||
) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
|
) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
|
||||||
context,
|
context,
|
||||||
logger,
|
logger,
|
||||||
@@ -87,6 +89,7 @@ open class ChipbarCoordinator @Inject constructor(
|
|||||||
powerManager,
|
powerManager,
|
||||||
R.layout.chipbar,
|
R.layout.chipbar,
|
||||||
wakeLockBuilder,
|
wakeLockBuilder,
|
||||||
|
systemClock,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private lateinit var parent: ChipbarRootView
|
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.Text
|
||||||
import com.android.systemui.common.shared.model.TintedIcon
|
import com.android.systemui.common.shared.model.TintedIcon
|
||||||
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
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].
|
* 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 wakeReason: String,
|
||||||
override val timeoutMs: Int,
|
override val timeoutMs: Int,
|
||||||
override val id: String,
|
override val id: String,
|
||||||
|
override val priority: ViewPriority,
|
||||||
) : TemporaryViewInfo() {
|
) : TemporaryViewInfo() {
|
||||||
companion object {
|
companion object {
|
||||||
@AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
|
@AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class ChipbarLogger
|
|||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@ChipbarLog buffer: LogBuffer,
|
@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
|
* Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
|
||||||
* [endItemDesc].
|
* [endItemDesc].
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.android.systemui.dump.DumpManager
|
|||||||
import com.android.systemui.log.LogBufferFactory
|
import com.android.systemui.log.LogBufferFactory
|
||||||
import com.android.systemui.plugins.log.LogBuffer
|
import com.android.systemui.plugins.log.LogBuffer
|
||||||
import com.android.systemui.plugins.log.LogcatEchoTracker
|
import com.android.systemui.plugins.log.LogcatEchoTracker
|
||||||
|
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
@@ -33,7 +34,7 @@ import org.mockito.Mockito.mock
|
|||||||
class MediaTttLoggerTest : SysuiTestCase() {
|
class MediaTttLoggerTest : SysuiTestCase() {
|
||||||
|
|
||||||
private lateinit var buffer: LogBuffer
|
private lateinit var buffer: LogBuffer
|
||||||
private lateinit var logger: MediaTttLogger
|
private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp () {
|
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
|
||||||
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
|
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
|
||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
|
import com.android.systemui.temporarydisplay.TemporaryViewInfo
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -40,7 +41,7 @@ class MediaTttUtilsTest : SysuiTestCase() {
|
|||||||
private lateinit var appIconFromPackageName: Drawable
|
private lateinit var appIconFromPackageName: Drawable
|
||||||
@Mock private lateinit var packageManager: PackageManager
|
@Mock private lateinit var packageManager: PackageManager
|
||||||
@Mock private lateinit var applicationInfo: ApplicationInfo
|
@Mock private lateinit var applicationInfo: ApplicationInfo
|
||||||
@Mock private lateinit var logger: MediaTttLogger
|
@Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
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.CommandQueue
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.time.SystemClock
|
||||||
import com.android.systemui.util.view.ViewUtil
|
import com.android.systemui.util.view.ViewUtil
|
||||||
import com.android.systemui.util.wakelock.WakeLock
|
import com.android.systemui.util.wakelock.WakeLock
|
||||||
|
|
||||||
class FakeMediaTttChipControllerReceiver(
|
class FakeMediaTttChipControllerReceiver(
|
||||||
commandQueue: CommandQueue,
|
commandQueue: CommandQueue,
|
||||||
context: Context,
|
context: Context,
|
||||||
logger: MediaTttLogger,
|
logger: MediaTttLogger<ChipReceiverInfo>,
|
||||||
windowManager: WindowManager,
|
windowManager: WindowManager,
|
||||||
mainExecutor: DelayableExecutor,
|
mainExecutor: DelayableExecutor,
|
||||||
accessibilityManager: AccessibilityManager,
|
accessibilityManager: AccessibilityManager,
|
||||||
@@ -44,6 +45,7 @@ class FakeMediaTttChipControllerReceiver(
|
|||||||
uiEventLogger: MediaTttReceiverUiEventLogger,
|
uiEventLogger: MediaTttReceiverUiEventLogger,
|
||||||
viewUtil: ViewUtil,
|
viewUtil: ViewUtil,
|
||||||
wakeLockBuilder: WakeLock.Builder,
|
wakeLockBuilder: WakeLock.Builder,
|
||||||
|
systemClock: SystemClock,
|
||||||
) :
|
) :
|
||||||
MediaTttChipControllerReceiver(
|
MediaTttChipControllerReceiver(
|
||||||
commandQueue,
|
commandQueue,
|
||||||
@@ -59,6 +61,7 @@ class FakeMediaTttChipControllerReceiver(
|
|||||||
uiEventLogger,
|
uiEventLogger,
|
||||||
viewUtil,
|
viewUtil,
|
||||||
wakeLockBuilder,
|
wakeLockBuilder,
|
||||||
|
systemClock,
|
||||||
) {
|
) {
|
||||||
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||||
// Just bypass the animation in tests
|
// Just bypass the animation in tests
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
|||||||
@Mock
|
@Mock
|
||||||
private lateinit var applicationInfo: ApplicationInfo
|
private lateinit var applicationInfo: ApplicationInfo
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var logger: MediaTttLogger
|
private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var accessibilityManager: AccessibilityManager
|
private lateinit var accessibilityManager: AccessibilityManager
|
||||||
@Mock
|
@Mock
|
||||||
@@ -128,6 +128,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
|||||||
receiverUiEventLogger,
|
receiverUiEventLogger,
|
||||||
viewUtil,
|
viewUtil,
|
||||||
fakeWakeLockBuilder,
|
fakeWakeLockBuilder,
|
||||||
|
fakeClock,
|
||||||
)
|
)
|
||||||
controllerReceiver.start()
|
controllerReceiver.start()
|
||||||
|
|
||||||
@@ -155,6 +156,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
|||||||
receiverUiEventLogger,
|
receiverUiEventLogger,
|
||||||
viewUtil,
|
viewUtil,
|
||||||
fakeWakeLockBuilder,
|
fakeWakeLockBuilder,
|
||||||
|
fakeClock,
|
||||||
)
|
)
|
||||||
controllerReceiver.start()
|
controllerReceiver.start()
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue
|
|||||||
import com.android.systemui.statusbar.VibratorHelper
|
import com.android.systemui.statusbar.VibratorHelper
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
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.ChipbarLogger
|
||||||
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
|
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
@@ -83,7 +84,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var falsingManager: FalsingManager
|
@Mock private lateinit var falsingManager: FalsingManager
|
||||||
@Mock private lateinit var falsingCollector: FalsingCollector
|
@Mock private lateinit var falsingCollector: FalsingCollector
|
||||||
@Mock private lateinit var chipbarLogger: ChipbarLogger
|
@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 mediaTttFlags: MediaTttFlags
|
||||||
@Mock private lateinit var packageManager: PackageManager
|
@Mock private lateinit var packageManager: PackageManager
|
||||||
@Mock private lateinit var powerManager: PowerManager
|
@Mock private lateinit var powerManager: PowerManager
|
||||||
@@ -142,6 +143,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
|
|||||||
viewUtil,
|
viewUtil,
|
||||||
vibratorHelper,
|
vibratorHelper,
|
||||||
fakeWakeLockBuilder,
|
fakeWakeLockBuilder,
|
||||||
|
fakeClock,
|
||||||
)
|
)
|
||||||
chipbarCoordinator.start()
|
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.argumentCaptor
|
||||||
import com.android.systemui.util.mockito.capture
|
import com.android.systemui.util.mockito.capture
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
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.WakeLock
|
||||||
import com.android.systemui.util.wakelock.WakeLockFake
|
import com.android.systemui.util.wakelock.WakeLockFake
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
@@ -59,7 +60,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
private lateinit var fakeWakeLock: WakeLockFake
|
private lateinit var fakeWakeLock: WakeLockFake
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var logger: TemporaryViewLogger
|
private lateinit var logger: TemporaryViewLogger<ViewInfo>
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var accessibilityManager: AccessibilityManager
|
private lateinit var accessibilityManager: AccessibilityManager
|
||||||
@Mock
|
@Mock
|
||||||
@@ -74,7 +75,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
|
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
|
||||||
.thenReturn(TIMEOUT_MS.toInt())
|
.thenAnswer { it.arguments[0] }
|
||||||
|
|
||||||
fakeClock = FakeSystemClock()
|
fakeClock = FakeSystemClock()
|
||||||
fakeExecutor = FakeExecutor(fakeClock)
|
fakeExecutor = FakeExecutor(fakeClock)
|
||||||
@@ -84,14 +85,15 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
|
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
|
||||||
|
|
||||||
underTest = TestController(
|
underTest = TestController(
|
||||||
context,
|
context,
|
||||||
logger,
|
logger,
|
||||||
windowManager,
|
windowManager,
|
||||||
fakeExecutor,
|
fakeExecutor,
|
||||||
accessibilityManager,
|
accessibilityManager,
|
||||||
configurationController,
|
configurationController,
|
||||||
powerManager,
|
powerManager,
|
||||||
fakeWakeLockBuilder,
|
fakeWakeLockBuilder,
|
||||||
|
fakeClock,
|
||||||
)
|
)
|
||||||
underTest.start()
|
underTest.start()
|
||||||
}
|
}
|
||||||
@@ -112,14 +114,14 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun displayView_logged() {
|
fun displayView_logged() {
|
||||||
underTest.displayView(
|
val info = ViewInfo(
|
||||||
ViewInfo(
|
name = "name",
|
||||||
name = "name",
|
windowTitle = "Fake Window Title",
|
||||||
windowTitle = "Fake Window Title",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
verify(logger).logViewAddition("id", "Fake Window Title")
|
underTest.displayView(info)
|
||||||
|
|
||||||
|
verify(logger).logViewAddition(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -168,10 +170,11 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
|
fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
|
||||||
underTest.displayView(
|
underTest.displayView(
|
||||||
ViewInfo(
|
ViewInfo(
|
||||||
name = "name",
|
name = "name",
|
||||||
|
id = "First",
|
||||||
windowTitle = "First Fake Window Title",
|
windowTitle = "First Fake Window Title",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -179,6 +182,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
underTest.displayView(
|
underTest.displayView(
|
||||||
ViewInfo(
|
ViewInfo(
|
||||||
name = "name",
|
name = "name",
|
||||||
|
id = "Second",
|
||||||
windowTitle = "Second Fake Window Title",
|
windowTitle = "Second Fake Window Title",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -262,20 +266,70 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
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
|
@Test
|
||||||
fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
|
fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
|
||||||
underTest.displayView(ViewInfo("First name", id = "id1"))
|
underTest.displayView(ViewInfo("First name", id = "id1"))
|
||||||
|
|
||||||
verify(windowManager).addView(any(), any())
|
verify(windowManager).addView(any(), any())
|
||||||
|
|
||||||
reset(windowManager)
|
reset(windowManager)
|
||||||
|
|
||||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||||
|
|
||||||
|
verify(windowManager).removeView(any())
|
||||||
|
verify(windowManager).addView(any(), any())
|
||||||
|
reset(windowManager)
|
||||||
|
|
||||||
underTest.removeView("id2", "test reason")
|
underTest.removeView("id2", "test reason")
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
|
verify(windowManager).addView(any(), any())
|
||||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
|
||||||
|
|
||||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
||||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
|
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
|
||||||
|
|
||||||
@@ -284,6 +338,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||||
|
verify(configurationController).removeCallback(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -291,19 +346,28 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
underTest.displayView(ViewInfo("First name", id = "id1"))
|
underTest.displayView(ViewInfo("First name", id = "id1"))
|
||||||
|
|
||||||
verify(windowManager).addView(any(), any())
|
verify(windowManager).addView(any(), any())
|
||||||
|
|
||||||
reset(windowManager)
|
reset(windowManager)
|
||||||
|
|
||||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
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")
|
underTest.removeView("id1", "test reason")
|
||||||
|
|
||||||
|
// THEN we don't update anything
|
||||||
verify(windowManager, never()).removeView(any())
|
verify(windowManager, never()).removeView(any())
|
||||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
|
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
|
||||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
||||||
|
verify(configurationController, never()).removeCallback(any())
|
||||||
|
|
||||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||||
|
verify(configurationController).removeCallback(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -312,33 +376,31 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||||
underTest.displayView(ViewInfo("Third name", id = "id3"))
|
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)
|
reset(windowManager)
|
||||||
underTest.removeView("id3", "test reason")
|
underTest.removeView("id3", "test reason")
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
|
|
||||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
|
||||||
|
|
||||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
|
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
|
||||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
|
||||||
|
verify(configurationController, never()).removeCallback(any())
|
||||||
|
|
||||||
reset(windowManager)
|
reset(windowManager)
|
||||||
underTest.removeView("id2", "test reason")
|
underTest.removeView("id2", "test reason")
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
|
|
||||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
|
||||||
|
|
||||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
||||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
|
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
|
||||||
|
verify(configurationController, never()).removeCallback(any())
|
||||||
|
|
||||||
reset(windowManager)
|
reset(windowManager)
|
||||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
assertThat(underTest.activeViews.size).isEqualTo(0)
|
||||||
|
verify(configurationController).removeCallback(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -347,18 +409,21 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
underTest.displayView(ViewInfo("New name", id = "id1"))
|
underTest.displayView(ViewInfo("New name", id = "id1"))
|
||||||
|
|
||||||
verify(windowManager).addView(any(), any())
|
verify(windowManager).addView(any(), any())
|
||||||
|
|
||||||
reset(windowManager)
|
reset(windowManager)
|
||||||
|
|
||||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
||||||
|
|
||||||
|
verify(windowManager).removeView(any())
|
||||||
|
verify(windowManager).addView(any(), any())
|
||||||
|
reset(windowManager)
|
||||||
|
|
||||||
underTest.removeView("id2", "test reason")
|
underTest.removeView("id2", "test reason")
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
|
verify(windowManager).addView(any(), any())
|
||||||
fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
|
|
||||||
|
|
||||||
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
|
||||||
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
|
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)
|
reset(windowManager)
|
||||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
||||||
@@ -368,19 +433,523 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
|
fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
|
||||||
underTest.displayView(ViewInfo("First name", id = "id1"))
|
underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
|
||||||
fakeClock.advanceTime(TIMEOUT_MS / 3)
|
fakeClock.advanceTime(1000)
|
||||||
underTest.displayView(ViewInfo("Second name", id = "id2"))
|
underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
|
||||||
fakeClock.advanceTime(TIMEOUT_MS / 3)
|
fakeClock.advanceTime(1000)
|
||||||
underTest.displayView(ViewInfo("Third name", id = "id3"))
|
underTest.displayView(ViewInfo("Third name", id = "id3", timeoutMs = 20000))
|
||||||
|
|
||||||
reset(windowManager)
|
reset(windowManager)
|
||||||
fakeClock.advanceTime(TIMEOUT_MS + 1)
|
fakeClock.advanceTime(20000 + 1)
|
||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
verify(windowManager, never()).addView(any(), any())
|
verify(windowManager, never()).addView(any(), any())
|
||||||
assertThat(underTest.activeViews.size).isEqualTo(0)
|
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
|
@Test
|
||||||
@@ -395,6 +964,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
verify(logger).logViewRemoval(deviceId, reason)
|
verify(logger).logViewRemoval(deviceId, reason)
|
||||||
|
verify(configurationController).removeCallback(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -414,14 +984,15 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
inner class TestController(
|
inner class TestController(
|
||||||
context: Context,
|
context: Context,
|
||||||
logger: TemporaryViewLogger,
|
logger: TemporaryViewLogger<ViewInfo>,
|
||||||
windowManager: WindowManager,
|
windowManager: WindowManager,
|
||||||
@Main mainExecutor: DelayableExecutor,
|
@Main mainExecutor: DelayableExecutor,
|
||||||
accessibilityManager: AccessibilityManager,
|
accessibilityManager: AccessibilityManager,
|
||||||
configurationController: ConfigurationController,
|
configurationController: ConfigurationController,
|
||||||
powerManager: PowerManager,
|
powerManager: PowerManager,
|
||||||
wakeLockBuilder: WakeLock.Builder,
|
wakeLockBuilder: WakeLock.Builder,
|
||||||
) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
|
systemClock: SystemClock,
|
||||||
|
) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>(
|
||||||
context,
|
context,
|
||||||
logger,
|
logger,
|
||||||
windowManager,
|
windowManager,
|
||||||
@@ -431,6 +1002,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
powerManager,
|
powerManager,
|
||||||
R.layout.chipbar,
|
R.layout.chipbar,
|
||||||
wakeLockBuilder,
|
wakeLockBuilder,
|
||||||
|
systemClock,
|
||||||
) {
|
) {
|
||||||
var mostRecentViewInfo: ViewInfo? = null
|
var mostRecentViewInfo: ViewInfo? = null
|
||||||
|
|
||||||
@@ -447,12 +1019,13 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
|
|||||||
override fun start() {}
|
override fun start() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewInfo(
|
data class ViewInfo(
|
||||||
val name: String,
|
val name: String,
|
||||||
override val windowTitle: String = "Window Title",
|
override val windowTitle: String = "Window Title",
|
||||||
override val wakeReason: String = "WAKE_REASON",
|
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 id: String = "id",
|
||||||
|
override val priority: ViewPriority = ViewPriority.NORMAL,
|
||||||
) : TemporaryViewInfo()
|
) : TemporaryViewInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.mockito.Mockito
|
|||||||
@SmallTest
|
@SmallTest
|
||||||
class TemporaryViewLoggerTest : SysuiTestCase() {
|
class TemporaryViewLoggerTest : SysuiTestCase() {
|
||||||
private lateinit var buffer: LogBuffer
|
private lateinit var buffer: LogBuffer
|
||||||
private lateinit var logger: TemporaryViewLogger
|
private lateinit var logger: TemporaryViewLogger<TemporaryViewInfo>
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
@@ -44,13 +44,22 @@ class TemporaryViewLoggerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun logViewAddition_bufferHasLog() {
|
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()
|
val stringWriter = StringWriter()
|
||||||
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||||
val actualString = stringWriter.toString()
|
val actualString = stringWriter.toString()
|
||||||
|
|
||||||
assertThat(actualString).contains(TAG)
|
assertThat(actualString).contains(TAG)
|
||||||
|
assertThat(actualString).contains("test id")
|
||||||
assertThat(actualString).contains("Test Window Title")
|
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.plugins.FalsingManager
|
||||||
import com.android.systemui.statusbar.VibratorHelper
|
import com.android.systemui.statusbar.VibratorHelper
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
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.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
@@ -105,6 +106,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
|||||||
viewUtil,
|
viewUtil,
|
||||||
vibratorHelper,
|
vibratorHelper,
|
||||||
fakeWakeLockBuilder,
|
fakeWakeLockBuilder,
|
||||||
|
fakeClock,
|
||||||
)
|
)
|
||||||
underTest.start()
|
underTest.start()
|
||||||
}
|
}
|
||||||
@@ -408,6 +410,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
|||||||
wakeReason = WAKE_REASON,
|
wakeReason = WAKE_REASON,
|
||||||
timeoutMs = TIMEOUT,
|
timeoutMs = TIMEOUT,
|
||||||
id = DEVICE_ID,
|
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.VibratorHelper
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.time.SystemClock
|
||||||
import com.android.systemui.util.view.ViewUtil
|
import com.android.systemui.util.view.ViewUtil
|
||||||
import com.android.systemui.util.wakelock.WakeLock
|
import com.android.systemui.util.wakelock.WakeLock
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ class FakeChipbarCoordinator(
|
|||||||
viewUtil: ViewUtil,
|
viewUtil: ViewUtil,
|
||||||
vibratorHelper: VibratorHelper,
|
vibratorHelper: VibratorHelper,
|
||||||
wakeLockBuilder: WakeLock.Builder,
|
wakeLockBuilder: WakeLock.Builder,
|
||||||
|
systemClock: SystemClock,
|
||||||
) :
|
) :
|
||||||
ChipbarCoordinator(
|
ChipbarCoordinator(
|
||||||
context,
|
context,
|
||||||
@@ -57,6 +59,7 @@ class FakeChipbarCoordinator(
|
|||||||
viewUtil,
|
viewUtil,
|
||||||
vibratorHelper,
|
vibratorHelper,
|
||||||
wakeLockBuilder,
|
wakeLockBuilder,
|
||||||
|
systemClock,
|
||||||
) {
|
) {
|
||||||
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||||
// Just bypass the animation in tests
|
// Just bypass the animation in tests
|
||||||
|
|||||||
Reference in New Issue
Block a user