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