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 package com.android.systemui.dreams.homecontrols 17 18 import android.app.Activity 19 import android.content.ComponentName 20 import android.content.Intent 21 import android.os.powerManager 22 import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL 23 import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM 24 import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE 25 import android.service.dreams.DreamService 26 import android.window.TaskFragmentInfo 27 import androidx.lifecycle.testing.TestLifecycleOwner 28 import androidx.test.ext.junit.runners.AndroidJUnit4 29 import androidx.test.filters.SmallTest 30 import com.android.systemui.SysuiTestCase 31 import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent 32 import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo 33 import com.android.systemui.dreams.homecontrols.shared.model.fakeHomeControlsDataSource 34 import com.android.systemui.dreams.homecontrols.shared.model.homeControlsDataSource 35 import com.android.systemui.kosmos.testDispatcher 36 import com.android.systemui.kosmos.testScope 37 import com.android.systemui.log.logcatLogBuffer 38 import com.android.systemui.testKosmos 39 import com.android.systemui.util.time.fakeSystemClock 40 import com.android.systemui.util.wakelock.WakeLockFake 41 import com.google.common.truth.Truth.assertThat 42 import kotlinx.coroutines.Dispatchers 43 import kotlinx.coroutines.test.advanceUntilIdle 44 import kotlinx.coroutines.test.resetMain 45 import kotlinx.coroutines.test.runCurrent 46 import kotlinx.coroutines.test.runTest 47 import kotlinx.coroutines.test.setMain 48 import org.junit.After 49 import org.junit.Before 50 import org.junit.Test 51 import org.junit.runner.RunWith 52 import org.mockito.kotlin.any 53 import org.mockito.kotlin.argThat 54 import org.mockito.kotlin.argumentCaptor 55 import org.mockito.kotlin.doReturn 56 import org.mockito.kotlin.eq 57 import org.mockito.kotlin.mock 58 import org.mockito.kotlin.never 59 import org.mockito.kotlin.times 60 import org.mockito.kotlin.verify 61 62 @SmallTest 63 @RunWith(AndroidJUnit4::class) 64 class HomeControlsDreamServiceTest : SysuiTestCase() { 65 66 private val kosmos = testKosmos() 67 private val testScope = kosmos.testScope 68 69 private val fakeWakeLock = WakeLockFake() <lambda>null70 private val fakeWakeLockBuilder by lazy { 71 WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) } 72 } 73 74 private val lifecycleOwner = TestLifecycleOwner(coroutineDispatcher = kosmos.testDispatcher) 75 76 private val taskFragmentComponent = mock<TaskFragmentComponent>() 77 private val activity = mock<Activity>() 78 private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>() 79 private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>() 80 private val hideCallback = argumentCaptor<() -> Unit>() 81 private var dreamService = <lambda>null82 mock<DreamService> { 83 on { activity } doReturn activity 84 on { redirectWake } doReturn false 85 } 86 87 private val taskFragmentComponentFactory = <lambda>null88 mock<TaskFragmentComponent.Factory> { 89 on { 90 create( 91 activity = eq(activity), 92 onCreateCallback = onCreateCallback.capture(), 93 onInfoChangedCallback = onInfoChangedCallback.capture(), 94 hide = hideCallback.capture(), 95 ) 96 } doReturn taskFragmentComponent 97 } 98 <lambda>null99 private val underTest: HomeControlsDreamServiceImpl by lazy { 100 with(kosmos) { 101 HomeControlsDreamServiceImpl( 102 taskFragmentFactory = taskFragmentComponentFactory, 103 wakeLockBuilder = fakeWakeLockBuilder, 104 powerManager = powerManager, 105 systemClock = fakeSystemClock, 106 dataSource = homeControlsDataSource, 107 logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest"), 108 service = dreamService, 109 lifecycleOwner = lifecycleOwner, 110 ) 111 } 112 } 113 114 @Before setupnull115 fun setup() { 116 Dispatchers.setMain(kosmos.testDispatcher) 117 kosmos.fakeHomeControlsDataSource.setComponentInfo( 118 HomeControlsComponentInfo(PANEL_COMPONENT, true) 119 ) 120 } 121 122 @After tearDownnull123 fun tearDown() { 124 Dispatchers.resetMain() 125 } 126 127 @Test testOnAttachedToWindowCreatesTaskFragmentComponentnull128 fun testOnAttachedToWindowCreatesTaskFragmentComponent() = 129 testScope.runTest { 130 underTest.onAttachedToWindow() 131 verify(taskFragmentComponentFactory).create(any(), any(), any(), any()) 132 } 133 134 @Test testOnDetachedFromWindowDestroyTaskFragmentComponentnull135 fun testOnDetachedFromWindowDestroyTaskFragmentComponent() = 136 testScope.runTest { 137 underTest.onAttachedToWindow() 138 underTest.onDetachedFromWindow() 139 verify(taskFragmentComponent).destroy() 140 } 141 142 @Test testNotCreatingTaskFragmentComponentWhenActivityIsNullnull143 fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() = 144 testScope.runTest { 145 dreamService = mock<DreamService> { on { activity } doReturn null } 146 underTest.onAttachedToWindow() 147 verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any()) 148 verify(dreamService).finish() 149 } 150 151 @Test testAttachWindow_wakeLockAcquirednull152 fun testAttachWindow_wakeLockAcquired() = 153 testScope.runTest { 154 underTest.onAttachedToWindow() 155 assertThat(fakeWakeLock.isHeld).isTrue() 156 } 157 158 @Test testDetachWindow_wakeLockCanBeReleasednull159 fun testDetachWindow_wakeLockCanBeReleased() = 160 testScope.runTest { 161 underTest.onAttachedToWindow() 162 assertThat(fakeWakeLock.isHeld).isTrue() 163 164 underTest.onDetachedFromWindow() 165 assertThat(fakeWakeLock.isHeld).isFalse() 166 } 167 168 @Test testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakesnull169 fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() = 170 testScope.runTest { 171 underTest.onAttachedToWindow() 172 onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>()) 173 runCurrent() 174 verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) 175 176 // Task fragment becomes empty 177 onInfoChangedCallback.firstValue.invoke( 178 mock<TaskFragmentInfo> { on { isEmpty } doReturn true } 179 ) 180 advanceUntilIdle() 181 // Dream is finished and activity is not restarted 182 verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) 183 verify(dreamService, never()).wakeUp() 184 verify(dreamService).finish() 185 } 186 187 @Test testRestartsActivityWhenRedirectingWakesnull188 fun testRestartsActivityWhenRedirectingWakes() = 189 testScope.runTest { 190 dreamService = 191 mock<DreamService> { 192 on { activity } doReturn activity 193 on { redirectWake } doReturn true 194 } 195 underTest.onAttachedToWindow() 196 onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>()) 197 runCurrent() 198 verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) 199 200 // Task fragment becomes empty 201 onInfoChangedCallback.firstValue.invoke( 202 mock<TaskFragmentInfo> { on { isEmpty } doReturn true } 203 ) 204 advanceUntilIdle() 205 206 // Activity is restarted instead of finishing the dream. 207 verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher()) 208 verify(dreamService).wakeUp() 209 verify(dreamService, never()).finish() 210 } 211 intentMatchernull212 private fun intentMatcher() = 213 argThat<Intent> { 214 getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) == 215 CONTROLS_SURFACE_DREAM && component == PANEL_COMPONENT 216 } 217 218 private companion object { 219 val PANEL_COMPONENT = ComponentName("test.pkg", "test.panel") 220 } 221 } 222