diff options
8 files changed, 152 insertions, 52 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt index 5e87f4663d76..61873ad294e3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row.ui.viewmodel +import android.app.Notification import android.app.PendingIntent import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -90,7 +91,8 @@ class TimerViewModelTest : SysuiTestCase() { name: String = "example", timeRemaining: Duration = Duration.ofMinutes(3), resumeIntent: PendingIntent? = null, - resetIntent: PendingIntent? = null + addMinuteAction: Notification.Action? = null, + resetAction: Notification.Action? = null ) = TimerContentModel( icon = icon, @@ -99,7 +101,8 @@ class TimerViewModelTest : SysuiTestCase() { Paused( timeRemaining = timeRemaining, resumeIntent = resumeIntent, - resetIntent = resetIntent, + addMinuteAction = addMinuteAction, + resetAction = resetAction, ) ) } diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml index f2bfbe5c960d..3a679e3c16cb 100644 --- a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml +++ b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml @@ -33,7 +33,6 @@ android:id="@+id/icon" android:layout_width="24dp" android:layout_height="24dp" - android:src="@drawable/ic_close" app:tint="@android:color/white" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/label" @@ -88,11 +87,10 @@ /> <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView + style="@*android:style/NotificationEmphasizedAction" android:id="@+id/mainButton" android:layout_width="124dp" android:layout_height="wrap_content" - tools:text="Reset" - tools:drawableStart="@android:drawable/ic_menu_add" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/altButton" app:layout_constraintTop_toBottomOf="@id/bottomOfTop" @@ -101,15 +99,23 @@ /> <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView + style="@*android:style/NotificationEmphasizedAction" android:id="@+id/altButton" - tools:text="Reset" - tools:drawableStart="@android:drawable/ic_menu_add" - android:drawablePadding="2dp" - android:drawableTint="@android:color/white" android:layout_width="124dp" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/bottomOfTop" app:layout_constraintStart_toEndOf="@id/mainButton" + app:layout_constraintEnd_toEndOf="@id/resetButton" + android:paddingEnd="4dp" + /> + + <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView + style="@*android:style/NotificationEmphasizedAction" + android:id="@+id/resetButton" + android:layout_width="124dp" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/bottomOfTop" + app:layout_constraintStart_toEndOf="@id/altButton" app:layout_constraintEnd_toEndOf="parent" android:paddingEnd="4dp" /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt index b5ea861c19a6..bf5b3a34afb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt @@ -118,12 +118,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : val timeRemaining = parseTimeDelta(remaining) TimerContentModel( icon = icon, - name = total, + // TODO: b/352142761 - define and use a string resource rather than " Timer". + // (The UX isn't final so using " Timer" for now). + name = total.replace("Σ", "") + " Timer", state = TimerContentModel.TimerState.Paused( timeRemaining = timeRemaining, - resumeIntent = notification.findActionWithName("Resume"), - resetIntent = notification.findActionWithName("Reset"), + resumeIntent = notification.findStartIntent(), + addMinuteAction = notification.findAddMinuteAction(), + resetAction = notification.findResetAction(), ) ) } @@ -132,12 +135,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis() TimerContentModel( icon = icon, - name = total, + // TODO: b/352142761 - define and use a string resource rather than " Timer". + // (The UX isn't final so using " Timer" for now). + name = total.replace("Σ", "") + " Timer", state = TimerContentModel.TimerState.Running( finishTime = finishTime, - pauseIntent = notification.findActionWithName("Pause"), - addOneMinuteIntent = notification.findActionWithName("Add 1 min"), + pauseIntent = notification.findPauseIntent(), + addMinuteAction = notification.findAddMinuteAction(), + resetAction = notification.findResetAction(), ) ) } @@ -145,8 +151,34 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : } } - private fun Notification.findActionWithName(name: String): PendingIntent? { - return actions.firstOrNull { name == it.title?.toString() }?.actionIntent + private fun Notification.findPauseIntent(): PendingIntent? { + return actions + .firstOrNull { it.actionIntent.intent?.action?.endsWith(".PAUSE_TIMER") == true } + ?.actionIntent + } + + private fun Notification.findStartIntent(): PendingIntent? { + return actions + .firstOrNull { it.actionIntent.intent?.action?.endsWith(".START_TIMER") == true } + ?.actionIntent + } + + // TODO: b/352142761 - switch to system attributes for label and icon. + // - We probably want a consistent look for the Reset button. (Double check with UX.) + // - Using the custom assets now since I couldn't an existing "Reset" icon. + private fun Notification.findResetAction(): Notification.Action? { + return actions.firstOrNull { + it.actionIntent.intent?.action?.endsWith(".RESET_TIMER") == true + } + } + + // TODO: b/352142761 - check with UX on whether this should be required. + // - Alternative is to allow for optional actions in addition to main and reset. + // - For optional actions, we should take the custom label and icon. + private fun Notification.findAddMinuteAction(): Notification.Action? { + return actions.firstOrNull { + it.actionIntent.intent?.action?.endsWith(".ADD_MINUTE_TIMER") == true + } } private fun parseCurrentTime(current: String): Long { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt index 558470175e8d..33b256456ca3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.shared +import android.app.Notification import android.app.PendingIntent import java.time.Duration @@ -32,6 +33,9 @@ data class TimerContentModel( ) : RichOngoingContentModel { /** The state (paused or running) of the timer, and relevant time */ sealed interface TimerState { + val addMinuteAction: Notification.Action? + val resetAction: Notification.Action? + /** * Indicates a running timer * @@ -41,7 +45,8 @@ data class TimerContentModel( data class Running( val finishTime: Long, val pauseIntent: PendingIntent?, - val addOneMinuteIntent: PendingIntent?, + override val addMinuteAction: Notification.Action?, + override val resetAction: Notification.Action?, ) : TimerState /** @@ -53,7 +58,8 @@ data class TimerContentModel( data class Paused( val timeRemaining: Duration, val resumeIntent: PendingIntent?, - val resetIntent: PendingIntent?, + override val addMinuteAction: Notification.Action?, + override val resetAction: Notification.Action?, ) : TimerState } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt index 0d83aced6d07..8c951877544c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt @@ -18,8 +18,9 @@ package com.android.systemui.statusbar.notification.row.ui.view import android.annotation.DrawableRes import android.content.Context +import android.graphics.BlendMode import android.util.AttributeSet -import android.widget.Button +import com.android.internal.widget.EmphasizedNotificationButton class TimerButtonView @JvmOverloads @@ -28,14 +29,19 @@ constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0, -) : Button(context, attrs, defStyleAttr, defStyleRes) { +) : EmphasizedNotificationButton(context, attrs, defStyleAttr, defStyleRes) { private val Int.dp: Int get() = (this * context.resources.displayMetrics.density).toInt() fun setIcon(@DrawableRes icon: Int) { val drawable = context.getDrawable(icon) + + drawable?.mutate() + drawable?.setTintList(textColors) + drawable?.setTintBlendMode(BlendMode.SRC_IN) drawable?.setBounds(0, 0, 24.dp, 24.dp) + setCompoundDrawablesRelative(drawable, null, null, null) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt index 2e164d60431d..d481b50101c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.row.ui.view import android.content.Context -import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon import android.os.SystemClock import android.util.AttributeSet import android.widget.Chronometer @@ -48,6 +48,9 @@ constructor( lateinit var altButton: TimerButtonView private set + lateinit var resetButton: TimerButtonView + private set + override fun onFinishInflate() { super.onFinishInflate() icon = requireViewById(R.id.icon) @@ -56,13 +59,14 @@ constructor( pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining) mainButton = requireViewById(R.id.mainButton) altButton = requireViewById(R.id.altButton) + resetButton = requireViewById(R.id.resetButton) } /** the resources configuration has changed such that the view needs to be reinflated */ fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange() - fun setIcon(iconDrawable: Drawable?) { - this.icon.setImageDrawable(iconDrawable) + fun setIcon(icon: Icon?) { + this.icon.setImageIcon(icon) } fun setLabel(label: String) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt index c9ff58961582..042d1bcfb2ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row.ui.viewbinder +import android.content.res.ColorStateList +import android.graphics.drawable.Icon import android.view.View import androidx.core.view.isGone import androidx.lifecycle.lifecycleScope @@ -46,12 +48,43 @@ object TimerViewBinder { launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } } launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } } launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } } + launch { viewModel.resetButtonModel.collect { bind(view.resetButton, it) } } } fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) { if (model != null) { - buttonView.setIcon(model.iconRes) - buttonView.setText(model.labelRes) + buttonView.setButtonBackground( + ColorStateList.valueOf( + buttonView.context.getColor(com.android.internal.R.color.system_accent2_100) + ) + ) + buttonView.setTextColor( + buttonView.context.getColor( + com.android.internal.R.color.notification_primary_text_color_light + ) + ) + + when (model) { + is TimerViewModel.ButtonViewModel.WithSystemAttrs -> { + buttonView.setIcon(model.iconRes) + buttonView.setText(model.labelRes) + } + is TimerViewModel.ButtonViewModel.WithCustomAttrs -> { + // TODO: b/352142761 - is there a better way to deal with TYPE_RESOURCE icons + // with empty resPackage? RemoteViews handles this by using a different + // `contextForResources` for inflation. + val icon = + if (model.icon.type == Icon.TYPE_RESOURCE && model.icon.resPackage == "") + Icon.createWithResource( + "com.google.android.deskclock", + model.icon.resId + ) + else model.icon + buttonView.setImageIcon(icon) + buttonView.text = model.label + } + } + buttonView.setOnClickListener( model.pendingIntent?.let { pendingIntent -> View.OnClickListener { pendingIntent.send() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt index a85c87f288d3..768a093e0b65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.row.ui.viewmodel import android.annotation.DrawableRes import android.annotation.StringRes import android.app.PendingIntent -import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag @@ -44,7 +44,7 @@ constructor( private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state } - val icon: Flow<Drawable?> = rowInteractor.timerContentModel.mapNotNull { it.icon.drawable } + val icon: Flow<Icon?> = rowInteractor.timerContentModel.mapNotNull { it.icon.icon } val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name } @@ -57,13 +57,13 @@ constructor( state.map { when (it) { is TimerState.Paused -> - ButtonViewModel( + ButtonViewModel.WithSystemAttrs( it.resumeIntent, com.android.systemui.res.R.string.controls_media_resume, // "Resume", com.android.systemui.res.R.drawable.ic_media_play ) is TimerState.Running -> - ButtonViewModel( + ButtonViewModel.WithSystemAttrs( it.pauseIntent, com.android.systemui.res.R.string.controls_media_button_pause, // "Pause", com.android.systemui.res.R.drawable.ic_media_pause @@ -73,31 +73,41 @@ constructor( val altButtonModel: Flow<ButtonViewModel?> = state.map { - when (it) { - is TimerState.Paused -> - it.resetIntent?.let { resetIntent -> - ButtonViewModel( - resetIntent, - com.android.systemui.res.R.string.reset, // "Reset", - com.android.systemui.res.R.drawable.ic_close_white_rounded - ) - } - is TimerState.Running -> - it.addOneMinuteIntent?.let { addOneMinuteIntent -> - ButtonViewModel( - addOneMinuteIntent, - com.android.systemui.res.R.string.add, // "Add 1 minute", - com.android.systemui.res.R.drawable.ic_add - ) - } + it.addMinuteAction?.let { action -> + ButtonViewModel.WithCustomAttrs( + action.actionIntent, + action.title, // "1:00", + action.getIcon() + ) + } + } + + val resetButtonModel: Flow<ButtonViewModel?> = + state.map { + it.resetAction?.let { action -> + ButtonViewModel.WithCustomAttrs( + action.actionIntent, + action.title, // "Reset", + action.getIcon() + ) } } - data class ButtonViewModel( - val pendingIntent: PendingIntent?, - @StringRes val labelRes: Int, - @DrawableRes val iconRes: Int, - ) + sealed interface ButtonViewModel { + val pendingIntent: PendingIntent? + + data class WithSystemAttrs( + override val pendingIntent: PendingIntent?, + @StringRes val labelRes: Int, + @DrawableRes val iconRes: Int, + ) : ButtonViewModel + + data class WithCustomAttrs( + override val pendingIntent: PendingIntent?, + val label: CharSequence, + val icon: Icon, + ) : ButtonViewModel + } } private fun Duration.format(): String { |