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