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