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 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.Switch 38 import android.widget.TextView 39 import com.android.settingslib.Utils 40 41 import com.android.systemui.res.R 42 import com.android.systemui.util.Assert 43 44 /** 45 * Half-shelf for notification channel controls 46 */ 47 class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { 48 lateinit var controller: ChannelEditorDialogController 49 var appIcon: Drawable? = null 50 var appName: String? = null 51 var channels = mutableListOf<NotificationChannel>() 52 set(newValue) { 53 field = newValue 54 updateRows() 55 } 56 57 // The first row is for the entire app 58 private lateinit var appControlRow: AppControlView 59 private lateinit var channelListView: LinearLayout 60 private val channelRows = mutableListOf<ChannelRow>() 61 62 override fun onFinishInflate() { 63 super.onFinishInflate() 64 65 appControlRow = requireViewById(R.id.app_control) 66 channelListView = requireViewById(R.id.scrollView) 67 } 68 69 /** 70 * Play a highlight animation for the given channel (it really should exist but this will just 71 * fail silently if it doesn't) 72 */ 73 fun highlightChannel(channel: NotificationChannel) { 74 Assert.isMainThread() 75 for (row in channelRows) { 76 if (row.channel == channel) { 77 row.playHighlight() 78 } 79 } 80 } 81 82 private fun updateRows() { 83 val enabled = controller.areAppNotificationsEnabled() 84 85 val transition = AutoTransition() 86 transition.duration = 200 87 transition.addListener(object : Transition.TransitionListener { 88 override fun onTransitionEnd(p0: Transition?) { 89 notifySubtreeAccessibilityStateChangedIfNeeded() 90 } 91 92 override fun onTransitionResume(p0: Transition?) { 93 } 94 95 override fun onTransitionPause(p0: Transition?) { 96 } 97 98 override fun onTransitionCancel(p0: Transition?) { 99 } 100 101 override fun onTransitionStart(p0: Transition?) { 102 } 103 }) 104 TransitionManager.beginDelayedTransition(this, transition) 105 106 // Remove any rows 107 for (row in channelRows) { 108 channelListView.removeView(row) 109 } 110 channelRows.clear() 111 112 updateAppControlRow(enabled) 113 114 if (enabled) { 115 val inflater = LayoutInflater.from(context) 116 for (channel in channels) { 117 addChannelRow(channel, inflater) 118 } 119 } 120 } 121 122 private fun addChannelRow(channel: NotificationChannel, inflater: LayoutInflater) { 123 val row = inflater.inflate(R.layout.notif_half_shelf_row, null) as ChannelRow 124 row.controller = controller 125 row.channel = channel 126 127 channelRows.add(row) 128 channelListView.addView(row) 129 } 130 131 private fun updateAppControlRow(enabled: Boolean) { 132 appControlRow.iconView.setImageDrawable(appIcon) 133 appControlRow.channelName.text = context.resources 134 .getString(R.string.notification_channel_dialog_title, appName) 135 appControlRow.switch.isChecked = enabled 136 appControlRow.switch.setOnCheckedChangeListener { _, b -> 137 controller.proposeSetAppNotificationsEnabled(b) 138 updateRows() 139 } 140 } 141 } 142 143 class AppControlView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { 144 lateinit var iconView: ImageView 145 lateinit var channelName: TextView 146 lateinit var switch: Switch 147 onFinishInflatenull148 override fun onFinishInflate() { 149 iconView = requireViewById(R.id.icon) 150 channelName = requireViewById(R.id.app_name) 151 switch = requireViewById(R.id.toggle) 152 153 setOnClickListener { switch.toggle() } 154 } 155 } 156 157 class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { 158 159 lateinit var controller: ChannelEditorDialogController 160 private lateinit var channelName: TextView 161 private lateinit var channelDescription: TextView 162 private lateinit var switch: Switch 163 private val highlightColor: Int 164 var gentle = false 165 166 init { 167 highlightColor = Utils.getColorAttrDefaultColor( 168 context, android.R.attr.colorControlHighlight) 169 } 170 171 var channel: NotificationChannel? = null 172 set(newValue) { 173 field = newValue 174 updateImportance() 175 updateViews() 176 } 177 onFinishInflatenull178 override fun onFinishInflate() { 179 super.onFinishInflate() 180 channelName = requireViewById(R.id.channel_name) 181 channelDescription = requireViewById(R.id.channel_description) 182 switch = requireViewById(R.id.toggle) 183 switch.setOnCheckedChangeListener { _, b -> 184 channel?.let { 185 controller.proposeEditForChannel(it, 186 if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW) 187 else IMPORTANCE_NONE) 188 } 189 } 190 setOnClickListener { switch.toggle() } 191 } 192 193 /** 194 * Play an animation that highlights this row 195 */ playHighlightnull196 fun playHighlight() { 197 // Use 0 for the start value because our background is given to us by our parent 198 val fadeInLoop = ValueAnimator.ofObject(ArgbEvaluator(), 0, highlightColor) 199 fadeInLoop.duration = 200L 200 fadeInLoop.addUpdateListener { animator -> 201 setBackgroundColor(animator.animatedValue as Int) 202 } 203 fadeInLoop.repeatMode = ValueAnimator.REVERSE 204 // Repeat an odd number of times to we end up normal 205 fadeInLoop.repeatCount = 5 206 fadeInLoop.start() 207 } 208 updateViewsnull209 private fun updateViews() { 210 val nc = channel ?: return 211 212 channelName.text = nc.name ?: "" 213 214 nc.group?.let { groupId -> 215 channelDescription.text = controller.groupNameForId(groupId) 216 } 217 218 if (nc.group == null || TextUtils.isEmpty(channelDescription.text)) { 219 channelDescription.visibility = View.GONE 220 } else { 221 channelDescription.visibility = View.VISIBLE 222 } 223 224 switch.isChecked = nc.importance != IMPORTANCE_NONE 225 } 226 updateImportancenull227 private fun updateImportance() { 228 val importance = channel?.importance ?: 0 229 gentle = importance != IMPORTANCE_UNSPECIFIED && importance < IMPORTANCE_DEFAULT 230 } 231 } 232