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