1 /* <lambda>null2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar.notification.row 18 19 import android.animation.ArgbEvaluator 20 import android.animation.ValueAnimator 21 import android.app.NotificationChannel 22 import android.app.NotificationManager.IMPORTANCE_DEFAULT 23 import android.app.NotificationManager.IMPORTANCE_LOW 24 import android.app.NotificationManager.IMPORTANCE_NONE 25 import android.app.NotificationManager.IMPORTANCE_UNSPECIFIED 26 import android.content.Context 27 import android.graphics.drawable.Drawable 28 import android.text.TextUtils.isEmpty 29 import android.transition.AutoTransition 30 import android.transition.Transition 31 import android.transition.TransitionManager 32 import android.util.AttributeSet 33 import android.view.LayoutInflater 34 import android.view.View 35 import android.widget.ImageView 36 import android.widget.LinearLayout 37 import android.widget.TextView 38 import com.android.settingslib.Utils 39 import com.android.systemui.res.R 40 import com.android.systemui.util.Assert 41 import com.google.android.material.materialswitch.MaterialSwitch 42 43 /** Half-shelf for notification channel controls */ 44 class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { 45 lateinit var controller: ChannelEditorDialogController 46 var appIcon: Drawable? = null 47 var appName: String? = null 48 var channels = mutableListOf<NotificationChannel>() 49 set(newValue) { 50 field = newValue 51 updateRows() 52 } 53 54 // The first row is for the entire app 55 private lateinit var appControlRow: AppControlView 56 private lateinit var channelListView: LinearLayout 57 private val channelRows = mutableListOf<ChannelRow>() 58 59 override fun onFinishInflate() { 60 super.onFinishInflate() 61 62 appControlRow = requireViewById(R.id.app_control) 63 channelListView = requireViewById(R.id.scrollView) 64 } 65 66 /** 67 * Play a highlight animation for the given channel (it really should exist but this will just 68 * fail silently if it doesn't) 69 */ 70 fun highlightChannel(channel: NotificationChannel) { 71 Assert.isMainThread() 72 for (row in channelRows) { 73 if (row.channel == channel) { 74 row.playHighlight() 75 } 76 } 77 } 78 79 private fun updateRows() { 80 val enabled = controller.areAppNotificationsEnabled() 81 82 val transition = AutoTransition() 83 transition.duration = 200 84 transition.addListener( 85 object : Transition.TransitionListener { 86 override fun onTransitionEnd(p0: Transition?) { 87 notifySubtreeAccessibilityStateChangedIfNeeded() 88 } 89 90 override fun onTransitionResume(p0: Transition?) {} 91 92 override fun onTransitionPause(p0: Transition?) {} 93 94 override fun onTransitionCancel(p0: Transition?) {} 95 96 override fun onTransitionStart(p0: Transition?) {} 97 } 98 ) 99 TransitionManager.beginDelayedTransition(this, transition) 100 101 // Remove any rows 102 for (row in channelRows) { 103 channelListView.removeView(row) 104 } 105 channelRows.clear() 106 107 updateAppControlRow(enabled) 108 109 if (enabled) { 110 val inflater = LayoutInflater.from(context) 111 for (channel in channels) { 112 addChannelRow(channel, inflater) 113 } 114 } 115 } 116 117 private fun addChannelRow(channel: NotificationChannel, inflater: LayoutInflater) { 118 val row = inflater.inflate(R.layout.notif_half_shelf_row, null) as ChannelRow 119 row.controller = controller 120 row.channel = channel 121 122 channelRows.add(row) 123 channelListView.addView(row) 124 } 125 126 private fun updateAppControlRow(enabled: Boolean) { 127 appControlRow.iconView.setImageDrawable(appIcon) 128 val title = context.resources.getString(R.string.notification_channel_dialog_title, appName) 129 appControlRow.channelName.text = title 130 appControlRow.switch.contentDescription = title 131 appControlRow.switch.isChecked = enabled 132 appControlRow.switch.setOnCheckedChangeListener { _, b -> 133 controller.proposeSetAppNotificationsEnabled(b) 134 updateRows() 135 } 136 } 137 } 138 139 class AppControlView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { 140 lateinit var iconView: ImageView 141 lateinit var channelName: TextView 142 lateinit var switch: MaterialSwitch 143 onFinishInflatenull144 override fun onFinishInflate() { 145 iconView = requireViewById(R.id.icon) 146 channelName = requireViewById(R.id.app_name) 147 switch = requireViewById(R.id.material_toggle) 148 149 setOnClickListener { switch.toggle() } 150 } 151 } 152 153 class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { 154 155 lateinit var controller: ChannelEditorDialogController 156 private lateinit var channelName: TextView 157 private lateinit var channelDescription: TextView 158 private lateinit var switch: MaterialSwitch 159 private val highlightColor: Int 160 var gentle = false 161 162 init { 163 highlightColor = 164 Utils.getColorAttrDefaultColor(context, android.R.attr.colorControlHighlight) 165 } 166 167 var channel: NotificationChannel? = null 168 set(newValue) { 169 field = newValue 170 updateImportance() 171 updateViews() 172 } 173 onFinishInflatenull174 override fun onFinishInflate() { 175 super.onFinishInflate() 176 channelName = requireViewById(R.id.channel_name) 177 channelDescription = requireViewById(R.id.channel_description) 178 switch = requireViewById(R.id.material_toggle) 179 switch.setOnCheckedChangeListener { _, b -> 180 channel?.let { 181 controller.proposeEditForChannel( 182 it, 183 if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW) else IMPORTANCE_NONE, 184 ) 185 } 186 } 187 setOnClickListener { switch.toggle() } 188 } 189 190 /** Play an animation that highlights this row */ playHighlightnull191 fun playHighlight() { 192 // Use 0 for the start value because our background is given to us by our parent 193 val fadeInLoop = ValueAnimator.ofObject(ArgbEvaluator(), 0, highlightColor) 194 fadeInLoop.duration = 200L 195 fadeInLoop.addUpdateListener { animator -> 196 setBackgroundColor(animator.animatedValue as Int) 197 } 198 fadeInLoop.repeatMode = ValueAnimator.REVERSE 199 // Repeat an odd number of times to we end up normal 200 fadeInLoop.repeatCount = 5 201 fadeInLoop.start() 202 } 203 updateViewsnull204 private fun updateViews() { 205 val nc = channel ?: return 206 207 channelName.text = nc.name ?: "" 208 209 nc.group?.let { groupId -> channelDescription.text = controller.groupNameForId(groupId) } 210 211 if (nc.group == null || isEmpty(channelDescription.text)) { 212 channelDescription.visibility = View.GONE 213 } else { 214 channelDescription.visibility = View.VISIBLE 215 } 216 217 switch.isChecked = nc.importance != IMPORTANCE_NONE 218 switch.contentDescription = 219 if (isEmpty(channelDescription.text)) { 220 channelName.text 221 } else { 222 "${channelName.text} ${channelDescription.text}" 223 } 224 } 225 updateImportancenull226 private fun updateImportance() { 227 val importance = channel?.importance ?: 0 228 gentle = importance != IMPORTANCE_UNSPECIFIED && importance < IMPORTANCE_DEFAULT 229 } 230 } 231