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