1 /* 2 * Copyright (C) 2022 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.smartspace 17 18 import android.app.smartspace.SmartspaceManager 19 import android.app.smartspace.SmartspaceSession 20 import android.app.smartspace.SmartspaceTarget 21 import android.content.Context 22 import android.graphics.drawable.Drawable 23 import android.testing.AndroidTestingRunner 24 import android.testing.TestableLooper 25 import android.view.View 26 import android.view.ViewGroup 27 import android.widget.FrameLayout 28 import androidx.test.filters.SmallTest 29 import com.android.systemui.SysuiTestCase 30 import com.android.systemui.dreams.smartspace.DreamSmartspaceController 31 import com.android.systemui.plugins.BcSmartspaceConfigPlugin 32 import com.android.systemui.plugins.BcSmartspaceDataPlugin 33 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView 34 import com.android.systemui.plugins.FalsingManager 35 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent 36 import com.android.systemui.util.concurrency.Execution 37 import com.android.systemui.util.mockito.any 38 import com.android.systemui.util.mockito.eq 39 import com.android.systemui.util.mockito.withArgCaptor 40 import com.google.common.truth.Truth.assertThat 41 import java.util.Optional 42 import java.util.concurrent.Executor 43 import org.junit.Before 44 import org.junit.Test 45 import org.junit.runner.RunWith 46 import org.mockito.Mock 47 import org.mockito.Mockito 48 import org.mockito.Mockito.verify 49 import org.mockito.Mockito.`when` 50 import org.mockito.Mockito.anyInt 51 import org.mockito.MockitoAnnotations 52 import org.mockito.Spy 53 54 @SmallTest 55 @RunWith(AndroidTestingRunner::class) 56 @TestableLooper.RunWithLooper 57 class DreamSmartspaceControllerTest : SysuiTestCase() { 58 @Mock 59 private lateinit var smartspaceManager: SmartspaceManager 60 61 @Mock 62 private lateinit var execution: Execution 63 64 @Mock 65 private lateinit var uiExecutor: Executor 66 67 @Mock 68 private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory 69 70 @Mock 71 private lateinit var viewComponent: SmartspaceViewComponent 72 73 @Mock 74 private lateinit var weatherViewComponent: SmartspaceViewComponent 75 76 @Spy 77 private var weatherSmartspaceView: SmartspaceView = TestView(context) 78 79 @Mock 80 private lateinit var targetFilter: SmartspaceTargetFilter 81 82 @Mock 83 private lateinit var plugin: BcSmartspaceDataPlugin 84 85 @Mock 86 private lateinit var weatherPlugin: BcSmartspaceDataPlugin 87 88 @Mock 89 private lateinit var precondition: SmartspacePrecondition 90 91 @Spy 92 private var smartspaceView: SmartspaceView = TestView(context) 93 94 @Mock 95 private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener 96 97 @Mock 98 private lateinit var session: SmartspaceSession 99 100 private lateinit var controller: DreamSmartspaceController 101 102 // TODO(b/272811280): Remove usage of real view 103 private val fakeParent = FrameLayout(context) 104 105 /** 106 * A class which implements SmartspaceView and extends View. This is mocked to provide the right 107 * object inheritance and interface implementation used in DreamSmartspaceController 108 */ 109 private class TestView(context: Context?) : View(context), SmartspaceView { 110 override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {} 111 112 override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {} 113 114 override fun setPrimaryTextColor(color: Int) {} 115 116 override fun setUiSurface(uiSurface: String) {} 117 118 override fun setDozeAmount(amount: Float) {} 119 120 override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} 121 122 override fun setFalsingManager(falsingManager: FalsingManager?) {} 123 124 override fun setDnd(image: Drawable?, description: String?) {} 125 126 override fun setNextAlarm(image: Drawable?, description: String?) {} 127 128 override fun setMediaTarget(target: SmartspaceTarget?) {} 129 130 override fun getSelectedPage(): Int { return 0; } 131 132 override fun getCurrentCardTopPadding(): Int { return 0; } 133 } 134 135 @Before 136 fun setup() { 137 MockitoAnnotations.initMocks(this) 138 `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null))) 139 .thenReturn(viewComponent) 140 `when`(viewComponent.getView()).thenReturn(smartspaceView) 141 `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any())) 142 .thenReturn(weatherViewComponent) 143 `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView) 144 `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) 145 146 controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor, 147 viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin), 148 Optional.of(weatherPlugin)) 149 } 150 151 /** 152 * Ensures smartspace session begins on a listener only flow. 153 */ 154 @Test 155 fun testConnectOnListen() { 156 `when`(precondition.conditionsMet()).thenReturn(true) 157 controller.addListener(listener) 158 159 verify(smartspaceManager).createSmartspaceSession(any()) 160 161 var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> { 162 verify(session).addOnTargetsAvailableListener(any(), capture()) 163 } 164 165 `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true) 166 167 var target = Mockito.mock(SmartspaceTarget::class.java) 168 targetListener.onTargetsAvailable(listOf(target)) 169 170 var targets = withArgCaptor<List<SmartspaceTarget>> { 171 verify(plugin).onTargetsAvailable(capture()) 172 } 173 174 assertThat(targets.contains(target)).isTrue() 175 176 controller.removeListener(listener) 177 178 verify(session).close() 179 } 180 181 /** 182 * Ensures session begins when a view is attached. 183 */ 184 @Test 185 fun testConnectOnViewCreate() { 186 `when`(precondition.conditionsMet()).thenReturn(true) 187 controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java)) 188 189 val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { 190 verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null)) 191 } 192 193 val mockView = Mockito.mock(TestView::class.java) 194 `when`(precondition.conditionsMet()).thenReturn(true) 195 stateChangeListener.onViewAttachedToWindow(mockView) 196 197 verify(smartspaceManager).createSmartspaceSession(any()) 198 verify(mockView).setDozeAmount(0f) 199 200 stateChangeListener.onViewDetachedFromWindow(mockView) 201 202 verify(session).close() 203 } 204 205 /** 206 * Ensures session is created when weather smartspace view is created and attached. 207 */ 208 @Test 209 fun testConnectOnWeatherViewCreate() { 210 `when`(precondition.conditionsMet()).thenReturn(true) 211 212 val customView = Mockito.mock(TestView::class.java) 213 val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) 214 val weatherSmartspaceView = weatherView as SmartspaceView 215 fakeParent.addView(weatherView) 216 217 // Then weather view is created with custom view and the default weatherPlugin.getView 218 // should not be called 219 verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(), 220 eq(customView)) 221 verify(weatherPlugin, Mockito.never()).getView(fakeParent) 222 223 // And then session is created 224 controller.stateChangeListener.onViewAttachedToWindow(weatherView) 225 verify(smartspaceManager).createSmartspaceSession(any()) 226 verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) 227 verify(weatherSmartspaceView).setDozeAmount(0f) 228 } 229 230 /** 231 * Ensures weather plugin registers target listener when it is added from the controller. 232 */ 233 @Test 234 fun testAddListenerInController_registersListenerForWeatherPlugin() { 235 val customView = Mockito.mock(TestView::class.java) 236 `when`(precondition.conditionsMet()).thenReturn(true) 237 238 // Given a session is created 239 val weatherView = 240 checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView)) 241 controller.stateChangeListener.onViewAttachedToWindow(weatherView) 242 verify(smartspaceManager).createSmartspaceSession(any()) 243 244 // When a listener is added 245 controller.addListenerForWeatherPlugin(listener) 246 247 // Then the listener is registered to the weather plugin only 248 verify(weatherPlugin).registerListener(listener) 249 verify(plugin, Mockito.never()).registerListener(any()) 250 } 251 252 /** 253 * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace 254 * view is detached. 255 */ 256 @Test 257 fun testDisconnect_emitsEmptyListAndRemovesNotifier() { 258 `when`(precondition.conditionsMet()).thenReturn(true) 259 260 // Given a session is created 261 val customView = Mockito.mock(TestView::class.java) 262 val weatherView = 263 checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView)) 264 controller.stateChangeListener.onViewAttachedToWindow(weatherView) 265 verify(smartspaceManager).createSmartspaceSession(any()) 266 267 // When view is detached 268 controller.stateChangeListener.onViewDetachedFromWindow(weatherView) 269 // Then the session is closed 270 verify(session).close() 271 272 // And the listener receives an empty list of targets and unregisters the notifier 273 verify(weatherPlugin).onTargetsAvailable(emptyList()) 274 verify(weatherPlugin).registerSmartspaceEventNotifier(null) 275 } 276 } 277