Loading packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +7 −5 Original line number Diff line number Diff line Loading @@ -61,7 +61,7 @@ class MediaTttCommandLineHelper @Inject constructor( @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager val routeInfo = MediaRoute2Info.Builder("id", args[0]) val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0]) .addFeature("feature") val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false") if (useAppIcon) { Loading Loading @@ -107,7 +107,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " + "<deviceName> <chipState> useAppIcon=[true|false]") "<deviceName> <chipState> useAppIcon=[true|false] <id>") } } Loading @@ -127,8 +127,10 @@ class MediaTttCommandLineHelper @Inject constructor( @SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager val routeInfo = MediaRoute2Info.Builder("id", "Test Name") .addFeature("feature") val routeInfo = MediaRoute2Info.Builder( if (args.size >= 3) args[2] else "id", "Test Name" ).addFeature("feature") val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false") if (useAppIcon) { routeInfo.setClientPackageName(TEST_PACKAGE_NAME) Loading @@ -144,7 +146,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " + "<chipState> useAppIcon=[true|false]") "<chipState> useAppIcon=[true|false] <id>") } } Loading packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +18 −3 Original line number Diff line number Diff line Loading @@ -121,18 +121,32 @@ class MediaTttChipControllerReceiver @Inject constructor( uiEventLogger.logReceiverStateChange(chipState) if (chipState == ChipStateReceiver.FAR_FROM_SENDER) { removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) return } if (appIcon == null) { displayView(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName)) displayView( ChipReceiverInfo( routeInfo, appIconDrawableOverride = null, appName, id = routeInfo.id, ) ) return } appIcon.loadDrawableAsync( context, Icon.OnDrawableLoadedListener { drawable -> displayView(ChipReceiverInfo(routeInfo, drawable, appName)) displayView( ChipReceiverInfo( routeInfo, drawable, appName, id = routeInfo.id, ) ) }, // Notify the listener on the main handler since the listener will update // the UI. Loading Loading @@ -234,4 +248,5 @@ data class ChipReceiverInfo( val appNameOverride: CharSequence?, override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER, override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER, override val id: String, ) : TemporaryViewInfo() packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +2 −1 Original line number Diff line number Diff line Loading @@ -108,7 +108,7 @@ constructor( } displayedState = null chipbarCoordinator.removeView(removalReason) chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { displayedState = chipState chipbarCoordinator.displayView( Loading Loading @@ -162,6 +162,7 @@ constructor( windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER, wakeReason = MediaTttUtils.WAKE_REASON_SENDER, timeoutMs = chipStateSender.timeout, id = routeInfo.id, ) } Loading packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +69 −9 Original line number Diff line number Diff line Loading @@ -93,6 +93,13 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora /** A string that keeps track of wakelock reason once it is acquired till it gets released */ private var wakeReasonAcquired: String? = null /** * A stack of pairs of device id and temporary view info. This is used when there may be * multiple devices in range, and we want to always display the chip for the most recently * active device. */ internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque() /** * Displays the view with the provided [newInfo]. * Loading @@ -102,6 +109,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora fun displayView(newInfo: T) { 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 Loading @@ -113,7 +126,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // 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(removalReason = "New info has new window title: ${newInfo.windowTitle}") removeView( id, removalReason = "New info has new window title: ${newInfo.windowTitle}" ) } // At this point, we're guaranteed to no longer be displaying a view. Loading @@ -140,7 +156,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } wakeLock?.acquire(newInfo.wakeReason) wakeReasonAcquired = newInfo.wakeReason logger.logViewAddition(newInfo.windowTitle) logger.logViewAddition(id, newInfo.windowTitle) inflateAndUpdateView(newInfo) } Loading @@ -151,9 +167,13 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // include it just to be safe. FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS ) // Only cancel timeout of the most recent view displayed, as it will be reset. if (position == 0) { cancelViewTimeout?.run() } cancelViewTimeout = mainExecutor.executeDelayed( { removeView(REMOVAL_REASON_TIMEOUT) }, { removeView(id, REMOVAL_REASON_TIMEOUT) }, timeout.toLong() ) } Loading Loading @@ -196,28 +216,67 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } /** * Hides the view. * Hides the view given its [id]. * * @param id the id of the device responsible of displaying the temp view. * @param removalReason a short string describing why the view was removed (timeout, state * change, etc.) */ fun removeView(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) val newViewToDisplay = if (activeViews.isEmpty()) { null } else { activeViews[0].second } val currentView = currentDisplayInfo.view animateViewOut(currentView) { windowManager.removeView(currentView) wakeLock?.release(wakeReasonAcquired) } logger.logViewRemoval(removalReason) 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. // that if a new view event comes in while this view is animating out, we still display // the new view appropriately. displayInfo = null // No need to time the view out since it's already gone cancelViewTimeout?.run() if (newViewToDisplay != null) { mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY) } } /** * Finds and removes the active view with the given [id] from the stack, or null if there is no * active view with that ID * * @param id that temporary view belonged to. * * @return index of the view in the stack , otherwise null. */ private fun findAndRemoveFromActiveViewsList(id: String): Int? { for (i in 0 until activeViews.size) { if (activeViews[i].first == id) { activeViews.removeAt(i) return i } } return null } /** Loading Loading @@ -258,6 +317,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT" const val DISPLAY_VIEW_DELAY = 50L private data class IconInfo( val iconName: String, Loading packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt +5 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,11 @@ abstract class TemporaryViewInfo { * disappears. */ open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS /** * The id of the temporary view. */ abstract val id: String } const val DEFAULT_TIMEOUT_MILLIS = 10000 Loading
packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +7 −5 Original line number Diff line number Diff line Loading @@ -61,7 +61,7 @@ class MediaTttCommandLineHelper @Inject constructor( @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager val routeInfo = MediaRoute2Info.Builder("id", args[0]) val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0]) .addFeature("feature") val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false") if (useAppIcon) { Loading Loading @@ -107,7 +107,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " + "<deviceName> <chipState> useAppIcon=[true|false]") "<deviceName> <chipState> useAppIcon=[true|false] <id>") } } Loading @@ -127,8 +127,10 @@ class MediaTttCommandLineHelper @Inject constructor( @SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager val routeInfo = MediaRoute2Info.Builder("id", "Test Name") .addFeature("feature") val routeInfo = MediaRoute2Info.Builder( if (args.size >= 3) args[2] else "id", "Test Name" ).addFeature("feature") val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false") if (useAppIcon) { routeInfo.setClientPackageName(TEST_PACKAGE_NAME) Loading @@ -144,7 +146,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " + "<chipState> useAppIcon=[true|false]") "<chipState> useAppIcon=[true|false] <id>") } } Loading
packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +18 −3 Original line number Diff line number Diff line Loading @@ -121,18 +121,32 @@ class MediaTttChipControllerReceiver @Inject constructor( uiEventLogger.logReceiverStateChange(chipState) if (chipState == ChipStateReceiver.FAR_FROM_SENDER) { removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) return } if (appIcon == null) { displayView(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName)) displayView( ChipReceiverInfo( routeInfo, appIconDrawableOverride = null, appName, id = routeInfo.id, ) ) return } appIcon.loadDrawableAsync( context, Icon.OnDrawableLoadedListener { drawable -> displayView(ChipReceiverInfo(routeInfo, drawable, appName)) displayView( ChipReceiverInfo( routeInfo, drawable, appName, id = routeInfo.id, ) ) }, // Notify the listener on the main handler since the listener will update // the UI. Loading Loading @@ -234,4 +248,5 @@ data class ChipReceiverInfo( val appNameOverride: CharSequence?, override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER, override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER, override val id: String, ) : TemporaryViewInfo()
packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +2 −1 Original line number Diff line number Diff line Loading @@ -108,7 +108,7 @@ constructor( } displayedState = null chipbarCoordinator.removeView(removalReason) chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { displayedState = chipState chipbarCoordinator.displayView( Loading Loading @@ -162,6 +162,7 @@ constructor( windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER, wakeReason = MediaTttUtils.WAKE_REASON_SENDER, timeoutMs = chipStateSender.timeout, id = routeInfo.id, ) } Loading
packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +69 −9 Original line number Diff line number Diff line Loading @@ -93,6 +93,13 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora /** A string that keeps track of wakelock reason once it is acquired till it gets released */ private var wakeReasonAcquired: String? = null /** * A stack of pairs of device id and temporary view info. This is used when there may be * multiple devices in range, and we want to always display the chip for the most recently * active device. */ internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque() /** * Displays the view with the provided [newInfo]. * Loading @@ -102,6 +109,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora fun displayView(newInfo: T) { 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 Loading @@ -113,7 +126,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // 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(removalReason = "New info has new window title: ${newInfo.windowTitle}") removeView( id, removalReason = "New info has new window title: ${newInfo.windowTitle}" ) } // At this point, we're guaranteed to no longer be displaying a view. Loading @@ -140,7 +156,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } wakeLock?.acquire(newInfo.wakeReason) wakeReasonAcquired = newInfo.wakeReason logger.logViewAddition(newInfo.windowTitle) logger.logViewAddition(id, newInfo.windowTitle) inflateAndUpdateView(newInfo) } Loading @@ -151,9 +167,13 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // include it just to be safe. FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS ) // Only cancel timeout of the most recent view displayed, as it will be reset. if (position == 0) { cancelViewTimeout?.run() } cancelViewTimeout = mainExecutor.executeDelayed( { removeView(REMOVAL_REASON_TIMEOUT) }, { removeView(id, REMOVAL_REASON_TIMEOUT) }, timeout.toLong() ) } Loading Loading @@ -196,28 +216,67 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } /** * Hides the view. * Hides the view given its [id]. * * @param id the id of the device responsible of displaying the temp view. * @param removalReason a short string describing why the view was removed (timeout, state * change, etc.) */ fun removeView(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) val newViewToDisplay = if (activeViews.isEmpty()) { null } else { activeViews[0].second } val currentView = currentDisplayInfo.view animateViewOut(currentView) { windowManager.removeView(currentView) wakeLock?.release(wakeReasonAcquired) } logger.logViewRemoval(removalReason) 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. // that if a new view event comes in while this view is animating out, we still display // the new view appropriately. displayInfo = null // No need to time the view out since it's already gone cancelViewTimeout?.run() if (newViewToDisplay != null) { mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY) } } /** * Finds and removes the active view with the given [id] from the stack, or null if there is no * active view with that ID * * @param id that temporary view belonged to. * * @return index of the view in the stack , otherwise null. */ private fun findAndRemoveFromActiveViewsList(id: String): Int? { for (i in 0 until activeViews.size) { if (activeViews[i].first == id) { activeViews.removeAt(i) return i } } return null } /** Loading Loading @@ -258,6 +317,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT" const val DISPLAY_VIEW_DELAY = 50L private data class IconInfo( val iconName: String, Loading
packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt +5 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,11 @@ abstract class TemporaryViewInfo { * disappears. */ open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS /** * The id of the temporary view. */ abstract val id: String } const val DEFAULT_TIMEOUT_MILLIS = 10000