1 /*
2  * 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.app.INotificationManager
20 import android.app.NotificationChannel
21 import android.app.NotificationChannelGroup
22 import android.app.NotificationManager.IMPORTANCE_DEFAULT
23 import android.app.NotificationManager.IMPORTANCE_NONE
24 import android.content.pm.ParceledListSlice
25 import android.graphics.Color
26 import android.graphics.drawable.ColorDrawable
27 import android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME
28 import androidx.test.ext.junit.runners.AndroidJUnit4
29 import androidx.test.filters.SmallTest
30 import android.testing.TestableLooper
31 import android.view.View
32 
33 import com.android.systemui.SysuiTestCase
34 
35 import org.junit.Assert.assertEquals
36 import org.junit.Before
37 import org.junit.runner.RunWith
38 import org.junit.Test
39 import org.mockito.Answers
40 import org.mockito.ArgumentMatchers.anyBoolean
41 import org.mockito.ArgumentMatchers.eq
42 import org.mockito.Mock
43 import org.mockito.Mockito
44 import org.mockito.Mockito.`when`
45 import org.mockito.Mockito.times
46 import org.mockito.Mockito.verify
47 import org.mockito.MockitoAnnotations
48 
49 @SmallTest
50 @RunWith(AndroidJUnit4::class)
51 @TestableLooper.RunWithLooper
52 class ChannelEditorDialogControllerTest : SysuiTestCase() {
53 
54     private lateinit var controller: ChannelEditorDialogController
55     private lateinit var channel1: NotificationChannel
56     private lateinit var channel2: NotificationChannel
57     private lateinit var channelDefault: NotificationChannel
58     private lateinit var group: NotificationChannelGroup
59 
60     private val appIcon = ColorDrawable(Color.MAGENTA)
61 
62     @Mock
63     private lateinit var mockNoMan: INotificationManager
64     @Mock(answer = Answers.RETURNS_SELF)
65     private lateinit var dialogBuilder: ChannelEditorDialog.Builder
66     @Mock
67     private lateinit var dialog: ChannelEditorDialog
68 
69     @Before
setupnull70     fun setup() {
71         MockitoAnnotations.initMocks(this)
72         `when`(dialogBuilder.build()).thenReturn(dialog)
73         controller = ChannelEditorDialogController(mContext, mockNoMan, dialogBuilder)
74 
75         channel1 = NotificationChannel(TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT)
76         channel2 = NotificationChannel(TEST_CHANNEL2, TEST_CHANNEL_NAME2, IMPORTANCE_NONE)
77         channelDefault = NotificationChannel(
78                 NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT)
79 
80         group = NotificationChannelGroup(TEST_GROUP_ID, TEST_GROUP_NAME)
81 
82         `when`(mockNoMan.getRecentBlockedNotificationChannelGroupsForPackage(
83                 eq(TEST_PACKAGE_NAME), eq(TEST_UID)))
84                 .thenReturn(ParceledListSlice(listOf(group)))
85 
86         `when`(mockNoMan.areNotificationsEnabledForPackage(eq(TEST_PACKAGE_NAME), eq(TEST_UID)))
87                 .thenReturn(true)
88     }
89 
90     @Test
testPrepareDialogForApp_noExtraChannelsnull91     fun testPrepareDialogForApp_noExtraChannels() {
92         channel1.group = group.id
93         channel2.group = group.id
94         group.channels = listOf(channel1, channel2)
95         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
96                 channel1, appIcon, clickListener)
97 
98         assertEquals(2, controller.channelList.size)
99     }
100 
101     @Test
testPrepareDialogForApp_onlyDefaultChannelnull102     fun testPrepareDialogForApp_onlyDefaultChannel() {
103         group.addChannel(channelDefault)
104 
105         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
106                 channelDefault, appIcon, clickListener)
107 
108         assertEquals("No channels should be shown when there is only the miscellaneous channel",
109                 0, controller.channelList.size)
110     }
111 
112     @Test
testPrepareDialogForApp_AddsAllChannelsAllGroupsnull113     fun testPrepareDialogForApp_AddsAllChannelsAllGroups() {
114         val group2 = NotificationChannelGroup("two", "group two")
115         val channel3 = NotificationChannel("test_channel_3", "Test channel 3", IMPORTANCE_DEFAULT)
116         channel3.group = group2.id
117         val channel4 = NotificationChannel("test_channel_4", "Test channel 4", IMPORTANCE_DEFAULT)
118         channel4.group = group.id
119         val channel5 = NotificationChannel("test_channel_5", "Test channel 5", IMPORTANCE_DEFAULT)
120         channel5.group = group.id
121 
122         `when`(mockNoMan.getRecentBlockedNotificationChannelGroupsForPackage(
123                 eq(TEST_PACKAGE_NAME), eq(TEST_UID)))
124                 .thenReturn(ParceledListSlice(listOf(group, group2)))
125 
126         group.channels = listOf(channel1, channel2, channel4, channel5)
127         group2.channels = listOf(channel3)
128 
129         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
130                 channel1, appIcon, clickListener)
131 
132         assertEquals("ChannelEditorDialog should show all channels",
133                 5, controller.channelList.size)
134     }
135 
136     @Test
testApply_demoteChannelnull137     fun testApply_demoteChannel() {
138         group.channels = listOf(channel1, channel2)
139         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
140                 channel1, appIcon, clickListener)
141 
142         // propose an adjustment of channel1
143         controller.proposeEditForChannel(channel1, IMPORTANCE_NONE)
144 
145         controller.apply()
146 
147         assertEquals("Proposed edits should take effect after apply",
148                 IMPORTANCE_NONE, channel1.importance)
149 
150         // Channel 2 shouldn't have changed
151         assertEquals("Proposed edits should take effect after apply",
152                 IMPORTANCE_NONE, channel2.importance)
153     }
154 
155     @Test
testApply_promoteChannelnull156     fun testApply_promoteChannel() {
157         group.channels = listOf(channel1, channel2)
158         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
159                 channel1, appIcon, clickListener)
160 
161         // propose an adjustment of channel1
162         controller.proposeEditForChannel(channel2, IMPORTANCE_DEFAULT)
163 
164         controller.apply()
165 
166         assertEquals("Proposed edits should take effect after apply",
167                 IMPORTANCE_DEFAULT, channel2.importance)
168 
169         // Channel 1 shouldn't have changed
170         assertEquals("Proposed edits should take effect after apply",
171                 IMPORTANCE_DEFAULT, channel1.importance)
172     }
173 
174     @Test
testApply_demoteAppnull175     fun testApply_demoteApp() {
176         group.channels = listOf(channel1, channel2)
177         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
178                 channel1, appIcon, clickListener)
179 
180         controller.proposeSetAppNotificationsEnabled(false)
181         controller.apply()
182 
183         verify(mockNoMan, times(1)).setNotificationsEnabledForPackage(
184                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(false))
185     }
186 
187     @Test
testApply_promoteAppnull188     fun testApply_promoteApp() {
189         // App starts out disabled
190         `when`(mockNoMan.areNotificationsEnabledForPackage(eq(TEST_PACKAGE_NAME), eq(TEST_UID)))
191                 .thenReturn(false)
192 
193         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
194                 channel1, appIcon, clickListener)
195         controller.proposeSetAppNotificationsEnabled(true)
196         controller.apply()
197 
198         verify(mockNoMan, times(1)).setNotificationsEnabledForPackage(
199                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(true))
200     }
201 
202     @Test
testSettingsClickListenerNull_noCrashnull203     fun testSettingsClickListenerNull_noCrash() {
204         // GIVEN editor dialog
205         group.channels = listOf(channel1, channel2)
206         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
207                 channel1, appIcon, null)
208 
209         // WHEN user taps settings
210         // Pass in any old view, it should never actually be used
211         controller.launchSettings(View(context))
212 
213         // THEN no crash
214     }
215 
216     @Test
testDoneButtonSaysDone_noChangesnull217     fun testDoneButtonSaysDone_noChanges() {
218         // GIVEN the editor dialog with no changes
219         `when`(dialogBuilder.build()).thenReturn(dialog)
220 
221         group.channels = listOf(channel1, channel2)
222         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
223                 channel1, appIcon, null)
224 
225         // WHEN the user proposes a change
226         controller.proposeEditForChannel(channel1, IMPORTANCE_NONE)
227 
228         // THEN the "done" button has been updated to "apply"
229         verify(dialog).updateDoneButtonText(true /* hasChanges */)
230     }
231 
232     @Test
testDoneButtonGoesBackToNormal_changeThenNoChangenull233     fun testDoneButtonGoesBackToNormal_changeThenNoChange() {
234         val inOrderDialog = Mockito.inOrder(dialog)
235         // GIVEN the editor dialog with no changes
236         `when`(dialogBuilder.build()).thenReturn(dialog)
237 
238         group.channels = listOf(channel1, channel2)
239         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
240                 channel1, appIcon, null)
241 
242         // WHEN the user proposes a change
243         controller.proposeEditForChannel(channel1, IMPORTANCE_NONE)
244         // and WHEN the user sets the importance back to its original value
245         controller.proposeEditForChannel(channel1, channel1.importance)
246 
247         // THEN the "done" button has been changed back to done
248         inOrderDialog.verify(dialog, times(1)).updateDoneButtonText(eq(true))
249         inOrderDialog.verify(dialog, times(1)).updateDoneButtonText(eq(false))
250     }
251 
252     private val clickListener = object : NotificationInfo.OnSettingsClickListener {
onClicknull253         override fun onClick(v: View, c: NotificationChannel, appUid: Int) {
254         }
255     }
256 
257     companion object {
258         const val TEST_APP_NAME = "Test App Name"
259         const val TEST_PACKAGE_NAME = "test_package"
260         const val TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME
261         const val TEST_UID = 1
262         const val TEST_CHANNEL = "test_channel"
263         const val TEST_CHANNEL_NAME = "Test Channel Name"
264         const val TEST_CHANNEL2 = "test_channel2"
265         const val TEST_CHANNEL_NAME2 = "Test Channel Name2"
266         const val TEST_GROUP_ID = "test_group_id"
267         const val TEST_GROUP_NAME = "Test Group Name"
268     }
269 }
270