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.screenrecord 18 19 import android.content.Intent 20 import android.hardware.display.DisplayManager 21 import android.hardware.display.VirtualDisplay 22 import android.hardware.display.VirtualDisplayConfig 23 import android.os.UserHandle 24 import android.platform.test.annotations.RequiresFlagsEnabled 25 import android.testing.TestableLooper 26 import android.view.View 27 import android.widget.Spinner 28 import android.widget.TextView 29 import androidx.test.ext.junit.runners.AndroidJUnit4 30 import androidx.test.filters.SmallTest 31 import com.android.systemui.Dependency 32 import com.android.systemui.SysuiTestCase 33 import com.android.systemui.animation.DialogTransitionAnimator 34 import com.android.systemui.broadcast.BroadcastDispatcher 35 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger 36 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity 37 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN 38 import com.android.systemui.mediaprojection.permission.SINGLE_APP 39 import com.android.systemui.model.SysUiState 40 import com.android.systemui.plugins.ActivityStarter 41 import com.android.systemui.res.R 42 import com.android.systemui.settings.UserContextProvider 43 import com.android.systemui.statusbar.phone.SystemUIDialog 44 import com.android.systemui.statusbar.phone.SystemUIDialogManager 45 import com.android.systemui.util.mockito.argumentCaptor 46 import com.android.systemui.util.mockito.mock 47 import com.google.common.truth.Truth.assertThat 48 import com.google.common.truth.Truth.assertWithMessage 49 import junit.framework.Assert.assertEquals 50 import org.junit.After 51 import org.junit.Before 52 import org.junit.Test 53 import org.junit.runner.RunWith 54 import org.mockito.Mock 55 import org.mockito.Mockito.eq 56 import org.mockito.Mockito.never 57 import org.mockito.Mockito.verify 58 import org.mockito.MockitoAnnotations 59 60 @SmallTest 61 @RunWith(AndroidJUnit4::class) 62 @TestableLooper.RunWithLooper(setAsMainLooper = true) 63 class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { 64 65 @Mock private lateinit var starter: ActivityStarter 66 @Mock private lateinit var controller: RecordingController 67 @Mock private lateinit var userContextProvider: UserContextProvider 68 @Mock private lateinit var onStartRecordingClicked: Runnable 69 @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger 70 71 private lateinit var dialog: SystemUIDialog 72 73 @Before setUpnull74 fun setUp() { 75 MockitoAnnotations.initMocks(this) 76 77 val systemUIDialogFactory = 78 SystemUIDialog.Factory( 79 context, 80 Dependency.get(SystemUIDialogManager::class.java), 81 Dependency.get(SysUiState::class.java), 82 Dependency.get(BroadcastDispatcher::class.java), 83 Dependency.get(DialogTransitionAnimator::class.java), 84 ) 85 86 val delegate = 87 ScreenRecordPermissionDialogDelegate( 88 UserHandle.of(0), 89 TEST_HOST_UID, 90 controller, 91 starter, 92 userContextProvider, 93 onStartRecordingClicked, 94 mediaProjectionMetricsLogger, 95 systemUIDialogFactory, 96 context, 97 context.getSystemService(DisplayManager::class.java)!!, 98 ) 99 dialog = delegate.createDialog() 100 } 101 102 @After teardownnull103 fun teardown() { 104 if (::dialog.isInitialized) { 105 dismissDialog() 106 } 107 } 108 109 @Test testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisiblenull110 fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() { 111 showDialog() 112 113 val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options).visibility 114 assertThat(visibility).isEqualTo(View.VISIBLE) 115 } 116 117 @Test testShowDialog_singleAppSelected_showTapsIsGonenull118 fun testShowDialog_singleAppSelected_showTapsIsGone() { 119 showDialog() 120 onSpinnerItemSelected(SINGLE_APP) 121 122 val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility 123 assertThat(visibility).isEqualTo(View.GONE) 124 } 125 126 @Test testShowDialog_entireScreenSelected_showTapsIsVisiblenull127 fun testShowDialog_entireScreenSelected_showTapsIsVisible() { 128 showDialog() 129 onSpinnerItemSelected(ENTIRE_SCREEN) 130 131 val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility 132 assertThat(visibility).isEqualTo(View.VISIBLE) 133 } 134 135 @Test startButtonText_entireScreenSelectednull136 fun startButtonText_entireScreenSelected() { 137 showDialog() 138 139 onSpinnerItemSelected(ENTIRE_SCREEN) 140 141 assertThat(getStartButton().text) 142 .isEqualTo( 143 context.getString(R.string.screenrecord_permission_dialog_continue_entire_screen) 144 ) 145 } 146 147 @Test startButtonText_singleAppSelectednull148 fun startButtonText_singleAppSelected() { 149 showDialog() 150 151 onSpinnerItemSelected(SINGLE_APP) 152 153 assertThat(getStartButton().text) 154 .isEqualTo( 155 context.getString( 156 R.string.media_projection_entry_generic_permission_dialog_continue_single_app 157 ) 158 ) 159 } 160 161 @Test startClicked_singleAppSelected_passesHostUidToAppSelectornull162 fun startClicked_singleAppSelected_passesHostUidToAppSelector() { 163 showDialog() 164 onSpinnerItemSelected(SINGLE_APP) 165 166 clickOnStart() 167 168 assertExtraPassedToAppSelector( 169 extraKey = MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, 170 value = TEST_HOST_UID, 171 ) 172 } 173 174 @Test showDialog_dialogIsShowingnull175 fun showDialog_dialogIsShowing() { 176 showDialog() 177 178 assertThat(dialog.isShowing).isTrue() 179 } 180 181 @Test showDialog_singleAppIsDefaultnull182 fun showDialog_singleAppIsDefault() { 183 showDialog() 184 185 val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) 186 val singleApp = 187 context.getString(R.string.screenrecord_permission_dialog_option_text_single_app) 188 assertEquals(spinner.adapter.getItem(0), singleApp) 189 } 190 191 @Test showDialog_cancelClicked_dialogIsDismissednull192 fun showDialog_cancelClicked_dialogIsDismissed() { 193 showDialog() 194 195 clickOnCancel() 196 197 assertThat(dialog.isShowing).isFalse() 198 } 199 200 @Test showDialog_cancelClickedMultipleTimes_projectionRequestCancelledIsLoggedOncenull201 fun showDialog_cancelClickedMultipleTimes_projectionRequestCancelledIsLoggedOnce() { 202 showDialog() 203 204 clickOnCancel() 205 clickOnCancel() 206 207 verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID) 208 } 209 210 @Test dismissDialog_dismissCalledMultipleTimes_projectionRequestCancelledIsLoggedOncenull211 fun dismissDialog_dismissCalledMultipleTimes_projectionRequestCancelledIsLoggedOnce() { 212 showDialog() 213 214 dismissDialog() 215 dismissDialog() 216 217 verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID) 218 } 219 220 @Test showDialog_singleAppSelected_clickOnStart_projectionRequestCancelledIsNotLoggedOncenull221 fun showDialog_singleAppSelected_clickOnStart_projectionRequestCancelledIsNotLoggedOnce() { 222 showDialog() 223 onSpinnerItemSelected(SINGLE_APP) 224 225 clickOnStart() 226 227 verify(mediaProjectionMetricsLogger, never()) 228 .notifyProjectionRequestCancelled(TEST_HOST_UID) 229 } 230 231 @Test 232 @RequiresFlagsEnabled( 233 com.android.media.projection.flags.Flags 234 .FLAG_MEDIA_PROJECTION_CONNECTED_DISPLAY_NO_VIRTUAL_DEVICE 235 ) doNotShowVirtualDisplayInDialognull236 fun doNotShowVirtualDisplayInDialog() { 237 val displayManager = context.getSystemService(DisplayManager::class.java)!! 238 var virtualDisplay: VirtualDisplay? = null 239 try { 240 virtualDisplay = 241 displayManager.createVirtualDisplay( 242 VirtualDisplayConfig.Builder("virtual display", 1, 1, 160).build() 243 ) 244 showDialog() 245 val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) 246 val adapter = spinner.adapter 247 val virtualDisplayAvailable = 248 (0 until adapter.count) 249 .mapNotNull { adapter.getItem(it) as? String } 250 .any { it.contains("virtual display", ignoreCase = true) } 251 assertWithMessage("A Virtual Display was shown in the list of display to record") 252 .that(virtualDisplayAvailable) 253 .isFalse() 254 } finally { 255 virtualDisplay?.release() 256 } 257 } 258 showDialognull259 private fun showDialog() { 260 dialog.show() 261 } 262 dismissDialognull263 private fun dismissDialog() { 264 dialog.dismiss() 265 } 266 clickOnCancelnull267 private fun clickOnCancel() { 268 dialog.requireViewById<View>(android.R.id.button2).performClick() 269 } 270 getStartButtonnull271 private fun getStartButton() = dialog.requireViewById<TextView>(android.R.id.button1) 272 273 private fun clickOnStart() { 274 getStartButton().performClick() 275 } 276 onSpinnerItemSelectednull277 private fun onSpinnerItemSelected(position: Int) { 278 val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) 279 checkNotNull(spinner.onItemSelectedListener) 280 .onItemSelected(spinner, mock(), position, /* id= */ 0) 281 } 282 assertExtraPassedToAppSelectornull283 private fun assertExtraPassedToAppSelector(extraKey: String, value: Int) { 284 val intentCaptor = argumentCaptor<Intent>() 285 verify(starter).startActivity(intentCaptor.capture(), /* dismissShade= */ eq(true)) 286 287 val intent = intentCaptor.value 288 assertThat(intent.extras!!.getInt(extraKey)).isEqualTo(value) 289 } 290 291 companion object { 292 private const val TEST_HOST_UID = 12345 293 } 294 } 295