1 /*
2  * Copyright (C) 2023 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.mediaprojection.taskswitcher.ui
18 
19 import android.app.Notification
20 import android.app.NotificationManager
21 import androidx.test.ext.junit.runners.AndroidJUnit4
22 import androidx.test.filters.SmallTest
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.kosmos.testScope
25 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
26 import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
27 import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
28 import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
29 import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
30 import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
31 import com.android.systemui.res.R
32 import com.android.systemui.util.mockito.any
33 import com.android.systemui.util.mockito.argumentCaptor
34 import com.android.systemui.util.mockito.mock
35 import com.google.common.truth.Truth.assertThat
36 import junit.framework.Assert.assertEquals
37 import kotlinx.coroutines.test.runTest
38 import org.junit.Before
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 import org.mockito.ArgumentCaptor
42 import org.mockito.Mockito.never
43 import org.mockito.Mockito.reset
44 import org.mockito.Mockito.verify
45 
46 @RunWith(AndroidJUnit4::class)
47 @SmallTest
48 class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
49 
50     private val notificationManager = mock<NotificationManager>()
51     private val kosmos = taskSwitcherKosmos()
52     private val testScope = kosmos.testScope
53     private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
54     private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
55     private val viewModel = kosmos.taskSwitcherViewModel
56 
57     private lateinit var coordinator: TaskSwitcherNotificationCoordinator
58 
59     @Before
setupnull60     fun setup() {
61         coordinator =
62             TaskSwitcherNotificationCoordinator(
63                 context,
64                 notificationManager,
65                 testScope.backgroundScope,
66                 viewModel,
67                 fakeBroadcastDispatcher,
68             )
69         coordinator.start()
70         // When the coordinator starts up, the view model will immediately emit a NotShowing event
71         // and hide the notification. That's fine, but we should reset the notification manager so
72         // that the initial emission isn't part of the tests.
73         reset(notificationManager)
74     }
75 
76     @Test
showNotificationnull77     fun showNotification() {
78         testScope.runTest {
79             switchTask()
80 
81             val notification = ArgumentCaptor.forClass(Notification::class.java)
82             verify(notificationManager).notify(any(), any(), notification.capture())
83             assertNotification(notification)
84         }
85     }
86 
87     @Test
hideNotificationnull88     fun hideNotification() {
89         testScope.runTest {
90             // First, show a notification
91             switchTask()
92 
93             // WHEN the projection is stopped
94             fakeMediaProjectionManager.dispatchOnStop()
95 
96             // THEN the notification is hidden
97             verify(notificationManager).cancel(any(), any())
98         }
99     }
100 
101     @Test
notificationIdIsConsistentnull102     fun notificationIdIsConsistent() {
103         testScope.runTest {
104             // First, show a notification
105             switchTask()
106             val idNotify = argumentCaptor<Int>()
107             verify(notificationManager).notify(any(), idNotify.capture(), any())
108 
109             // Then, hide the notification
110             fakeMediaProjectionManager.dispatchOnStop()
111             val idCancel = argumentCaptor<Int>()
112             verify(notificationManager).cancel(any(), idCancel.capture())
113 
114             assertEquals(idCancel.value, idNotify.value)
115         }
116     }
117 
118     @Test
switchTaskAction_hidesNotificationnull119     fun switchTaskAction_hidesNotification() =
120         testScope.runTest {
121             switchTask()
122             val notification = argumentCaptor<Notification>()
123             verify(notificationManager).notify(any(), any(), notification.capture())
124             verify(notificationManager, never()).cancel(any(), any())
125 
126             val action = findSwitchAction(notification.value)
127             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
128                 context,
129                 action.actionIntent.intent
130             )
131 
132             verify(notificationManager).cancel(any(), any())
133         }
134 
135     @Test
goBackAction_hidesNotificationnull136     fun goBackAction_hidesNotification() =
137         testScope.runTest {
138             switchTask()
139             val notification = argumentCaptor<Notification>()
140             verify(notificationManager).notify(any(), any(), notification.capture())
141             verify(notificationManager, never()).cancel(any(), any())
142 
143             val action = findGoBackAction(notification.value)
144             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
145                 context,
146                 action.actionIntent.intent
147             )
148 
149             verify(notificationManager).cancel(any(), any())
150         }
151 
findSwitchActionnull152     private fun findSwitchAction(notification: Notification): Notification.Action {
153         return notification.actions.first {
154             it.title == context.getString(R.string.media_projection_task_switcher_action_switch)
155         }
156     }
157 
findGoBackActionnull158     private fun findGoBackAction(notification: Notification): Notification.Action {
159         return notification.actions.first {
160             it.title == context.getString(R.string.media_projection_task_switcher_action_back)
161         }
162     }
163 
switchTasknull164     private fun switchTask() {
165         val projectedTask = createTask(taskId = 1)
166         val foregroundTask = createTask(taskId = 2)
167         fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
168         fakeMediaProjectionManager.dispatchOnSessionSet(
169             session =
170                 FakeMediaProjectionManager.createSingleTaskSession(projectedTask.token.asBinder())
171         )
172         fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
173     }
174 
assertNotificationnull175     private fun assertNotification(notification: ArgumentCaptor<Notification>) {
176         val text = notification.value.extras.getCharSequence(Notification.EXTRA_TEXT)
177         assertEquals(context.getString(R.string.media_projection_task_switcher_text), text)
178 
179         val actions = notification.value.actions
180         assertThat(actions).hasLength(2)
181     }
182 }
183