xref: /aosp_15_r20/cts/tests/app/WallpaperTest/src/android/app/cts/wallpapers/WallpaperManagerTestUtils.java (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 
17 package android.app.cts.wallpapers;
18 
19 import static android.app.WallpaperManager.FLAG_LOCK;
20 import static android.app.WallpaperManager.FLAG_SYSTEM;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import android.app.WallpaperColors;
25 import android.app.WallpaperManager;
26 import android.content.ComponentName;
27 import android.os.Handler;
28 import android.util.Log;
29 
30 import androidx.annotation.Nullable;
31 
32 import com.android.compatibility.common.util.ThrowingRunnable;
33 
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Set;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 import java.util.stream.Stream;
41 
42 public class WallpaperManagerTestUtils {
43 
44     private static final String TAG = "WallpaperManagerTest";
45 
46     public static final ComponentName TEST_LIVE_WALLPAPER_COMPONENT = new ComponentName(
47             TestLiveWallpaper.class.getPackageName(),
48             TestLiveWallpaper.class.getName());
49 
50     public static final ComponentName TEST_LIVE_WALLPAPER_NO_UNFOLD_COMPONENT = new ComponentName(
51             TestLiveWallpaperNoUnfoldTransition.class.getPackageName(),
52             TestLiveWallpaperNoUnfoldTransition.class.getName());
53 
54     public static final ComponentName TEST_LIVE_WALLPAPER_AMBIENT_COMPONENT = new ComponentName(
55             TestLiveWallpaperSupportingAmbientMode.class.getPackageName(),
56             TestLiveWallpaperSupportingAmbientMode.class.getName());
57 
58     private static final Set<ComponentName> TEST_COMPONENTS = Set.of(
59             TEST_LIVE_WALLPAPER_COMPONENT,
60             TEST_LIVE_WALLPAPER_NO_UNFOLD_COMPONENT,
61             TEST_LIVE_WALLPAPER_AMBIENT_COMPONENT);
62 
63     /**
64      * Runs the given runnable and waits until request number of engine events are triggered.
65      *
66      * @param timeout              Amount of time to wait.
67      * @param unit                 Unit for the timeout.
68      * @param onCreateCount        Number of times Engine::onCreate is expected to be called.
69      * @param onDestroyCount       Number of times Engine::onDestroy is expected to be called.
70      * @param surfaceCreationCount Number of times Engine::onSurfaceChanged is expected to be
71      *                             called.
72      * @param runnable             The action to perform that will trigger engine events.
73      * @return True if all events were received, false if there was a timeout waiting for any of
74      * the events.
75      */
runAndAwaitChanges(long timeout, TimeUnit unit, int onCreateCount, int onDestroyCount, int surfaceCreationCount, ThrowingRunnable runnable)76     public static boolean runAndAwaitChanges(long timeout, TimeUnit unit,
77             int onCreateCount, int onDestroyCount, int surfaceCreationCount,
78             ThrowingRunnable runnable) {
79         TestWallpaperService.EngineCallbackCountdown callback =
80                 new TestWallpaperService.EngineCallbackCountdown(onCreateCount, onDestroyCount,
81                         surfaceCreationCount);
82         TestWallpaperService.Companion.addCallback(callback);
83         boolean result;
84         try {
85             runnable.run();
86             result = callback.awaitEvents(timeout, unit);
87         } catch (Exception e) {
88             throw new RuntimeException("Caught exception", e);
89         } finally {
90             TestWallpaperService.Companion.removeCallback(callback);
91         }
92 
93         return result;
94     }
95 
96     /**
97      * Runs the given runnable and waits until request number of color change events are triggered.
98      * @param timeout          Amount of time to wait.
99      * @param unit             Unit for the timeout.
100      * @param whichColors      Flags for which we expect a color change callback.
101      * @param wallpaperManager Instance of WallpaperManager that will register the color listener.
102      * @param handler          Handler that will receive the color callbacks.
103      * @param runnable         The action to perform that will trigger engine events.
104      * @return True if all events were received, false if there was a timeout waiting for any of
105      * the events.
106      *
107      * @see #runAndAwaitChanges(long, TimeUnit, int, int, int, ThrowingRunnable)
108      */
runAndAwaitColorChanges(long timeout, TimeUnit unit, int whichColors, WallpaperManager wallpaperManager, Handler handler, ThrowingRunnable runnable)109     public static boolean runAndAwaitColorChanges(long timeout, TimeUnit unit,
110             int whichColors, WallpaperManager wallpaperManager, Handler handler,
111             ThrowingRunnable runnable) {
112         assertThat(whichColors).isIn(List.of(FLAG_LOCK, FLAG_SYSTEM, FLAG_LOCK | FLAG_SYSTEM));
113         ColorChangeWaiter callback = new ColorChangeWaiter(whichColors);
114         wallpaperManager.addOnColorsChangedListener(callback, handler);
115         boolean result;
116         try {
117             runnable.run();
118             result = callback.waitForChanges(timeout, unit);
119         } catch (Exception e) {
120             throw new RuntimeException("Caught exception", e);
121         } finally {
122             wallpaperManager.removeOnColorsChangedListener(callback);
123         }
124         return result;
125     }
126 
127     /**
128      * enumeration of all wallpapers used for test purposes: 3 static, 3 live wallpapers:   <br>
129      * static1 <=> red bitmap <br>
130      * static2 <=> green bitmap <br>
131      * static3 <=> blue bitmap <br>
132      * <br>
133      * live1 <=> TestLiveWallpaper (cyan) <br>
134      * live2 <=> TestLiveWallpaperNoUnfoldTransition (magenta) <br>
135      * live3 <=> TestLiveWallpaperSupportingAmbientMode (yellow) <br>
136      */
137     public enum TestWallpaper {
138         STATIC1(R.drawable.icon_red, null),
139         STATIC2(R.drawable.icon_green, null),
140         STATIC3(R.drawable.icon_blue, null),
141         LIVE1(null, TEST_LIVE_WALLPAPER_COMPONENT),
142         LIVE2(null, TEST_LIVE_WALLPAPER_NO_UNFOLD_COMPONENT),
143         LIVE3(null, TEST_LIVE_WALLPAPER_AMBIENT_COMPONENT);
144 
145         private final Integer mBitmapResourceId;
146         private final ComponentName mComponentName;
147 
TestWallpaper(Integer bitmapResourceId, ComponentName componentName)148         TestWallpaper(Integer bitmapResourceId, ComponentName componentName) {
149             mBitmapResourceId = bitmapResourceId;
150             mComponentName = componentName;
151         }
152 
getBitmapResourceId()153         int getBitmapResourceId() {
154             return mBitmapResourceId;
155         }
156 
getComponentName()157         ComponentName getComponentName() {
158             return mComponentName;
159         }
160 
type()161         private String type() {
162             return isStatic() ? "static" : "live";
163         }
164 
isStatic()165         private boolean isStatic() {
166             return mComponentName == null;
167         }
168 
isLive()169         private boolean isLive() {
170             return !isStatic();
171         }
172     }
173 
allStaticTestWallpapers()174     private static List<TestWallpaper> allStaticTestWallpapers() {
175         return List.of(TestWallpaper.STATIC1, TestWallpaper.STATIC2, TestWallpaper.STATIC3);
176     }
177 
allLiveTestWallpapers()178     private static List<TestWallpaper> allLiveTestWallpapers() {
179         return List.of(TestWallpaper.LIVE1, TestWallpaper.LIVE2, TestWallpaper.LIVE3);
180     }
181 
182     public static class WallpaperChange {
183         final TestWallpaper mWallpaper;
184         int mDestination;
WallpaperChange( TestWallpaper wallpaper, int destination)185         public WallpaperChange(
186                 TestWallpaper wallpaper, int destination) {
187             this.mWallpaper = wallpaper;
188             this.mDestination = destination;
189         }
190     }
191 
192     /**
193      * Class representing a state in which our WallpaperManager may be during our tests.
194      * A state is fully represented by the wallpaper that are present on home and lock screen.
195      */
196     public enum WallpaperState {
197         LIVE_SAME_SINGLE(TestWallpaper.LIVE1, TestWallpaper.LIVE1, true),
198         LIVE_SAME_MULTI(TestWallpaper.LIVE1, TestWallpaper.LIVE1, false),
199         LIVE_DIFF_MULTI(TestWallpaper.LIVE1, TestWallpaper.LIVE2, false),
200         LIVE_STATIC_MULTI(TestWallpaper.LIVE1, TestWallpaper.STATIC1, false),
201         STATIC_SAME_SINGLE(TestWallpaper.STATIC1, TestWallpaper.STATIC1, true),
202         STATIC_SAME_MULTI(TestWallpaper.STATIC1, TestWallpaper.STATIC1, false),
203         STATIC_DIFF_MULTI(TestWallpaper.STATIC1, TestWallpaper.STATIC2, false),
204         STATIC_LIVE_MULTI(TestWallpaper.STATIC1, TestWallpaper.LIVE1, false);
205 
206         private final TestWallpaper mHomeWallpaper;
207         private final TestWallpaper mLockWallpaper;
208 
209         /**
210          * it is possible to have two copies of the same engine on home + lock screen,
211          * in which this flag would be false.
212          * True means that mHomeWallpaper == mLockWallpaper and there is only one active engine.
213          */
214         private final boolean mSingleEngine;
215 
WallpaperState( TestWallpaper homeWallpaper, TestWallpaper lockWallpaper, boolean singleEngine)216         WallpaperState(
217                 TestWallpaper homeWallpaper, TestWallpaper lockWallpaper, boolean singleEngine) {
218             mHomeWallpaper = homeWallpaper;
219             mLockWallpaper = lockWallpaper;
220             assertThat(!singleEngine || (homeWallpaper == lockWallpaper)).isTrue();
221             assertThat(homeWallpaper).isNotNull();
222             assertThat(lockWallpaper).isNotNull();
223             mSingleEngine = singleEngine;
224         }
225 
pickUnused(List<TestWallpaper> choices)226         private TestWallpaper pickUnused(List<TestWallpaper> choices) {
227             return choices.stream()
228                     .filter(wallpaper -> wallpaper != mHomeWallpaper && wallpaper != mLockWallpaper)
229                     .findFirst().orElseThrow();
230         }
231 
pickUnusedStatic()232         private TestWallpaper pickUnusedStatic() {
233             return pickUnused(allStaticTestWallpapers());
234         }
235 
pickUnusedLive()236         private TestWallpaper pickUnusedLive() {
237             return pickUnused(allLiveTestWallpapers());
238         }
239 
240         /**
241          * Enumerate all the possible logically different {@link WallpaperChange} changes from
242          * this state. <br>
243          * Two changes are considered logically different if their destination is different,
244          * or if their wallpaper type (static or live) is different.
245          */
allPossibleChanges()246         public List<WallpaperChange> allPossibleChanges() {
247             TestWallpaper unusedStatic = pickUnusedStatic();
248             TestWallpaper unusedLive = pickUnusedLive();
249 
250             // one can always add a new wallpaper, either static or live, at any destination
251             List<WallpaperChange> result = new ArrayList<>(Stream.of(unusedStatic, unusedLive)
252                     .flatMap(newWallpaper -> Stream
253                             .of(FLAG_LOCK, FLAG_SYSTEM, FLAG_LOCK | FLAG_SYSTEM)
254                             .map(destination -> new WallpaperChange(newWallpaper, destination)))
255                     .toList());
256 
257             // if we have a lock & home single engine, we can separate it
258             if (mSingleEngine) {
259                 result.addAll(List.of(
260                         new WallpaperChange(mHomeWallpaper, FLAG_SYSTEM),
261                         new WallpaperChange(mHomeWallpaper, FLAG_LOCK)
262                 ));
263 
264                 // else if we have the same engine twice, we can merge it
265             } else if (mHomeWallpaper == mLockWallpaper) {
266                 result.add(new WallpaperChange(mHomeWallpaper, FLAG_SYSTEM | FLAG_LOCK));
267             }
268 
269             // if we have different engines on home / lock,
270             // we can set one of them at the other location or at both locations
271             if (mHomeWallpaper != mLockWallpaper) {
272                 result.addAll(List.of(
273                         new WallpaperChange(mHomeWallpaper, FLAG_LOCK | FLAG_SYSTEM),
274                         new WallpaperChange(mLockWallpaper, FLAG_LOCK | FLAG_SYSTEM),
275                         new WallpaperChange(mHomeWallpaper, FLAG_LOCK),
276                         new WallpaperChange(mLockWallpaper, FLAG_SYSTEM)
277                 ));
278             }
279             return result;
280         }
281 
282         /**
283          * Given a change, return the number of times we expect an engine.onCreate operation
284          * of a live wallpaper from this state
285          */
expectedNumberOfLiveWallpaperCreate(WallpaperChange change)286         public int expectedNumberOfLiveWallpaperCreate(WallpaperChange change) {
287 
288             if (change.mWallpaper.isStatic()) return 0;
289             switch (change.mDestination) {
290                 case FLAG_SYSTEM | FLAG_LOCK:
291                     return change.mWallpaper != mHomeWallpaper ? 1 : 0;
292                 case FLAG_SYSTEM:
293                     return mSingleEngine || (change.mWallpaper != mHomeWallpaper) ? 1 : 0;
294                 case FLAG_LOCK:
295                     return mSingleEngine || (change.mWallpaper != mLockWallpaper) ? 1 : 0;
296                 default:
297                     throw new IllegalArgumentException();
298             }
299         }
300 
301 
302         /**
303          * Given a change, return the number of times we expect an engine.onDestroy operation
304          * of a live wallpaper from this state
305          */
expectedNumberOfLiveWallpaperDestroy(WallpaperChange change)306         public int expectedNumberOfLiveWallpaperDestroy(WallpaperChange change) {
307 
308             if (mSingleEngine) {
309                 return mHomeWallpaper.isLive()
310                         && mHomeWallpaper != change.mWallpaper
311                         && change.mDestination == (FLAG_LOCK | FLAG_SYSTEM) ? 1 : 0;
312             }
313 
314             boolean changeSystem = (change.mDestination & FLAG_SYSTEM) != 0;
315             boolean changeLock = (change.mDestination & FLAG_LOCK) != 0;
316             boolean systemReplaced = changeSystem && change.mWallpaper != mHomeWallpaper;
317             boolean lockReplaced =
318                     changeLock && (change.mWallpaper != mLockWallpaper || changeSystem);
319 
320             int result = 0;
321             if (systemReplaced && mHomeWallpaper.isLive()) result += 1;
322             if (lockReplaced && mLockWallpaper.isLive()) result += 1;
323             return result;
324         }
325 
326         /**
327          * Describes how to reproduce a failure obtained from this state with the given change
328          */
reproduceDescription(WallpaperChange change)329         public String reproduceDescription(WallpaperChange change) {
330             return String.format("To reproduce, start with:\n%s\nand %s",
331                     description(), changeDescription(change));
332         }
333 
description()334         private String description() {
335             String homeType = mHomeWallpaper.type();
336             String lockType = mLockWallpaper.type();
337             return mLockWallpaper == mHomeWallpaper
338                     ? String.format(" - the same %s wallpaper on home & lock screen (%s)", homeType,
339                     mSingleEngine ? "sharing the same engine" : "each using its own engine")
340                     : String.format(" - a %s wallpaper on home screen\n"
341                                     + " - %s %s wallpaper on lock screen",
342                             homeType, homeType.equals(lockType) ? "another" : "a", lockType);
343         }
344 
changeDescription(WallpaperChange change)345         private String changeDescription(WallpaperChange change) {
346             String newWallpaperDescription =
347                     change.mWallpaper == mHomeWallpaper || change.mWallpaper == mLockWallpaper
348                             ? String.format("the same %s wallpaper as %s screen",
349                             change.mWallpaper.type(),
350                             change.mWallpaper == mHomeWallpaper ? "home" : "lock")
351                             : String.format("a different %s wallpaper", change.mWallpaper.type());
352 
353             String destinationDescription =
354                     change.mDestination == FLAG_SYSTEM ? "home screen only"
355                             : change.mDestination == FLAG_LOCK ? "lock screen only"
356                                     : "both home & lock screens";
357 
358             String methodUsed = change.mWallpaper.isLive()
359                     ? "setWallpaperComponentWithFlags" : "setResource";
360 
361             String flagDescription =
362                     change.mDestination == FLAG_SYSTEM ? "FLAG_SYSTEM"
363                             : change.mDestination == FLAG_LOCK ? "FLAG_LOCK"
364                                     : "FLAG_SYSTEM|FLAG_LOCK";
365 
366             return String.format("apply %s on %s (via WallpaperManager#%s(..., %s))",
367                     newWallpaperDescription, destinationDescription, methodUsed, flagDescription);
368         }
369     }
370 
371     /**
372      * Uses the provided wallpaperManager instance to perform a {@link WallpaperChange}.
373      */
performChange( WallpaperManager wallpaperManager, WallpaperChange change)374     public static void performChange(
375             WallpaperManager wallpaperManager, WallpaperChange change)
376             throws IOException {
377 
378         // Count the number of test live wallpapers that will be replaced
379         final int onDestroyCount = (int) Stream.of(FLAG_SYSTEM, FLAG_LOCK)
380                 .filter(which -> (change.mDestination & which) > 0)
381                 .map(wallpaperManager::getWallpaperInfo)
382                 .filter(info -> info != null && TEST_COMPONENTS.contains(info.getComponent()))
383                 .count();
384 
385         if (change.mWallpaper.isStatic()) {
386             runAndAwaitChanges(500, TimeUnit.MILLISECONDS, 0, onDestroyCount, 0, () ->
387                     wallpaperManager.setResource(
388                             change.mWallpaper.getBitmapResourceId(), change.mDestination));
389         } else {
390             // Up to one surface is expected to be created when switching wallpapers. It's possible
391             // that this operation ends up being a no-op, in that case the wait will time out.
392             final int expectedSurfaceCreations = 1;
393             runAndAwaitChanges(500, TimeUnit.MILLISECONDS, 0,
394                     onDestroyCount, expectedSurfaceCreations, () -> {
395                         wallpaperManager.setWallpaperComponentWithFlags(
396                                 change.mWallpaper.getComponentName(), change.mDestination);
397                     });
398         }
399     }
400 
401     /**
402      * Sets a wallpaperManager in some state. Always proceeds the same way: <br>
403      *   - put the home wallpaper on lock and home screens <br>
404      *   - put the lock wallpaper on lock screen, if it is different from the home screen wallpaper
405      */
goToState( WallpaperManager wallpaperManager, WallpaperState state)406     public static void goToState(
407             WallpaperManager wallpaperManager, WallpaperState state)
408             throws IOException {
409         WallpaperChange change1 = new WallpaperChange(
410                 state.mHomeWallpaper, FLAG_SYSTEM | (state.mSingleEngine ? FLAG_LOCK : 0));
411         performChange(wallpaperManager, change1);
412 
413         WallpaperChange change2 = new WallpaperChange(state.mLockWallpaper, FLAG_LOCK);
414         if (!state.mSingleEngine) performChange(wallpaperManager, change2);
415     }
416 
417     static class ColorChangeWaiter implements WallpaperManager.OnColorsChangedListener {
418         private CountDownLatch mHomeCountDownLatch;
419         private CountDownLatch mLockCountDownLatch;
ColorChangeWaiter(int which)420         ColorChangeWaiter(int which) {
421             int expectedHomeEvents = ((which & FLAG_SYSTEM) != 0) ? 1 : 0;
422             int expectedLockEvents = ((which & FLAG_LOCK) != 0) ? 1 : 0;
423             mHomeCountDownLatch = new CountDownLatch(expectedHomeEvents);
424             mLockCountDownLatch = new CountDownLatch(expectedLockEvents);
425         }
426 
427         @Override
onColorsChanged(@ullable WallpaperColors colors, int which)428         public void onColorsChanged(@Nullable WallpaperColors colors, int which) {
429             if ((which & FLAG_SYSTEM) != 0) {
430                 mHomeCountDownLatch.countDown();
431             }
432             if ((which & FLAG_LOCK) != 0) {
433                 mLockCountDownLatch.countDown();
434             }
435             Log.d(TAG, "color state count down: " + which + " - " + colors);
436         }
437 
waitForChanges(long timeout, TimeUnit unit)438         public boolean waitForChanges(long timeout, TimeUnit unit) {
439             try {
440                 return mHomeCountDownLatch.await(timeout, unit)
441                         && mLockCountDownLatch.await(timeout, unit);
442             } catch (InterruptedException e) {
443                 throw new RuntimeException("Wallpaper colors wait interrupted");
444             }
445         }
446     }
447 }
448