1 /*
2  * Copyright (C) 2024 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.chips.call.ui.viewmodel
18 
19 import android.app.PendingIntent
20 import android.platform.test.annotations.DisableFlags
21 import android.platform.test.annotations.EnableFlags
22 import android.view.View
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.common.shared.model.Icon
28 import com.android.systemui.coroutines.collectLastValue
29 import com.android.systemui.kosmos.Kosmos
30 import com.android.systemui.kosmos.testScope
31 import com.android.systemui.plugins.activityStarter
32 import com.android.systemui.res.R
33 import com.android.systemui.statusbar.StatusBarIconView
34 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
35 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
36 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
37 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
38 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
39 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
40 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
41 import com.android.systemui.util.time.fakeSystemClock
42 import com.google.common.truth.Truth.assertThat
43 import kotlin.test.Test
44 import kotlinx.coroutines.test.runTest
45 import org.junit.runner.RunWith
46 import org.mockito.kotlin.mock
47 import org.mockito.kotlin.verify
48 import org.mockito.kotlin.whenever
49 
50 @SmallTest
51 @RunWith(AndroidJUnit4::class)
52 class CallChipViewModelTest : SysuiTestCase() {
53     private val kosmos = Kosmos()
54     private val testScope = kosmos.testScope
55     private val repo = kosmos.ongoingCallRepository
56 
57     private val chipBackgroundView = mock<ChipBackgroundContainer>()
58     private val chipView =
<lambda>null59         mock<View>().apply {
60             whenever(
61                     this.requireViewById<ChipBackgroundContainer>(
62                         R.id.ongoing_activity_chip_background
63                     )
64                 )
65                 .thenReturn(chipBackgroundView)
66         }
67 
68     private val underTest = kosmos.callChipViewModel
69 
70     @Test
chip_noCall_isHiddennull71     fun chip_noCall_isHidden() =
72         testScope.runTest {
73             val latest by collectLastValue(underTest.chip)
74 
75             repo.setOngoingCallState(OngoingCallModel.NoCall)
76 
77             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
78         }
79 
80     @Test
chip_inCall_zeroStartTime_isShownAsIconOnlynull81     fun chip_inCall_zeroStartTime_isShownAsIconOnly() =
82         testScope.runTest {
83             val latest by collectLastValue(underTest.chip)
84 
85             repo.setOngoingCallState(inCallModel(startTimeMs = 0))
86 
87             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
88         }
89 
90     @Test
chip_inCall_negativeStartTime_isShownAsIconOnlynull91     fun chip_inCall_negativeStartTime_isShownAsIconOnly() =
92         testScope.runTest {
93             val latest by collectLastValue(underTest.chip)
94 
95             repo.setOngoingCallState(inCallModel(startTimeMs = -2))
96 
97             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
98         }
99 
100     @Test
chip_inCall_positiveStartTime_isShownAsTimernull101     fun chip_inCall_positiveStartTime_isShownAsTimer() =
102         testScope.runTest {
103             val latest by collectLastValue(underTest.chip)
104 
105             repo.setOngoingCallState(inCallModel(startTimeMs = 345))
106 
107             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
108         }
109 
110     @Test
chip_inCall_startTimeConvertedToElapsedRealtimenull111     fun chip_inCall_startTimeConvertedToElapsedRealtime() =
112         testScope.runTest {
113             val latest by collectLastValue(underTest.chip)
114 
115             kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
116             kosmos.fakeSystemClock.setElapsedRealtime(400_000)
117 
118             repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
119 
120             // The OngoingCallModel start time is relative to currentTimeMillis, so this call
121             // started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
122             // relative to elapsedRealtime, so it should be 2000ms before the elapsed realtime set
123             // on the clock.
124             assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
125                 .isEqualTo(398_000)
126         }
127 
128     @Test
129     @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
chip_positiveStartTime_notifIconFlagOff_iconIsPhonenull130     fun chip_positiveStartTime_notifIconFlagOff_iconIsPhone() =
131         testScope.runTest {
132             val latest by collectLastValue(underTest.chip)
133 
134             repo.setOngoingCallState(
135                 inCallModel(startTimeMs = 1000, notificationIcon = mock<StatusBarIconView>())
136             )
137 
138             assertThat((latest as OngoingActivityChipModel.Shown).icon)
139                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
140             val icon =
141                 (((latest as OngoingActivityChipModel.Shown).icon)
142                         as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
143                     .impl as Icon.Resource
144             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
145             assertThat(icon.contentDescription).isNotNull()
146         }
147 
148     @Test
149     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
chip_positiveStartTime_notifIconFlagOn_iconIsNotifIconnull150     fun chip_positiveStartTime_notifIconFlagOn_iconIsNotifIcon() =
151         testScope.runTest {
152             val latest by collectLastValue(underTest.chip)
153 
154             val notifIcon = mock<StatusBarIconView>()
155             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = notifIcon))
156 
157             assertThat((latest as OngoingActivityChipModel.Shown).icon)
158                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
159             val actualIcon =
160                 (((latest as OngoingActivityChipModel.Shown).icon)
161                         as OngoingActivityChipModel.ChipIcon.StatusBarView)
162                     .impl
163             assertThat(actualIcon).isEqualTo(notifIcon)
164         }
165 
166     @Test
167     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIconnull168     fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() =
169         testScope.runTest {
170             val latest by collectLastValue(underTest.chip)
171 
172             val notifKey = "testNotifKey"
173             repo.setOngoingCallState(
174                 inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey)
175             )
176 
177             assertThat((latest as OngoingActivityChipModel.Shown).icon)
178                 .isInstanceOf(
179                     OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
180                 )
181             val actualNotifKey =
182                 (((latest as OngoingActivityChipModel.Shown).icon)
183                         as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon)
184                     .notificationKey
185             assertThat(actualNotifKey).isEqualTo(notifKey)
186         }
187 
188     @Test
189     @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
chip_zeroStartTime_notifIconFlagOff_iconIsPhonenull190     fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() =
191         testScope.runTest {
192             val latest by collectLastValue(underTest.chip)
193 
194             repo.setOngoingCallState(
195                 inCallModel(startTimeMs = 0, notificationIcon = mock<StatusBarIconView>())
196             )
197 
198             assertThat((latest as OngoingActivityChipModel.Shown).icon)
199                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
200             val icon =
201                 (((latest as OngoingActivityChipModel.Shown).icon)
202                         as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
203                     .impl as Icon.Resource
204             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
205             assertThat(icon.contentDescription).isNotNull()
206         }
207 
208     @Test
209     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
chip_zeroStartTime_notifIconFlagOn_iconIsNotifIconnull210     fun chip_zeroStartTime_notifIconFlagOn_iconIsNotifIcon() =
211         testScope.runTest {
212             val latest by collectLastValue(underTest.chip)
213 
214             val notifIcon = mock<StatusBarIconView>()
215             repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon))
216 
217             assertThat((latest as OngoingActivityChipModel.Shown).icon)
218                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
219             val actualIcon =
220                 (((latest as OngoingActivityChipModel.Shown).icon)
221                         as OngoingActivityChipModel.ChipIcon.StatusBarView)
222                     .impl
223             assertThat(actualIcon).isEqualTo(notifIcon)
224         }
225 
226     @Test
227     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
chip_notifIconFlagOn_butNullNotifIcon_iconIsPhonenull228     fun chip_notifIconFlagOn_butNullNotifIcon_iconIsPhone() =
229         testScope.runTest {
230             val latest by collectLastValue(underTest.chip)
231 
232             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
233 
234             assertThat((latest as OngoingActivityChipModel.Shown).icon)
235                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
236             val icon =
237                 (((latest as OngoingActivityChipModel.Shown).icon)
238                         as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
239                     .impl as Icon.Resource
240             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
241             assertThat(icon.contentDescription).isNotNull()
242         }
243 
244     @Test
chip_positiveStartTime_colorsAreThemednull245     fun chip_positiveStartTime_colorsAreThemed() =
246         testScope.runTest {
247             val latest by collectLastValue(underTest.chip)
248 
249             repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
250 
251             assertThat((latest as OngoingActivityChipModel.Shown).colors)
252                 .isEqualTo(ColorsModel.Themed)
253         }
254 
255     @Test
chip_zeroStartTime_colorsAreThemednull256     fun chip_zeroStartTime_colorsAreThemed() =
257         testScope.runTest {
258             val latest by collectLastValue(underTest.chip)
259 
260             repo.setOngoingCallState(inCallModel(startTimeMs = 0))
261 
262             assertThat((latest as OngoingActivityChipModel.Shown).colors)
263                 .isEqualTo(ColorsModel.Themed)
264         }
265 
266     @Test
chip_resetsCorrectlynull267     fun chip_resetsCorrectly() =
268         testScope.runTest {
269             val latest by collectLastValue(underTest.chip)
270             kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
271             kosmos.fakeSystemClock.setElapsedRealtime(400_000)
272 
273             // Start a call
274             repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
275             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
276             assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
277                 .isEqualTo(398_000)
278 
279             // End the call
280             repo.setOngoingCallState(OngoingCallModel.NoCall)
281             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
282 
283             // Let 100_000ms elapse
284             kosmos.fakeSystemClock.setCurrentTimeMillis(103_000)
285             kosmos.fakeSystemClock.setElapsedRealtime(500_000)
286 
287             // Start a new call, which started 1000ms ago
288             repo.setOngoingCallState(inCallModel(startTimeMs = 102_000))
289             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
290             assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
291                 .isEqualTo(499_000)
292         }
293 
294     @Test
chip_inCall_nullIntent_nullClickListenernull295     fun chip_inCall_nullIntent_nullClickListener() =
296         testScope.runTest {
297             val latest by collectLastValue(underTest.chip)
298 
299             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
300 
301             assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
302         }
303 
304     @Test
chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntentnull305     fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
306         testScope.runTest {
307             val latest by collectLastValue(underTest.chip)
308 
309             val intent = mock<PendingIntent>()
310             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = intent))
311             val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
312             assertThat(clickListener).isNotNull()
313 
314             clickListener!!.onClick(chipView)
315 
316             verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null)
317         }
318 
319     @Test
chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntentnull320     fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
321         testScope.runTest {
322             val latest by collectLastValue(underTest.chip)
323 
324             val intent = mock<PendingIntent>()
325             repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = intent))
326             val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
327             assertThat(clickListener).isNotNull()
328 
329             clickListener!!.onClick(chipView)
330 
331             verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null)
332         }
333 }
334