• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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