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 package android.app.cts.wallpapers 17 18 import android.app.WallpaperColors 19 import android.os.Bundle 20 import android.os.Looper 21 import android.service.wallpaper.WallpaperService 22 import android.util.Log 23 import android.view.MotionEvent 24 import android.view.SurfaceHolder 25 import android.view.WindowInsets 26 import com.google.common.truth.Truth.assertWithMessage 27 import java.util.concurrent.CountDownLatch 28 import java.util.concurrent.TimeUnit 29 30 /** 31 * Wrapper for WallpaperService. 32 * This class does not add any logic or change any function. 33 * It verifies that the public callback methods from [WallpaperService.Engine] 34 * are called by the main thread. 35 * The callback methods are the methods overridden by this class. 36 * 37 * It also includes a few checks to verify that the methods are called in a proper order. 38 * For example, many methods should only be called after [WallpaperService.Engine.onSurfaceCreated], 39 * which itself should only be called after [WallpaperService.Engine.onCreate]. 40 */ 41 abstract class TestWallpaperService : WallpaperService() { 42 43 private val mainThread: Thread = Looper.getMainLooper().thread 44 companion object { 45 private val TAG = TestWallpaperService::class.java.simpleName 46 private const val DEBUG = true 47 private var assertionError: AssertionError? = null 48 private var prevAction: String? = null 49 private var engineStateCallbacks = mutableSetOf<EngineCallback>() 50 51 /** 52 * Tracks the number of times [FakeEngine.onCreate] is called 53 */ 54 var createCount: Int = 0 55 56 /** 57 * Tracks the number of times [FakeEngine.onDestroy] is called 58 */ 59 var destroyCount: Int = 0 60 resetCountsnull61 fun resetCounts() { 62 createCount = 0 63 destroyCount = 0 64 } 65 66 /** 67 * To be called at the end of tests requiring assertion checks from this class. 68 * The first assertion error encountered by this class, if there is any, 69 * will be raised when calling this function. 70 * We use this to avoid raising errors directly in the callback methods, since errors in 71 * callback methods could be raised from the main thread and crash the entire test process. 72 */ checkAssertionsnull73 fun checkAssertions() { 74 assertionError?.let { throw assertionError!! } 75 assertionError = null 76 } 77 78 /** 79 * Allow user to peek the previous handle command 80 */ getPrevActionnull81 fun getPrevAction(): String? { 82 return prevAction 83 } 84 resetPrevActionnull85 fun resetPrevAction() { 86 prevAction = null 87 } 88 addCallbacknull89 fun addCallback(callback: EngineCallback) { 90 engineStateCallbacks.add(callback) 91 } 92 removeCallbacknull93 fun removeCallback(callback: EngineCallback) { 94 engineStateCallbacks.remove(callback) 95 } 96 } 97 onCreateEnginenull98 override fun onCreateEngine(): Engine { 99 if (DEBUG) Log.d(TAG, "onCreateEngine") 100 assertMainThread() 101 return FakeEngine() 102 } 103 104 internal inner class FakeEngine : Engine() { 105 private var mCreated = false 106 private var mSurfaceCreated = false 107 drawnull108 private fun draw(holder: SurfaceHolder) { 109 val c = holder.lockCanvas() 110 c.drawColor(getColor()) 111 holder.unlockCanvasAndPost(c) 112 } 113 onAmbientModeChangednull114 override fun onAmbientModeChanged(inAmbientMode: Boolean, animationDuration: Long) { 115 if (DEBUG) Log.d(TAG, "onAmbientModeChanged") 116 assertMainThread() 117 super.onAmbientModeChanged(inAmbientMode, animationDuration) 118 } 119 onApplyWindowInsetsnull120 override fun onApplyWindowInsets(insets: WindowInsets) { 121 if (DEBUG) Log.d(TAG, "onApplyWindowInsets") 122 assertMainThread() 123 super.onApplyWindowInsets(insets) 124 } 125 onCommandnull126 override fun onCommand( 127 action: String, 128 x: Int, 129 y: Int, 130 z: Int, 131 extras: Bundle?, 132 resultRequested: Boolean 133 ): Bundle? { 134 if (DEBUG) Log.d(TAG, "onCommand") 135 assertMainThread() 136 prevAction = action 137 return super.onCommand(action, x, y, z, extras, resultRequested) 138 } 139 onComputeColorsnull140 override fun onComputeColors(): WallpaperColors? { 141 if (DEBUG) Log.d(TAG, "onComputeColors") 142 assertMainThread() 143 return super.onComputeColors() 144 } 145 onCreatenull146 override fun onCreate(surfaceHolder: SurfaceHolder) { 147 if (DEBUG) Log.d(TAG, "onCreate") 148 assertMainThread() 149 assertNotCreated() 150 assertSurfaceNotCreated() 151 mCreated = true 152 createCount++ 153 super.onCreate(surfaceHolder) 154 engineStateCallbacks.forEach { it.onEvent(EngineCallback.Event.CREATE) } 155 } 156 onDesiredSizeChangednull157 override fun onDesiredSizeChanged(desiredWidth: Int, desiredHeight: Int) { 158 if (DEBUG) Log.d(TAG, "onDesiredSizeChanged") 159 assertMainThread() 160 assertCreated() 161 assertSurfaceCreated() 162 super.onDesiredSizeChanged(desiredWidth, desiredHeight) 163 } 164 onDestroynull165 override fun onDestroy() { 166 if (DEBUG) Log.d(TAG, "onDestroy, new count=" + (destroyCount + 1)) 167 assertMainThread() 168 assertCreated() 169 mCreated = false 170 destroyCount++ 171 super.onDestroy() 172 engineStateCallbacks.forEach { it.onEvent(EngineCallback.Event.DESTROY) } 173 } 174 onOffsetsChangednull175 override fun onOffsetsChanged( 176 xOffset: Float, 177 yOffset: Float, 178 xOffsetStep: Float, 179 yOffsetStep: Float, 180 xPixelOffset: Int, 181 yPixelOffset: Int 182 ) { 183 if (DEBUG) Log.d(TAG, "onOffsetsChanged") 184 assertMainThread() 185 super.onOffsetsChanged( 186 xOffset, 187 yOffset, 188 xOffsetStep, 189 yOffsetStep, 190 xPixelOffset, 191 yPixelOffset 192 ) 193 } 194 onSurfaceChangednull195 override fun onSurfaceChanged( 196 holder: SurfaceHolder, 197 format: Int, 198 width: Int, 199 height: Int 200 ) { 201 if (DEBUG) Log.d(TAG, "onSurfaceChanged") 202 assertMainThread() 203 assertCreated() 204 assertSurfaceCreated() 205 super.onSurfaceChanged(holder, format, width, height) 206 } 207 onSurfaceCreatednull208 override fun onSurfaceCreated(holder: SurfaceHolder) { 209 if (DEBUG) Log.d(TAG, "onSurfaceCreated") 210 assertMainThread() 211 assertCreated() 212 assertSurfaceNotCreated() 213 mSurfaceCreated = true 214 super.onSurfaceCreated(holder) 215 engineStateCallbacks.forEach { it.onEvent(EngineCallback.Event.SURFACE_CREATE) } 216 } 217 onSurfaceDestroyednull218 override fun onSurfaceDestroyed(holder: SurfaceHolder) { 219 if (DEBUG) Log.d(TAG, "onSurfaceDestroyed") 220 assertMainThread() 221 assertCreated() 222 assertSurfaceCreated() 223 super.onSurfaceDestroyed(holder) 224 } 225 onSurfaceRedrawNeedednull226 override fun onSurfaceRedrawNeeded(holder: SurfaceHolder) { 227 if (DEBUG) Log.d(TAG, "onSurfaceRedrawNeeded") 228 draw(holder) 229 assertMainThread() 230 assertCreated() 231 assertSurfaceCreated() 232 super.onSurfaceRedrawNeeded(holder) 233 } 234 onTouchEventnull235 override fun onTouchEvent(event: MotionEvent) { 236 if (DEBUG) Log.d(TAG, "onTouchEvent") 237 assertMainThread() 238 super.onTouchEvent(event) 239 } 240 onVisibilityChangednull241 override fun onVisibilityChanged(visible: Boolean) { 242 if (DEBUG) Log.d(TAG, "onVisibilityChanged") 243 assertMainThread() 244 super.onVisibilityChanged(visible) 245 } 246 onZoomChangednull247 override fun onZoomChanged(zoom: Float) { 248 if (DEBUG) Log.d(TAG, "onZoomChanged") 249 assertMainThread() 250 super.onZoomChanged(zoom) 251 } 252 assertCreatednull253 private fun assertCreated() { 254 assertHelper { 255 assertWithMessage( 256 "Engine must be created (with onCreate) " + 257 "and not destroyed before calling this function" 258 ) 259 .that(mCreated).isTrue() 260 } 261 } 262 assertSurfaceCreatednull263 private fun assertSurfaceCreated() { 264 assertHelper { 265 assertWithMessage( 266 "Surface must be created (with onSurfaceCreated) " + 267 "and not destroyed before calling this function" 268 ) 269 .that(mSurfaceCreated).isTrue() 270 } 271 } 272 assertNotCreatednull273 private fun assertNotCreated() { 274 assertHelper { 275 assertWithMessage( 276 "Engine must not be created (with onCreate) " + 277 "or must be destroyed before calling this function" 278 ) 279 .that(mCreated).isFalse() 280 } 281 } 282 assertSurfaceNotCreatednull283 private fun assertSurfaceNotCreated() { 284 assertHelper { 285 assertWithMessage( 286 "Surface must not be created (with onSurfaceCreated) " + 287 "or must be destroyed before calling this function" 288 ) 289 .that(mSurfaceCreated).isFalse() 290 } 291 } 292 } 293 294 /** 295 * Check that the current thread is the main thread 296 */ assertMainThreadnull297 private fun assertMainThread() { 298 assertHelper { 299 val callerThread = Thread.currentThread() 300 assertWithMessage( 301 "Callback methods from WallpaperService.Engine " + 302 "must be called by the main thread; but was called by " + callerThread 303 ) 304 .that(callerThread).isSameInstanceAs(mainThread) 305 } 306 } 307 308 /** 309 * Run an executable that performs some assertions. If any assertion is raised, and it is the 310 * first one raised so far, store it. 311 */ assertHelpernull312 private fun assertHelper(check: Runnable) { 313 try { 314 check.run() 315 } catch (error: AssertionError) { 316 assertionError = assertionError ?: error 317 } 318 } 319 320 /** 321 * The color that this test wallpaper should draw, for debug purposes. 322 */ getColornull323 protected abstract fun getColor(): Int 324 325 interface EngineCallback { 326 enum class Event { CREATE, DESTROY, SURFACE_CREATE } 327 328 fun onEvent(e: Event) 329 } 330 331 class EngineCallbackCountdown(create: Int, destroy: Int, surfaceCreate: Int) : EngineCallback { 332 private val createLatch = CountDownLatch(create) 333 private val destroyLatch = CountDownLatch(destroy) 334 private val surfaceCreateLatch = CountDownLatch(surfaceCreate) 335 onEventnull336 override fun onEvent(e: EngineCallback.Event) { 337 when (e) { 338 EngineCallback.Event.CREATE -> createLatch.countDown() 339 EngineCallback.Event.DESTROY -> destroyLatch.countDown() 340 EngineCallback.Event.SURFACE_CREATE -> surfaceCreateLatch.countDown() 341 } 342 } 343 awaitEventsnull344 fun awaitEvents(time: Long, units: TimeUnit): Boolean { 345 return (createLatch.await(time, units) && 346 destroyLatch.await(time, units) && 347 surfaceCreateLatch.await(time, units)) 348 } 349 } 350 } 351