1 /* <lambda>null2 * Copyright 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 androidx.graphics.surface 18 19 import android.app.Instrumentation 20 import android.graphics.Bitmap 21 import android.graphics.Color 22 import android.graphics.Rect 23 import android.hardware.HardwareBuffer 24 import android.os.Build 25 import android.os.SystemClock 26 import android.view.SurfaceHolder 27 import android.view.SurfaceView 28 import android.view.Window 29 import androidx.annotation.RequiresApi 30 import androidx.lifecycle.Lifecycle 31 import androidx.test.core.app.ActivityScenario 32 import androidx.test.filters.SdkSuppress 33 import androidx.test.platform.app.InstrumentationRegistry 34 import java.util.concurrent.CountDownLatch 35 import java.util.concurrent.Executors 36 import java.util.concurrent.TimeUnit 37 import org.junit.Assert 38 39 @SdkSuppress(minSdkVersion = 29) 40 internal class SurfaceControlUtils { 41 companion object { 42 43 @RequiresApi(Build.VERSION_CODES.Q) 44 fun surfaceControlTestHelper( 45 onSurfaceCreated: (SurfaceView, CountDownLatch) -> Unit, 46 verifyOutput: (Bitmap, Rect) -> Boolean 47 ) { 48 val setupLatch = CountDownLatch(1) 49 var surfaceView: SurfaceView? = null 50 val destroyLatch = CountDownLatch(1) 51 val scenario = 52 ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java).onActivity { 53 it.setDestroyCallback { destroyLatch.countDown() } 54 val callback = 55 object : SurfaceHolder.Callback { 56 override fun surfaceCreated(sh: SurfaceHolder) { 57 surfaceView = it.mSurfaceView 58 onSurfaceCreated(surfaceView!!, setupLatch) 59 } 60 61 override fun surfaceChanged( 62 holder: SurfaceHolder, 63 format: Int, 64 width: Int, 65 height: Int 66 ) { 67 // NO-OP 68 } 69 70 override fun surfaceDestroyed(holder: SurfaceHolder) { 71 // NO-OP 72 } 73 } 74 75 it.addSurface(it.mSurfaceView, callback) 76 surfaceView = it.mSurfaceView 77 } 78 79 Assert.assertTrue(setupLatch.await(3000, TimeUnit.MILLISECONDS)) 80 val coords = intArrayOf(0, 0) 81 surfaceView!!.getLocationOnScreen(coords) 82 try { 83 validateOutput { bitmap -> 84 verifyOutput( 85 bitmap, 86 Rect( 87 coords[0], 88 coords[1], 89 coords[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH, 90 coords[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT 91 ) 92 ) 93 } 94 } finally { 95 scenario.moveToState(Lifecycle.State.DESTROYED) 96 Assert.assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS)) 97 } 98 } 99 100 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 101 fun validateOutput(window: Window, block: (bitmap: Bitmap) -> Boolean) { 102 val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation 103 val bitmap = uiAutomation.takeScreenshot(window) 104 if (bitmap != null) { 105 block(bitmap) 106 } else { 107 throw IllegalArgumentException("Unable to obtain bitmap from screenshot") 108 } 109 } 110 111 private fun flushSurfaceFlinger() { 112 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 113 var commitLatch: CountDownLatch? = null 114 // Android S only requires 1 additional transaction 115 val maxTransactions = 116 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 117 1 118 } else { 119 3 120 } 121 for (i in 0 until maxTransactions) { 122 val transaction = SurfaceControlCompat.Transaction() 123 // CommittedListener only added on Android S 124 if ( 125 i == maxTransactions - 1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S 126 ) { 127 commitLatch = CountDownLatch(1) 128 val executor = Executors.newSingleThreadExecutor() 129 transaction.addTransactionCommittedListener( 130 executor, 131 object : SurfaceControlCompat.TransactionCommittedListener { 132 override fun onTransactionCommitted() { 133 executor.shutdownNow() 134 commitLatch.countDown() 135 } 136 } 137 ) 138 } 139 transaction.commit() 140 } 141 142 if (commitLatch != null) { 143 commitLatch.await(3000, TimeUnit.MILLISECONDS) 144 } else { 145 // Wait for transactions to flush 146 SystemClock.sleep(maxTransactions * 16L) 147 } 148 } 149 } 150 151 fun validateOutput(block: (bitmap: Bitmap) -> Boolean) { 152 flushSurfaceFlinger() 153 var sleepDurationMillis = 1000L 154 var success = false 155 for (i in 0..3) { 156 val bitmap = getScreenshot(InstrumentationRegistry.getInstrumentation()) 157 success = block(bitmap) 158 if (!success) { 159 SystemClock.sleep(sleepDurationMillis) 160 sleepDurationMillis *= 2 161 } else { 162 break 163 } 164 } 165 Assert.assertTrue(success) 166 } 167 168 fun checkNullCrop(bitmap: Bitmap, coord: IntArray): Boolean { 169 // check top left 170 return Color.RED == bitmap.getPixel(coord[0], coord[1]) && 171 // check top right 172 Color.RED == 173 bitmap.getPixel( 174 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH - 1, 175 coord[1] 176 ) && 177 // check bottom right 178 Color.RED == 179 bitmap.getPixel( 180 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH - 1, 181 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT - 1 182 ) && 183 // check bottom left 184 Color.RED == 185 bitmap.getPixel( 186 coord[0], 187 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT - 1 188 ) && 189 // check center 190 Color.RED == 191 bitmap.getPixel( 192 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2, 193 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 194 ) 195 } 196 197 fun checkStandardCrop(bitmap: Bitmap, coord: IntArray): Boolean { 198 // check left crop 199 return Color.BLACK == 200 bitmap.getPixel( 201 coord[0] + 19, 202 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 203 ) && 204 Color.RED == 205 bitmap.getPixel( 206 coord[0] + 20, 207 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 208 ) && 209 // check top crop 210 Color.BLACK == 211 bitmap.getPixel( 212 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2, 213 coord[1] + 29 214 ) && 215 Color.RED == 216 bitmap.getPixel( 217 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2, 218 coord[1] + 30 219 ) && 220 // check right crop 221 Color.BLACK == 222 bitmap.getPixel( 223 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH - 10, 224 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 225 ) && 226 Color.RED == 227 bitmap.getPixel( 228 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT - 11, 229 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 230 ) && 231 // check bottom crop 232 Color.BLACK == 233 bitmap.getPixel( 234 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2, 235 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT - 40 236 ) && 237 Color.RED == 238 bitmap.getPixel( 239 coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2, 240 coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT - 41 241 ) 242 } 243 244 fun getScreenshot(instrumentation: Instrumentation): Bitmap { 245 val uiAutomation = instrumentation.uiAutomation 246 val screenshot = uiAutomation.takeScreenshot() 247 return screenshot 248 } 249 250 fun getSolidBuffer(width: Int, height: Int, color: Int): HardwareBuffer { 251 return nGetSolidBuffer(width, height, color) 252 } 253 254 fun getQuadrantBuffer( 255 width: Int, 256 height: Int, 257 colorTopLeft: Int, 258 colorTopRight: Int, 259 colorBottomRight: Int, 260 colorBottomLeft: Int 261 ): HardwareBuffer { 262 return nGetQuadrantBuffer( 263 width, 264 height, 265 colorTopLeft, 266 colorTopRight, 267 colorBottomRight, 268 colorBottomLeft 269 ) 270 } 271 272 private external fun nGetSolidBuffer(width: Int, height: Int, color: Int): HardwareBuffer 273 274 private external fun nGetQuadrantBuffer( 275 width: Int, 276 height: Int, 277 colorTopLeft: Int, 278 colorTopRight: Int, 279 colorBottomRight: Int, 280 colorBottomLeft: Int 281 ): HardwareBuffer 282 283 init { 284 System.loadLibrary("graphics-core") 285 } 286 } 287 } 288