Highlight provided channels

The ChannelEditorDialogController is handed up to 4 channels from
NotificationInfo when presented, and pads out to 4 in the case when it
gets fewer than 4 (the most common case is that it gets 1).

This change tells the controller to play a highlight animation for the
provided channel(s) so that it's more obvious to users which channel
they are editing the preference of.

Test: atest SystemUITests; manual
Bug: 140956160
Change-Id: I48717c7ca6e54259e54f32a5af6acc4c264cc8eb
This commit is contained in:
Evan Laird
2020-04-28 18:20:27 -04:00
parent 18bd6e676d
commit 0fd0654129
3 changed files with 73 additions and 26 deletions

View File

@@ -24,7 +24,6 @@ import android.app.NotificationChannelGroup
import android.app.NotificationManager.IMPORTANCE_NONE
import android.app.NotificationManager.Importance
import android.content.Context
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
@@ -79,9 +78,10 @@ class ChannelEditorDialogController @Inject constructor(
// Caller should set this if they care about when we dismiss
var onFinishListener: OnChannelEditorDialogFinishedListener? = null
// Channels handed to us from NotificationInfo
@VisibleForTesting
internal val providedChannels = mutableListOf<NotificationChannel>()
internal val paddedChannels = mutableListOf<NotificationChannel>()
// Channels handed to us from NotificationInfo
private val providedChannels = mutableListOf<NotificationChannel>()
// Map from NotificationChannel to importance
private val edits = mutableMapOf<NotificationChannel, Int>()
@@ -119,6 +119,8 @@ class ChannelEditorDialogController @Inject constructor(
channelGroupList.clear()
channelGroupList.addAll(fetchNotificationChannelGroups())
buildGroupNameLookup()
providedChannels.clear()
providedChannels.addAll(channels)
padToFourChannels(channels)
initDialog()
@@ -134,21 +136,21 @@ class ChannelEditorDialogController @Inject constructor(
}
private fun padToFourChannels(channels: Set<NotificationChannel>) {
providedChannels.clear()
paddedChannels.clear()
// First, add all of the given channels
providedChannels.addAll(channels.asSequence().take(4))
paddedChannels.addAll(channels.asSequence().take(4))
// Then pad to 4 if we haven't been given that many
providedChannels.addAll(getDisplayableChannels(channelGroupList.asSequence())
.filterNot { providedChannels.contains(it) }
paddedChannels.addAll(getDisplayableChannels(channelGroupList.asSequence())
.filterNot { paddedChannels.contains(it) }
.distinct()
.take(4 - providedChannels.size))
.take(4 - paddedChannels.size))
// If we only got one channel and it has the default miscellaneous tag, then we actually
// are looking at an app with a targetSdk <= O, and it doesn't make much sense to show the
// channel
if (providedChannels.size == 1 && DEFAULT_CHANNEL_ID == providedChannels[0].id) {
providedChannels.clear()
if (paddedChannels.size == 1 && DEFAULT_CHANNEL_ID == paddedChannels[0].id) {
paddedChannels.clear()
}
}
@@ -196,6 +198,7 @@ class ChannelEditorDialogController @Inject constructor(
appNotificationsCurrentlyEnabled = null
edits.clear()
paddedChannels.clear()
providedChannels.clear()
groupNameLookup.clear()
}
@@ -292,16 +295,21 @@ class ChannelEditorDialogController @Inject constructor(
dialog.apply {
setContentView(R.layout.notif_half_shelf)
setCanceledOnTouchOutside(true)
setOnDismissListener(object : DialogInterface.OnDismissListener {
override fun onDismiss(dialog: DialogInterface?) {
onFinishListener?.onChannelEditorDialogFinished()
}
})
findViewById<ChannelEditorListView>(R.id.half_shelf_container)?.apply {
setOnDismissListener { onFinishListener?.onChannelEditorDialogFinished() }
val listView = findViewById<ChannelEditorListView>(R.id.half_shelf_container)
listView?.apply {
controller = this@ChannelEditorDialogController
appIcon = this@ChannelEditorDialogController.appIcon
appName = this@ChannelEditorDialogController.appName
channels = providedChannels
channels = paddedChannels
}
setOnShowListener {
// play a highlight animation for the given channels
for (channel in providedChannels) {
listView?.highlightChannel(channel)
}
}
findViewById<TextView>(R.id.done_button)?.setOnClickListener {

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_NONE
@@ -33,8 +35,10 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Switch
import android.widget.TextView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.util.Assert
/**
* Half-shelf for notification channel controls
@@ -51,6 +55,7 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
// The first row is for the entire app
private lateinit var appControlRow: AppControlView
private val channelRows = mutableListOf<ChannelRow>()
override fun onFinishInflate() {
super.onFinishInflate()
@@ -58,6 +63,19 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
appControlRow = findViewById(R.id.app_control)
}
/**
* Play a highlight animation for the given channel (it really should exist but this will just
* fail silently if it doesn't)
*/
fun highlightChannel(channel: NotificationChannel) {
Assert.isMainThread()
for (row in channelRows) {
if (row.channel == channel) {
row.playHighlight()
}
}
}
private fun updateRows() {
val enabled = controller.areAppNotificationsEnabled()
@@ -83,13 +101,10 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
TransitionManager.beginDelayedTransition(this, transition)
// Remove any rows
val n = childCount
for (i in n.downTo(0)) {
val child = getChildAt(i)
if (child is ChannelRow) {
removeView(child)
}
for (row in channelRows) {
removeView(row)
}
channelRows.clear()
updateAppControlRow(enabled)
@@ -105,6 +120,8 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
val row = inflater.inflate(R.layout.notif_half_shelf_row, null) as ChannelRow
row.controller = controller
row.channel = channel
channelRows.add(row)
addView(row)
}
@@ -140,8 +157,14 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
private lateinit var channelName: TextView
private lateinit var channelDescription: TextView
private lateinit var switch: Switch
private val highlightColor: Int
var gentle = false
init {
highlightColor = Utils.getColorAttrDefaultColor(
context, android.R.attr.colorControlHighlight)
}
var channel: NotificationChannel? = null
set(newValue) {
field = newValue
@@ -162,6 +185,22 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
setOnClickListener { switch.toggle() }
}
/**
* Play an animation that highlights this row
*/
fun playHighlight() {
// Use 0 for the start value because our background is given to us by our parent
val fadeInLoop = ValueAnimator.ofObject(ArgbEvaluator(), 0, highlightColor)
fadeInLoop.duration = 200L
fadeInLoop.addUpdateListener { animator ->
setBackgroundColor(animator.animatedValue as Int)
}
fadeInLoop.repeatMode = ValueAnimator.REVERSE
// Repeat an odd number of times to we end up normal
fadeInLoop.repeatCount = 5
fadeInLoop.start()
}
private fun updateViews() {
val nc = channel ?: return

View File

@@ -93,7 +93,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() {
controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
setOf(channel1, channel2), appIcon, clickListener)
assertEquals(2, controller.providedChannels.size)
assertEquals(2, controller.paddedChannels.size)
}
@Test
@@ -104,7 +104,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() {
setOf(channelDefault), appIcon, clickListener)
assertEquals("No channels should be shown when there is only the miscellaneous channel",
0, controller.providedChannels.size)
0, controller.paddedChannels.size)
}
@Test
@@ -126,7 +126,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() {
setOf(channel1), appIcon, clickListener)
assertEquals("ChannelEditorDialog should fetch enough channels to show 4",
4, controller.providedChannels.size)
4, controller.paddedChannels.size)
}
@Test