1 /*
2 * Copyright (C) 2023 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.cts.input
17
18 import android.Manifest
19 import android.app.Activity
20 import android.app.ActivityOptions
21 import android.content.Intent
22 import android.content.pm.PackageManager
23 import android.content.pm.PackageManager.PERMISSION_GRANTED
24 import android.graphics.Bitmap
25 import android.graphics.PixelFormat
26 import android.hardware.display.DisplayManager
27 import android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE
28 import android.hardware.display.VirtualDisplay
29 import android.hardware.display.VirtualDisplayConfig
30 import android.media.ImageReader
31 import android.os.Handler
32 import android.os.Looper
33 import android.os.Process
34 import android.server.wm.WindowManagerStateHelper
35 import android.util.Size
36 import android.view.Surface
37 import android.virtualdevice.cts.common.VirtualDeviceRule
38 import androidx.test.platform.app.InstrumentationRegistry
39 import com.android.compatibility.common.util.SystemUtil
40 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.CAPTURE_SECURE_VIDEO_OUTPUT
41 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.DENSITY
42 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.TAG
43 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
44 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
45 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.VIRTUAL_DISPLAY_FLAG_TRUSTED
46 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.VIRTUAL_DISPLAY_NAME
47 import java.nio.ByteBuffer
48 import java.util.concurrent.CountDownLatch
49 import java.util.concurrent.TimeUnit
50 import junit.framework.AssertionFailedError
51 import org.junit.Assert.assertTrue
52 import org.junit.Assume.assumeTrue
53 import org.junit.rules.ExternalResource
54 import org.junit.rules.TestName
55
56 interface VirtualDisplayActivityScenario<A : Activity> {
57 companion object {
58 const val TAG = "VirtualDisplayActivityScenario"
59 const val VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay"
60 const val CAPTURE_SECURE_VIDEO_OUTPUT = "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
61 const val DEFAULT_WIDTH = 480
62 const val DEFAULT_HEIGHT = 800
63 const val DENSITY = 160
64 const val ORIENTATION_0 = Surface.ROTATION_0
65 const val ORIENTATION_90 = Surface.ROTATION_90
66 const val ORIENTATION_180 = Surface.ROTATION_180
67 const val ORIENTATION_270 = Surface.ROTATION_270
68
69 /** See [DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH]. */
70 const val VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 shl 6
71
72 /** See [DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT]. */
73 const val VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 shl 7
74
75 /** See [DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED]. */
76 const val VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 shl 10
77 }
78
79 var virtualDisplay: VirtualDisplay
80 var activity: A
81 val displayId: Int get() = virtualDisplay.display.displayId
82
83 /**
84 * This is a helper methods for tests to make assertions with the display rotated to the given
85 * orientation.
86 *
87 * @param orientation The orientation to which the display should be rotated.
88 * @param runnable The function to run with the display is in the given orientation.
89 */
runInDisplayOrientationnull90 fun runInDisplayOrientation(orientation: Int, runnable: () -> Unit)
91
92 /**
93 * Retrieves a Bitmap screenshot from the internal ImageReader this virtual display writes to.
94 *
95 * <p>Currently only supports screenshots in the RGBA_8888.
96 */
97 fun getScreenshot(): Bitmap?
98
99 /**
100 * A [VirtualDisplayActivityScenario] that can be used within a test function.
101 */
102 class AutoClose<A : Activity> private constructor(
103 private val impl: Impl<A>
104 ) : AutoCloseable, VirtualDisplayActivityScenario<A> by impl {
105
106 companion object {
107 inline operator fun <reified A : Activity> invoke(
108 testName: TestName,
109 useSecureDisplay: Boolean = false,
110 size: Size = Size(DEFAULT_WIDTH, DEFAULT_HEIGHT),
111 ): AutoClose<A> = AutoClose(
112 A::class.java,
113 testName,
114 useSecureDisplay,
115 size,
116 )
117 }
118
119 constructor(
120 type: Class<A>,
121 testName: TestName,
122 useSecureDisplay: Boolean,
123 size: Size
124 ) : this(Impl(type, testName, useSecureDisplay, size))
125
126 init {
127 impl.start()
128 }
129
130 override fun close() {
131 impl.stop()
132 }
133 }
134
135 /**
136 * A [VirtualDisplayActivityScenario] that can be used as a test rule.
137 */
138 class Rule<A : Activity> private constructor(
139 private val impl: Impl<A>
140 ) : ExternalResource(), VirtualDisplayActivityScenario<A> by impl {
141
142 companion object {
invokenull143 inline operator fun <reified A : Activity> invoke(
144 testName: TestName,
145 useSecureDisplay: Boolean = false,
146 size: Size = Size(DEFAULT_WIDTH, DEFAULT_HEIGHT),
147 virtualDeviceRule: VirtualDeviceRule? = null,
148 ): Rule<A> = Rule(
149 A::class.java,
150 testName,
151 useSecureDisplay,
152 size,
153 virtualDeviceRule,
154 )
155 }
156
157 constructor(
158 type: Class<A>,
159 testName: TestName,
160 useSecureDisplay: Boolean,
161 size: Size,
162 virtualDeviceRule: VirtualDeviceRule? = null,
163 ) : this(Impl(type, testName, useSecureDisplay, size, virtualDeviceRule))
164
165 override fun before() {
166 impl.start()
167 }
168
afternull169 override fun after() {
170 impl.stop()
171 }
172 }
173 }
174
175 /**
176 * The private implementation of a [VirtualDisplayActivityScenario].
177 */
178 private class Impl<A : Activity>(
179 val type: Class<A>,
180 val testName: TestName,
181 val useSecureDisplay: Boolean,
182 val size: Size,
183 // If provided, creates the VirtualDisplay using VDM instead of DisplayManager.
184 // TODO(b/366492484): Remove reliance on VDM when we achieve feature parity between VDM
185 // and display + input APIs.
186 val virtualDeviceRule: VirtualDeviceRule? = null,
187 ) : VirtualDisplayActivityScenario<A> {
188
189 private val instrumentation = InstrumentationRegistry.getInstrumentation()
190 private lateinit var reader: ImageReader
191
192 override lateinit var virtualDisplay: VirtualDisplay
193 override lateinit var activity: A
194
195 /** Set up the virtual display and start the activity A on that display. */
startnull196 fun start() {
197 assumeTrue(supportsMultiDisplay())
198 reader = ImageReader.newInstance(size.width, size.height, PixelFormat.RGBA_8888, 2)
199
200 if (virtualDeviceRule != null) {
201 createDisplayUsingVirtualDeviceManager()
202 } else {
203 createDisplay()
204 }
205
206 val bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle()
207 val intent =
208 Intent(Intent.ACTION_VIEW).setClass(instrumentation.targetContext, type)
209 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
210 SystemUtil.runWithShellPermissionIdentity({
211 @Suppress("UNCHECKED_CAST")
212 activity = instrumentation.startActivitySync(intent, bundle) as A
213 }, Manifest.permission.INTERNAL_SYSTEM_WINDOW)
214 waitUntilActivityReadyForInput()
215 }
216
217 /** Clean up the resources related to the virtual display and the test activity. */
stopnull218 fun stop() {
219 if (!supportsMultiDisplay()) {
220 return
221 }
222 releaseDisplay()
223 activity.finish()
224 }
225
runInDisplayOrientationnull226 override fun runInDisplayOrientation(orientation: Int, runnable: () -> Unit) {
227 val initialUserRotation =
228 SystemUtil.runShellCommandOrThrow("wm user-rotation -d $displayId")!!
229 SystemUtil.runShellCommandOrThrow("wm user-rotation -d $displayId lock $orientation")
230 waitUntilActivityReadyForInput()
231
232 try {
233 runnable()
234 } finally {
235 SystemUtil.runShellCommandOrThrow(
236 "wm user-rotation -d $displayId $initialUserRotation"
237 )
238 }
239 }
240
supportsMultiDisplaynull241 private fun supportsMultiDisplay(): Boolean {
242 return instrumentation.targetContext.packageManager
243 .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)
244 }
245
createDisplaynull246 private fun createDisplay() {
247 val displayManager =
248 instrumentation.targetContext.getSystemService(DisplayManager::class.java)
249
250 val displayCreated = CountDownLatch(1)
251 displayManager.registerDisplayListener(object : DisplayManager.DisplayListener {
252 override fun onDisplayAdded(displayId: Int) {}
253 override fun onDisplayRemoved(displayId: Int) {}
254 override fun onDisplayChanged(displayId: Int) {
255 displayCreated.countDown()
256 displayManager.unregisterDisplayListener(this)
257 }
258 }, Handler(Looper.getMainLooper()))
259
260 val runWithPermissions =
261 if (useSecureDisplay) this::runWithSecureDisplayPermission else ::defaultRunner
262 val flags = VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH or
263 VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT or
264 (if (useSecureDisplay) VIRTUAL_DISPLAY_FLAG_SECURE else 0)
265
266 runWithPermissions {
267 virtualDisplay = displayManager.createVirtualDisplay(
268 VIRTUAL_DISPLAY_NAME, size.width, size.height, DENSITY, reader.surface, flags
269 )
270 }
271 assertTrue(displayCreated.await(5, TimeUnit.SECONDS))
272 }
273
createDisplayUsingVirtualDeviceManagernull274 private fun createDisplayUsingVirtualDeviceManager() {
275 val runWithPermissions =
276 if (useSecureDisplay) this::runWithSecureDisplayPermission else ::defaultRunner
277 val flags = VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH or
278 VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT or
279 VIRTUAL_DISPLAY_FLAG_TRUSTED or
280 (if (useSecureDisplay) VIRTUAL_DISPLAY_FLAG_SECURE else 0)
281
282 runWithPermissions {
283 val virtualDevice = virtualDeviceRule!!.defaultVirtualDevice
284 virtualDevice.setShowPointerIcon(true)
285 virtualDisplay =
286 virtualDevice.createVirtualDisplay(
287 VirtualDisplayConfig.Builder(
288 VIRTUAL_DISPLAY_NAME,
289 size.width,
290 size.height,
291 DENSITY,
292 )
293 .setSurface(reader.surface)
294 .setFlags(flags)
295 .build(),
296 /*executor*/
297 null,
298 /*callback*/
299 null,
300 ) ?: throw AssertionFailedError("Failed to create virtual display")
301 }
302 }
303
304 /**
305 * NOTE: Running a test with the [CAPTURE_SECURE_VIDEO_OUTPUT] permission requires the test
306 * suite to be run as root.
307 */
runWithSecureDisplayPermissionnull308 private fun runWithSecureDisplayPermission(command: () -> Unit) {
309 instrumentation.uiAutomation.addOverridePermissionState(
310 Process.myUid(),
311 CAPTURE_SECURE_VIDEO_OUTPUT,
312 PERMISSION_GRANTED
313 )
314 try {
315 return command()
316 } finally {
317 instrumentation.uiAutomation.clearOverridePermissionStates(Process.myUid())
318 }
319 }
320
releaseDisplaynull321 private fun releaseDisplay() {
322 virtualDisplay.release()
323 reader.close()
324 }
325
waitUntilActivityReadyForInputnull326 private fun waitUntilActivityReadyForInput() {
327 // If we requested an orientation change, just waiting for the window to be visible is
328 // not sufficient. We should first wait for the transitions to stop, and the for app's
329 // UI thread to process them before making sure the window is visible.
330 WindowManagerStateHelper().waitUntilActivityReadyForInputInjection(
331 activity,
332 instrumentation,
333 TAG,
334 "test: ${testName.methodName}, virtualDisplayId=$displayId"
335 )
336 }
337
getScreenshotnull338 override fun getScreenshot(): Bitmap? {
339 val image = reader.acquireNextImage()
340 if (image == null || image.format != PixelFormat.RGBA_8888) {
341 return null
342 }
343 val buffer = image.planes[0].getBuffer()
344 val pixelStrideBytes: Int = image.planes[0].getPixelStride()
345 val rowStrideBytes: Int = image.planes[0].getRowStride()
346 val pixelBytesPerRow = pixelStrideBytes * image.width
347 val rowPaddingBytes = rowStrideBytes - pixelBytesPerRow
348
349 // Remove the row padding bytes from the buffer before converting to a Bitmap
350 val trimmedBuffer = ByteBuffer.allocate(buffer.remaining())
351 buffer.rewind()
352 while (buffer.hasRemaining()) {
353 for (i in 0 until pixelBytesPerRow) {
354 trimmedBuffer.put(buffer.get())
355 }
356 // Skip the padding bytes
357 buffer.position(buffer.position() + rowPaddingBytes)
358 }
359 trimmedBuffer.flip() // Prepare the trimmed buffer for reading
360
361 val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
362 bitmap.copyPixelsFromBuffer(trimmedBuffer)
363 return bitmap
364 }
365 }
366
defaultRunnernull367 private fun defaultRunner(command: () -> Unit) = command()
368