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 17 package com.android.systemui.controls.management 18 19 import android.app.Dialog 20 import android.content.ComponentName 21 import android.content.Intent 22 import android.content.pm.ApplicationInfo 23 import android.content.pm.ServiceInfo 24 import android.graphics.drawable.Drawable 25 import android.os.Bundle 26 import android.testing.TestableLooper 27 import android.window.OnBackInvokedCallback 28 import android.window.OnBackInvokedDispatcher 29 import androidx.test.ext.junit.runners.AndroidJUnit4 30 import androidx.test.filters.SmallTest 31 import androidx.test.rule.ActivityTestRule 32 import com.android.systemui.SysuiTestCase 33 import com.android.systemui.activity.SingleActivityFactory 34 import com.android.systemui.controls.ControlsServiceInfo 35 import com.android.systemui.controls.controller.ControlsController 36 import com.android.systemui.controls.panels.AuthorizedPanelsRepository 37 import com.android.systemui.controls.ui.SelectedItem 38 import com.android.systemui.dagger.qualifiers.Background 39 import com.android.systemui.dagger.qualifiers.Main 40 import com.android.systemui.settings.UserTracker 41 import com.android.systemui.util.mockito.any 42 import com.android.systemui.util.mockito.argumentCaptor 43 import com.android.systemui.util.mockito.capture 44 import com.android.systemui.util.mockito.eq 45 import com.android.systemui.util.mockito.mock 46 import com.android.systemui.util.mockito.whenever 47 import com.google.common.truth.Truth.assertThat 48 import com.google.common.util.concurrent.MoreExecutors 49 import java.util.concurrent.CountDownLatch 50 import java.util.concurrent.Executor 51 import java.util.function.Consumer 52 import org.junit.Before 53 import org.junit.Rule 54 import org.junit.Test 55 import org.junit.runner.RunWith 56 import org.mockito.ArgumentCaptor 57 import org.mockito.ArgumentMatchers 58 import org.mockito.Captor 59 import org.mockito.Mock 60 import org.mockito.Mockito 61 import org.mockito.Mockito.doReturn 62 import org.mockito.Mockito.never 63 import org.mockito.Mockito.verify 64 import org.mockito.Mockito.verifyNoMoreInteractions 65 import org.mockito.MockitoAnnotations 66 67 @SmallTest 68 @RunWith(AndroidJUnit4::class) 69 @TestableLooper.RunWithLooper 70 class ControlsProviderSelectorActivityTest : SysuiTestCase() { 71 @Main private val executor: Executor = MoreExecutors.directExecutor() 72 73 @Background private val backExecutor: Executor = MoreExecutors.directExecutor() 74 75 @Mock lateinit var listingController: ControlsListingController 76 77 @Mock lateinit var controlsController: ControlsController 78 79 @Mock lateinit var userTracker: UserTracker 80 81 @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository 82 83 @Mock lateinit var dialogFactory: PanelConfirmationDialogFactory 84 85 private var latch: CountDownLatch = CountDownLatch(1) 86 87 @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher 88 @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback> 89 90 @Rule 91 @JvmField 92 var activityRule = 93 ActivityTestRule( <lambda>null94 /* activityFactory= */ SingleActivityFactory { 95 TestableControlsProviderSelectorActivity( 96 executor, 97 backExecutor, 98 listingController, 99 controlsController, 100 userTracker, 101 authorizedPanelsRepository, 102 dialogFactory, 103 mockDispatcher, 104 latch 105 ) 106 }, 107 /* initialTouchMode= */ false, 108 /* launchActivity= */ false, 109 ) 110 111 @Before setUpnull112 fun setUp() { 113 MockitoAnnotations.initMocks(this) 114 val intent = Intent() 115 intent.putExtra(ControlsProviderSelectorActivity.BACK_SHOULD_EXIT, true) 116 activityRule.launchActivity(intent) 117 } 118 119 @Test testBackCallbackRegistrationAndUnregistrationnull120 fun testBackCallbackRegistrationAndUnregistration() { 121 // 1. ensure that launching the activity results in it registering a callback 122 verify(mockDispatcher) 123 .registerOnBackInvokedCallback( 124 ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), 125 captureCallback.capture() 126 ) 127 activityRule.finishActivity() 128 latch.await() // ensure activity is finished 129 // 2. ensure that when the activity is finished, it unregisters the same callback 130 verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value) 131 } 132 133 @Test testOnAppSelectedForNonPanelStartsFavoritingActivitynull134 fun testOnAppSelectedForNonPanelStartsFavoritingActivity() { 135 val info = ControlsServiceInfo(ComponentName("test_pkg", "service"), "", null) 136 activityRule.activity.onAppSelected(info) 137 138 verifyNoMoreInteractions(dialogFactory) 139 140 assertThat(activityRule.activity.lastStartedActivity?.component?.className) 141 .isEqualTo(ControlsFavoritingActivity::class.java.name) 142 143 assertThat(activityRule.activity.triedToFinish).isFalse() 144 } 145 146 @Test testOnAppSelectedForPanelTriggersDialognull147 fun testOnAppSelectedForPanelTriggersDialog() { 148 val label = "label" 149 val info = 150 ControlsServiceInfo( 151 ComponentName("test_pkg", "service"), 152 label, 153 ComponentName("test_pkg", "activity") 154 ) 155 156 val dialog: Dialog = mock() 157 whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog) 158 159 activityRule.activity.onAppSelected(info) 160 verify(dialogFactory).createConfirmationDialog(any(), eq(label), any()) 161 verify(dialog).show() 162 163 assertThat(activityRule.activity.triedToFinish).isFalse() 164 } 165 166 @Test dialogAcceptAddsPackagenull167 fun dialogAcceptAddsPackage() { 168 val label = "label" 169 val info = 170 ControlsServiceInfo( 171 ComponentName("test_pkg", "service"), 172 label, 173 ComponentName("test_pkg", "activity") 174 ) 175 176 val dialog: Dialog = mock() 177 whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog) 178 179 activityRule.activity.onAppSelected(info) 180 181 val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor() 182 verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor)) 183 184 captor.value.accept(true) 185 186 val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor() 187 verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor)) 188 assertThat(setCaptor.value).containsExactly(info.componentName.packageName) 189 val selectedComponentCaptor: ArgumentCaptor<SelectedItem> = argumentCaptor() 190 verify(controlsController).setPreferredSelection(capture(selectedComponentCaptor)) 191 assertThat(selectedComponentCaptor.value.componentName).isEqualTo(info.componentName) 192 193 assertThat(activityRule.activity.triedToFinish).isTrue() 194 } 195 196 @Test dialogCancelDoesntAddPackagenull197 fun dialogCancelDoesntAddPackage() { 198 val label = "label" 199 val info = 200 ControlsServiceInfo( 201 ComponentName("test_pkg", "service"), 202 label, 203 ComponentName("test_pkg", "activity") 204 ) 205 206 val dialog: Dialog = mock() 207 whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog) 208 209 activityRule.activity.onAppSelected(info) 210 211 val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor() 212 verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor)) 213 214 captor.value.accept(false) 215 216 verify(authorizedPanelsRepository, never()).addAuthorizedPanels(any()) 217 218 assertThat(activityRule.activity.triedToFinish).isFalse() 219 } 220 221 class TestableControlsProviderSelectorActivity( 222 executor: Executor, 223 backExecutor: Executor, 224 listingController: ControlsListingController, 225 controlsController: ControlsController, 226 userTracker: UserTracker, 227 authorizedPanelsRepository: AuthorizedPanelsRepository, 228 dialogFactory: PanelConfirmationDialogFactory, 229 private val mockDispatcher: OnBackInvokedDispatcher, 230 private val latch: CountDownLatch 231 ) : 232 ControlsProviderSelectorActivity( 233 executor, 234 backExecutor, 235 listingController, 236 controlsController, 237 userTracker, 238 authorizedPanelsRepository, 239 dialogFactory 240 ) { 241 242 var lastStartedActivity: Intent? = null 243 var triedToFinish = false 244 getOnBackInvokedDispatchernull245 override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher { 246 return mockDispatcher 247 } 248 startActivitynull249 override fun startActivity(intent: Intent?, options: Bundle?) { 250 lastStartedActivity = intent 251 } 252 onStopnull253 override fun onStop() { 254 super.onStop() 255 // ensures that test runner thread does not proceed until ui thread is done 256 latch.countDown() 257 } 258 animateExitAndFinishnull259 override fun animateExitAndFinish() { 260 // Activity should only be finished from the rule. 261 triedToFinish = true 262 } 263 } 264 265 companion object { ControlsServiceInfonull266 private fun ControlsServiceInfo( 267 componentName: ComponentName, 268 label: CharSequence, 269 panelComponentName: ComponentName? = null 270 ): ControlsServiceInfo { 271 val serviceInfo = 272 ServiceInfo().apply { 273 applicationInfo = ApplicationInfo() 274 packageName = componentName.packageName 275 name = componentName.className 276 } 277 return Mockito.spy(ControlsServiceInfo(mock(), serviceInfo)).apply { 278 doReturn(label).`when`(this).loadLabel() 279 doReturn(mock<Drawable>()).`when`(this).loadIcon() 280 doReturn(panelComponentName).`when`(this).panelActivity 281 } 282 } 283 } 284 } 285