Merge changes I3c1f0ebb,I6bf500c5,I86ff54f9,Ia2441b38 into tm-qpr-dev

* changes:
  [Chipbar] Add logs at the exact time WindowManager calls are made.
  [Media TTT] Align the timeout with heads up notifications.
  [Chipbar] Update the colors to make the chipbar stand out more.
  [Chipbar] Re-name the dimens to be chipbar instead of media_ttt.
This commit is contained in:
TreeHugger Robot
2023-03-12 22:54:01 +00:00
committed by Android (Google) Code Review
10 changed files with 208 additions and 45 deletions

View File

@@ -17,6 +17,6 @@
<shape <shape
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<solid android:color="?androidprv:attr/colorSurface" /> <solid android:color="?androidprv:attr/colorAccentSecondary" />
<corners android:radius="32dp" /> <corners android:radius="32dp" />
</shape> </shape>

View File

@@ -20,7 +20,7 @@
android:color="?android:textColorPrimary"> android:color="?android:textColorPrimary">
<item android:id="@android:id/background"> <item android:id="@android:id/background">
<shape> <shape>
<solid android:color="?androidprv:attr/colorAccentPrimary"/> <solid android:color="@android:color/system_accent1_200"/>
<corners android:radius="24dp" /> <corners android:radius="24dp" />
</shape> </shape>
</item> </item>

View File

@@ -29,8 +29,8 @@
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="@dimen/media_ttt_chip_outer_padding" android:padding="@dimen/chipbar_outer_padding"
android:background="@drawable/media_ttt_chip_background" android:background="@drawable/chipbar_background"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_marginStart="@dimen/notification_side_paddings" android:layout_marginStart="@dimen/notification_side_paddings"
android:layout_marginEnd="@dimen/notification_side_paddings" android:layout_marginEnd="@dimen/notification_side_paddings"
@@ -43,8 +43,8 @@
<com.android.internal.widget.CachingIconView <com.android.internal.widget.CachingIconView
android:id="@+id/start_icon" android:id="@+id/start_icon"
android:layout_width="@dimen/media_ttt_app_icon_size" android:layout_width="@dimen/chipbar_start_icon_size"
android:layout_height="@dimen/media_ttt_app_icon_size" android:layout_height="@dimen/chipbar_start_icon_size"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:alpha="0.0" android:alpha="0.0"
/> />
@@ -54,47 +54,46 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:textSize="@dimen/media_ttt_text_size" android:textSize="@dimen/chipbar_text_size"
android:textColor="?android:attr/textColorPrimary" android:textColor="@android:color/system_accent2_900"
android:alpha="0.0" android:alpha="0.0"
/> />
<!-- At most one of [loading, failure_icon, undo] will be visible at a time. --> <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
<ImageView <ImageView
android:id="@+id/loading" android:id="@+id/loading"
android:layout_width="@dimen/media_ttt_status_icon_size" android:layout_width="@dimen/chipbar_end_icon_size"
android:layout_height="@dimen/media_ttt_status_icon_size" android:layout_height="@dimen/chipbar_end_icon_size"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
android:src="@drawable/ic_progress_activity" android:src="@drawable/ic_progress_activity"
android:tint="?androidprv:attr/colorAccentPrimaryVariant" android:tint="@android:color/system_accent2_700"
android:alpha="0.0" android:alpha="0.0"
/> />
<ImageView <ImageView
android:id="@+id/error" android:id="@+id/error"
android:layout_width="@dimen/media_ttt_status_icon_size" android:layout_width="@dimen/chipbar_end_icon_size"
android:layout_height="@dimen/media_ttt_status_icon_size" android:layout_height="@dimen/chipbar_end_icon_size"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
android:src="@drawable/ic_warning" android:src="@drawable/ic_warning"
android:tint="@color/GM2_red_500" android:tint="@color/GM2_red_600"
android:alpha="0.0" android:alpha="0.0"
/> />
<!-- TODO(b/245610654): Re-name all the media-specific dimens to chipbar dimens instead. -->
<TextView <TextView
android:id="@+id/end_button" android:id="@+id/end_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?androidprv:attr/textColorOnAccent" android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
android:textSize="@dimen/media_ttt_text_size" android:textSize="@dimen/chipbar_text_size"
android:paddingStart="@dimen/media_ttt_chip_outer_padding" android:paddingStart="@dimen/chipbar_outer_padding"
android:paddingEnd="@dimen/media_ttt_chip_outer_padding" android:paddingEnd="@dimen/chipbar_outer_padding"
android:paddingTop="@dimen/media_ttt_undo_button_vertical_padding" android:paddingTop="@dimen/chipbar_end_button_vertical_padding"
android:paddingBottom="@dimen/media_ttt_undo_button_vertical_padding" android:paddingBottom="@dimen/chipbar_end_button_vertical_padding"
android:layout_marginTop="@dimen/media_ttt_undo_button_vertical_negative_margin" android:layout_marginTop="@dimen/chipbar_end_button_vertical_negative_margin"
android:layout_marginBottom="@dimen/media_ttt_undo_button_vertical_negative_margin" android:layout_marginBottom="@dimen/chipbar_end_button_vertical_negative_margin"
android:background="@drawable/media_ttt_undo_background" android:background="@drawable/chipbar_end_button_background"
android:alpha="0.0" android:alpha="0.0"
/> />

View File

@@ -161,6 +161,7 @@
<color name="GM2_red_300">#F28B82</color> <color name="GM2_red_300">#F28B82</color>
<color name="GM2_red_500">#EA4335</color> <color name="GM2_red_500">#EA4335</color>
<color name="GM2_red_600">#B3261E</color>
<color name="GM2_red_700">#C5221F</color> <color name="GM2_red_700">#C5221F</color>
<color name="GM2_blue_300">#8AB4F8</color> <color name="GM2_blue_300">#8AB4F8</color>

View File

@@ -1111,14 +1111,15 @@
<dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen> <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
<dimen name="qs_media_rec_album_subtitle_height">12dp</dimen> <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
<!-- Media tap-to-transfer chip for sender device --> <!-- Chipbar -->
<dimen name="media_ttt_chip_outer_padding">16dp</dimen> <!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
<dimen name="media_ttt_text_size">16sp</dimen> <dimen name="chipbar_outer_padding">16dp</dimen>
<dimen name="media_ttt_app_icon_size">24dp</dimen> <dimen name="chipbar_text_size">16sp</dimen>
<dimen name="media_ttt_status_icon_size">20dp</dimen> <dimen name="chipbar_start_icon_size">24dp</dimen>
<dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen> <dimen name="chipbar_end_icon_size">20dp</dimen>
<dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen> <dimen name="chipbar_end_button_vertical_padding">8dp</dimen>
<dimen name="media_ttt_last_item_start_margin">12dp</dimen> <dimen name="chipbar_end_button_vertical_negative_margin">-8dp</dimen>
<dimen name="chipbar_end_item_start_margin">12dp</dimen>
<!-- Media tap-to-transfer chip for receiver device --> <!-- Media tap-to-transfer chip for receiver device -->
<dimen name="media_ttt_icon_size_receiver">112dp</dimen> <dimen name="media_ttt_icon_size_receiver">112dp</dimen>

View File

@@ -23,7 +23,6 @@ import androidx.annotation.StringRes
import com.android.internal.logging.UiEventLogger import com.android.internal.logging.UiEventLogger
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.Text
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
/** /**
* A class enumerating all the possible states of the media tap-to-transfer chip on the sender * A class enumerating all the possible states of the media tap-to-transfer chip on the sender
@@ -34,8 +33,8 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
* state should not have the chip be displayed. * state should not have the chip be displayed.
* @property transferStatus the transfer status that the chip state represents. * @property transferStatus the transfer status that the chip state represents.
* @property endItem the item that should be displayed in the end section of the chip. * @property endItem the item that should be displayed in the end section of the chip.
* @property timeout the amount of time this chip should display on the screen before it times out * @property timeoutLength how long the chip should display on the screen before it times out and
* and disappears. * disappears.
*/ */
enum class ChipStateSender( enum class ChipStateSender(
@StatusBarManager.MediaTransferSenderState val stateInt: Int, @StatusBarManager.MediaTransferSenderState val stateInt: Int,
@@ -43,7 +42,7 @@ enum class ChipStateSender(
@StringRes val stringResId: Int?, @StringRes val stringResId: Int?,
val transferStatus: TransferStatus, val transferStatus: TransferStatus,
val endItem: SenderEndItem?, val endItem: SenderEndItem?,
val timeout: Int = DEFAULT_TIMEOUT_MILLIS, val timeoutLength: TimeoutLength = TimeoutLength.DEFAULT,
) { ) {
/** /**
* A state representing that the two devices are close but not close enough to *start* a cast to * A state representing that the two devices are close but not close enough to *start* a cast to
@@ -56,6 +55,9 @@ enum class ChipStateSender(
R.string.media_move_closer_to_start_cast, R.string.media_move_closer_to_start_cast,
transferStatus = TransferStatus.NOT_STARTED, transferStatus = TransferStatus.NOT_STARTED,
endItem = null, endItem = null,
// Give this view more time in case the loading view takes a bit to come in. (We don't want
// this view to disappear and then the loading view to appear quickly afterwards.)
timeoutLength = TimeoutLength.LONG,
) { ) {
override fun isValidNextState(nextState: ChipStateSender): Boolean { override fun isValidNextState(nextState: ChipStateSender): Boolean {
return nextState == FAR_FROM_RECEIVER || return nextState == FAR_FROM_RECEIVER ||
@@ -75,6 +77,7 @@ enum class ChipStateSender(
R.string.media_move_closer_to_end_cast, R.string.media_move_closer_to_end_cast,
transferStatus = TransferStatus.NOT_STARTED, transferStatus = TransferStatus.NOT_STARTED,
endItem = null, endItem = null,
timeoutLength = TimeoutLength.LONG,
) { ) {
override fun isValidNextState(nextState: ChipStateSender): Boolean { override fun isValidNextState(nextState: ChipStateSender): Boolean {
return nextState == FAR_FROM_RECEIVER || return nextState == FAR_FROM_RECEIVER ||
@@ -92,7 +95,9 @@ enum class ChipStateSender(
R.string.media_transfer_playing_different_device, R.string.media_transfer_playing_different_device,
transferStatus = TransferStatus.IN_PROGRESS, transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading, endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS // Give this view more time in case the succeeded/failed view takes a bit to come in. (We
// don't want this view to disappear and then the next view to appear quickly afterwards.)
timeoutLength = TimeoutLength.LONG,
) { ) {
override fun isValidNextState(nextState: ChipStateSender): Boolean { override fun isValidNextState(nextState: ChipStateSender): Boolean {
return nextState == FAR_FROM_RECEIVER || return nextState == FAR_FROM_RECEIVER ||
@@ -111,7 +116,7 @@ enum class ChipStateSender(
R.string.media_transfer_playing_this_device, R.string.media_transfer_playing_this_device,
transferStatus = TransferStatus.IN_PROGRESS, transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading, endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS timeoutLength = TimeoutLength.LONG,
) { ) {
override fun isValidNextState(nextState: ChipStateSender): Boolean { override fun isValidNextState(nextState: ChipStateSender): Boolean {
return nextState == FAR_FROM_RECEIVER || return nextState == FAR_FROM_RECEIVER ||
@@ -325,9 +330,16 @@ sealed class SenderEndItem {
) : SenderEndItem() ) : SenderEndItem()
} }
// Give the Transfer*Triggered states a longer timeout since those states represent an active /** Represents how long the chip should be visible before it times out. */
// process and we should keep the user informed about it as long as possible (but don't allow it to enum class TimeoutLength {
// continue indefinitely). /** A default timeout used for temporary displays at the top of the screen. */
private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000 DEFAULT,
/**
* A longer timeout. Should be used when the status is pending (e.g. loading), so that the user
* remains informed about the process for longer and so that the UI has more time to resolve the
* pending state before disappearing.
*/
LONG,
}
private const val TAG = "ChipStateSender" private const val TAG = "ChipStateSender"

View File

@@ -56,6 +56,9 @@ constructor(
private val uiEventLogger: MediaTttSenderUiEventLogger, private val uiEventLogger: MediaTttSenderUiEventLogger,
) : CoreStartable, Dumpable { ) : CoreStartable, Dumpable {
// Since the media transfer display is similar to a heads-up notification, use the same timeout.
private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
// A map to store current chip state per id. // A map to store current chip state per id.
private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf() private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
@@ -165,6 +168,12 @@ constructor(
logger.logPackageNotFound(packageName) logger.logPackageNotFound(packageName)
} }
val timeout =
when (chipStateSender.timeoutLength) {
TimeoutLength.DEFAULT -> defaultTimeout
TimeoutLength.LONG -> 2 * defaultTimeout
}
return ChipbarInfo( return ChipbarInfo(
// Display the app's icon as the start icon // Display the app's icon as the start icon
startIcon = icon.toTintedIcon(), startIcon = icon.toTintedIcon(),
@@ -191,7 +200,7 @@ constructor(
allowSwipeToDismiss = true, allowSwipeToDismiss = true,
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER, windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
wakeReason = MediaTttUtils.WAKE_REASON_SENDER, wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout, timeoutMs = timeout,
id = routeInfo.id, id = routeInfo.id,
priority = ViewPriority.NORMAL, priority = ViewPriority.NORMAL,
) )

View File

@@ -274,6 +274,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
it.title = newInfo.windowTitle it.title = newInfo.windowTitle
} }
newView.keepScreenOn = true newView.keepScreenOn = true
logger.logViewAddedToWindowManager(displayInfo.info, newView)
windowManager.addView(newView, paramsWithTitle) windowManager.addView(newView, paramsWithTitle)
animateViewIn(newView) animateViewIn(newView)
} }
@@ -286,6 +287,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
val view = checkNotNull(currentDisplayInfo.view) { val view = checkNotNull(currentDisplayInfo.view) {
"First item in activeViews list must have a valid view" "First item in activeViews list must have a valid view"
} }
logger.logViewRemovedFromWindowManager(
currentDisplayInfo.info,
view,
isReinflation = true,
)
windowManager.removeView(view) windowManager.removeView(view)
inflateAndUpdateView(currentDisplayInfo) inflateAndUpdateView(currentDisplayInfo)
} }
@@ -294,6 +300,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
override fun onDensityOrFontScaleChanged() { override fun onDensityOrFontScaleChanged() {
reinflateView() reinflateView()
} }
override fun onThemeChanged() {
reinflateView()
}
} }
private fun addCallbacks() { private fun addCallbacks() {
@@ -378,6 +388,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
} }
displayInfo.view = null // Need other places?? displayInfo.view = null // Need other places??
animateViewOut(view, removalReason) { animateViewOut(view, removalReason) {
logger.logViewRemovedFromWindowManager(displayInfo.info, view)
windowManager.removeView(view) windowManager.removeView(view)
displayInfo.wakeLock?.release(displayInfo.info.wakeReason) displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
} }

View File

@@ -16,6 +16,7 @@
package com.android.systemui.temporarydisplay package com.android.systemui.temporarydisplay
import android.view.View
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
@@ -141,4 +142,46 @@ open class TemporaryViewLogger<T : TemporaryViewInfo>(
{ "Removal of view with id=$str2 is ignored because $str1" } { "Removal of view with id=$str2 is ignored because $str1" }
) )
} }
fun logViewAddedToWindowManager(info: T, view: View) {
buffer.log(
tag,
LogLevel.DEBUG,
{
str1 = info.id
str2 = info.windowTitle
str3 = view.javaClass.name
int1 = view.getIdForLogging()
},
{
"Adding view to window manager. " +
"id=$str1 window=$str2 view=$str3(id=${Integer.toHexString(int1)})"
}
)
}
fun logViewRemovedFromWindowManager(info: T, view: View, isReinflation: Boolean = false) {
buffer.log(
tag,
LogLevel.DEBUG,
{
str1 = info.id
str2 = info.windowTitle
str3 = view.javaClass.name
int1 = view.getIdForLogging()
bool1 = isReinflation
},
{
"Removing view from window manager${if (bool1) " due to reinflation" else ""}. " +
"id=$str1 window=$str2 view=$str3(id=${Integer.toHexString(int1)})"
}
)
}
companion object {
private fun View.getIdForLogging(): Int {
// The identityHashCode is guaranteed to be constant for the lifetime of the object.
return System.identityHashCode(this)
}
}
} }

View File

@@ -107,6 +107,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
private lateinit var fakeExecutor: FakeExecutor private lateinit var fakeExecutor: FakeExecutor
private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var uiEventLogger: MediaTttSenderUiEventLogger private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
@Before @Before
fun setUp() { fun setUp() {
@@ -1356,6 +1357,92 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
} }
@Test
fun almostClose_hasLongTimeout_eventuallyTimesOut() {
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
it.arguments[0]
}
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
routeInfo,
null,
)
// WHEN the default timeout has passed
fakeClock.advanceTime(defaultTimeout + 1L)
// THEN the view is still on-screen because it has a long timeout
verify(windowManager, never()).removeView(any())
// WHEN a very long amount of time has passed
fakeClock.advanceTime(5L * defaultTimeout)
// THEN the view does time out
verify(windowManager).removeView(any())
}
@Test
fun loading_hasLongTimeout_eventuallyTimesOut() {
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
it.arguments[0]
}
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
routeInfo,
null,
)
// WHEN the default timeout has passed
fakeClock.advanceTime(defaultTimeout + 1L)
// THEN the view is still on-screen because it has a long timeout
verify(windowManager, never()).removeView(any())
// WHEN a very long amount of time has passed
fakeClock.advanceTime(5L * defaultTimeout)
// THEN the view does time out
verify(windowManager).removeView(any())
}
@Test
fun succeeded_hasDefaultTimeout() {
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
it.arguments[0]
}
displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
null,
)
fakeClock.advanceTime(defaultTimeout + 1L)
verify(windowManager).removeView(any())
}
@Test
fun failed_hasDefaultTimeout() {
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
it.arguments[0]
}
displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
routeInfo,
null,
)
fakeClock.advanceTime(defaultTimeout + 1L)
verify(windowManager).removeView(any())
}
private fun getChipbarView(): ViewGroup { private fun getChipbarView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java) val viewCaptor = ArgumentCaptor.forClass(View::class.java)
verify(windowManager).addView(viewCaptor.capture(), any()) verify(windowManager).addView(viewCaptor.capture(), any())