xref: /aosp_15_r20/cts/libs/input/src/com/android/cts/input/VirtualDisplayActivityScenario.kt (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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