1 /* 2 * Copyright (C) 2011 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; 18 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.anyInt; 21 import static org.mockito.ArgumentMatchers.nullable; 22 import static org.mockito.Mockito.atLeast; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.never; 25 import static org.mockito.Mockito.spy; 26 import static org.mockito.Mockito.verify; 27 28 import android.app.WallpaperColors; 29 import android.app.WallpaperManager; 30 import android.app.stubs.R; 31 import android.content.BroadcastReceiver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.graphics.Bitmap; 36 import android.graphics.Canvas; 37 import android.graphics.Color; 38 import android.graphics.Point; 39 import android.os.Handler; 40 import android.os.HandlerThread; 41 import android.os.Looper; 42 import android.util.Log; 43 import android.view.Display; 44 import android.view.WindowManager; 45 46 import androidx.test.InstrumentationRegistry; 47 import androidx.test.runner.AndroidJUnit4; 48 49 import org.junit.Assert; 50 import org.junit.Before; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 import org.mockito.MockitoAnnotations; 54 55 import java.io.IOException; 56 import java.util.ArrayList; 57 import java.util.concurrent.CountDownLatch; 58 import java.util.concurrent.TimeUnit; 59 60 @RunWith(AndroidJUnit4.class) 61 public class WallpaperManagerTest { 62 63 private static final boolean DEBUG = false; 64 private static final String TAG = "WallpaperManagerTest"; 65 66 private WallpaperManager mWallpaperManager; 67 private Context mContext; 68 private Handler mHandler; 69 70 @Before 71 public void setUp() throws Exception { 72 MockitoAnnotations.initMocks(this); 73 mContext = InstrumentationRegistry.getTargetContext(); 74 mWallpaperManager = WallpaperManager.getInstance(mContext); 75 final HandlerThread handlerThread = new HandlerThread("TestCallbacks"); 76 handlerThread.start(); 77 mHandler = new Handler(handlerThread.getLooper()); 78 } 79 80 @Test 81 public void setBitmapTest() { 82 Bitmap tmpWallpaper = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 83 Canvas canvas = new Canvas(tmpWallpaper); 84 canvas.drawColor(Color.RED); 85 86 try { 87 int which = WallpaperManager.FLAG_SYSTEM; 88 mWallpaperManager.setBitmap(tmpWallpaper); 89 int oldWallpaperId = mWallpaperManager.getWallpaperId(which); 90 canvas.drawColor(Color.GREEN); 91 mWallpaperManager.setBitmap(tmpWallpaper); 92 int newWallpaperId = mWallpaperManager.getWallpaperId(which); 93 Assert.assertNotEquals(oldWallpaperId, newWallpaperId); 94 } catch (IOException e) { 95 throw new RuntimeException(e); 96 } finally { 97 tmpWallpaper.recycle(); 98 } 99 } 100 101 @Test 102 public void setResourceTest() { 103 try { 104 int which = WallpaperManager.FLAG_SYSTEM; 105 int oldWallpaperId = mWallpaperManager.getWallpaperId(which); 106 mWallpaperManager.setResource(R.drawable.robot); 107 int newWallpaperId = mWallpaperManager.getWallpaperId(which); 108 Assert.assertNotEquals(oldWallpaperId, newWallpaperId); 109 } catch (IOException e) { 110 throw new RuntimeException(e); 111 } 112 } 113 114 @Test 115 public void wallpaperChangedBroadcastTest() { 116 Bitmap tmpWallpaper = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 117 Canvas canvas = new Canvas(tmpWallpaper); 118 canvas.drawColor(Color.BLACK); 119 120 CountDownLatch latch = new CountDownLatch(1); 121 mContext.registerReceiver(new BroadcastReceiver() { 122 @Override 123 public void onReceive(Context context, Intent intent) { 124 latch.countDown(); 125 } 126 }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); 127 128 try { 129 mWallpaperManager.setBitmap(tmpWallpaper); 130 131 // Wait for up to 5 sec since this is an async call. 132 // Should fail if Intent.ACTION_WALLPAPER_CHANGED isn't delivered. 133 if (!latch.await(5, TimeUnit.SECONDS)) { 134 throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received."); 135 } 136 } catch (InterruptedException | IOException e) { 137 throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received."); 138 } finally { 139 tmpWallpaper.recycle(); 140 } 141 } 142 143 @Test 144 public void wallpaperClearBroadcastTest() { 145 CountDownLatch latch = new CountDownLatch(1); 146 mContext.registerReceiver(new BroadcastReceiver() { 147 @Override 148 public void onReceive(Context context, Intent intent) { 149 latch.countDown(); 150 } 151 }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); 152 153 try { 154 mWallpaperManager.clear(WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM); 155 156 // Wait for 5 sec since this is an async call. 157 // Should fail if Intent.ACTION_WALLPAPER_CHANGED isn't delivered. 158 if (!latch.await(5, TimeUnit.SECONDS)) { 159 throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received."); 160 } 161 } catch (InterruptedException | IOException e) { 162 throw new AssertionError(e); 163 } 164 } 165 166 @Test 167 public void invokeOnColorsChangedListenerTest_systemOnly() { 168 int both = WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM; 169 // Expect both since the first step is to migrate the current wallpaper 170 // to the lock screen. 171 verifyColorListenerInvoked(WallpaperManager.FLAG_SYSTEM, both); 172 } 173 174 @Test 175 public void invokeOnColorsChangedListenerTest_lockOnly() { 176 verifyColorListenerInvoked(WallpaperManager.FLAG_LOCK, WallpaperManager.FLAG_LOCK); 177 } 178 179 @Test 180 public void invokeOnColorsChangedListenerTest_both() { 181 int both = WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM; 182 verifyColorListenerInvoked(both, both); 183 } 184 185 @Test 186 public void invokeOnColorsChangedListenerTest_clearLock() throws IOException { 187 verifyColorListenerInvokedClearing(WallpaperManager.FLAG_LOCK); 188 } 189 190 @Test 191 public void invokeOnColorsChangedListenerTest_clearSystem() throws IOException { 192 verifyColorListenerInvokedClearing(WallpaperManager.FLAG_SYSTEM); 193 } 194 195 /** 196 * Removing a listener should not invoke it anymore 197 */ 198 @Test 199 public void addRemoveOnColorsChangedListenerTest_onlyInvokeAdded() throws IOException { 200 ensureCleanState(); 201 202 final CountDownLatch latch = new CountDownLatch(1); 203 WallpaperManager.OnColorsChangedListener counter = (colors, whichWp) -> latch.countDown(); 204 205 // Add and remove listener 206 WallpaperManager.OnColorsChangedListener listener = getTestableListener(); 207 mWallpaperManager.addOnColorsChangedListener(listener, mHandler); 208 mWallpaperManager.removeOnColorsChangedListener(listener); 209 210 // Verify that the listener is not called 211 mWallpaperManager.addOnColorsChangedListener(counter, mHandler); 212 try { 213 mWallpaperManager.setResource(R.drawable.robot); 214 if (!latch.await(5, TimeUnit.SECONDS)) { 215 throw new AssertionError("Registered listener not invoked"); 216 } 217 } catch (InterruptedException | IOException e) { 218 throw new RuntimeException(e); 219 } 220 verify(listener, never()).onColorsChanged(any(WallpaperColors.class), anyInt()); 221 mWallpaperManager.removeOnColorsChangedListener(counter); 222 } 223 224 /** 225 * Suggesting desired dimensions is only a hint to the system that can be ignored. 226 * 227 * Test if the desired minimum width or height the WallpaperManager returns 228 * is greater than 0. If so, then we check whether that the size is the dimension 229 * that was suggested. 230 */ 231 @Test 232 public void suggestDesiredDimensionsTest() { 233 final Point min = getScreenSize(); 234 final int w = min.x * 3; 235 final int h = min.y * 2; 236 assertDesiredDimension(new Point(min.x / 2, min.y / 2), new Point(min.x / 2, min.y / 2)); 237 238 assertDesiredDimension(new Point(w, h), new Point(w, h)); 239 240 assertDesiredDimension(new Point(min.x / 2, h), new Point(min.x / 2, h)); 241 242 assertDesiredDimension(new Point(w, min.y / 2), new Point(w, min.y / 2)); 243 } 244 245 @Test 246 public void wallpaperColors_primary() { 247 Bitmap tmpWallpaper = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 248 Canvas canvas = new Canvas(tmpWallpaper); 249 canvas.drawColor(Color.RED); 250 251 try { 252 mWallpaperManager.setBitmap(tmpWallpaper); 253 WallpaperColors colors = mWallpaperManager.getWallpaperColors( 254 WallpaperManager.FLAG_SYSTEM); 255 256 // Check that primary color is almost red 257 Color primary = colors.getPrimaryColor(); 258 final float delta = 0.1f; 259 Assert.assertEquals("red", 1f, primary.red(), delta); 260 Assert.assertEquals("green", 0f, primary.green(), delta); 261 Assert.assertEquals("blue", 0f, primary.blue(), delta); 262 263 Assert.assertNull(colors.getSecondaryColor()); 264 Assert.assertNull(colors.getTertiaryColor()); 265 } catch (IOException e) { 266 throw new RuntimeException(e); 267 } finally { 268 tmpWallpaper.recycle(); 269 } 270 } 271 272 273 @Test 274 public void wallpaperColors_secondary() { 275 Bitmap tmpWallpaper = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 276 Canvas canvas = new Canvas(tmpWallpaper); 277 canvas.drawColor(Color.RED); 278 // Make 20% of the wallpaper BLUE so that secondary color is BLUE 279 canvas.clipRect(0, 0, 100, 20); 280 canvas.drawColor(Color.BLUE); 281 282 try { 283 mWallpaperManager.setBitmap(tmpWallpaper); 284 WallpaperColors colors = mWallpaperManager.getWallpaperColors( 285 WallpaperManager.FLAG_SYSTEM); 286 287 // Check that the secondary color is almost blue 288 Color secondary = colors.getSecondaryColor(); 289 final float delta = 0.1f; 290 Assert.assertEquals("red", 0f, secondary.red(), delta); 291 Assert.assertEquals("green", 0f, secondary.green(), delta); 292 Assert.assertEquals("blue", 1f, secondary.blue(), delta); 293 294 Assert.assertNull(colors.getTertiaryColor()); 295 } catch (IOException e) { 296 throw new RuntimeException(e); 297 } finally { 298 tmpWallpaper.recycle(); 299 } 300 } 301 302 private void assertDesiredDimension(Point suggestedSize, Point expectedSize) { 303 mWallpaperManager.suggestDesiredDimensions(suggestedSize.x, suggestedSize.y); 304 Point actualSize = new Point(mWallpaperManager.getDesiredMinimumWidth(), 305 mWallpaperManager.getDesiredMinimumHeight()); 306 if (actualSize.x > 0 || actualSize.y > 0) { 307 if ((actualSize.x != expectedSize.x || actualSize.y != expectedSize.y)) { 308 throw new AssertionError("Expected x: " + expectedSize.x + " y: " 309 + expectedSize.y + ", got x: " + actualSize.x + 310 " y: " + actualSize.y); 311 } 312 } 313 } 314 315 private Point getScreenSize() { 316 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 317 Display d = wm.getDefaultDisplay(); 318 Point p = new Point(); 319 d.getRealSize(p); 320 return p; 321 } 322 323 /** 324 * Helper to set a listener and verify if it was called with the same flags. 325 * Executes operation synchronously. 326 * 327 * @param which FLAG_LOCK, FLAG_SYSTEM or a combination of both. 328 */ 329 private void verifyColorListenerInvoked(int which, int whichExpected) { 330 ensureCleanState(); 331 int expected = 0; 332 if ((whichExpected & WallpaperManager.FLAG_LOCK) != 0) expected++; 333 if ((whichExpected & WallpaperManager.FLAG_SYSTEM) != 0) expected++; 334 ArrayList<Integer> received = new ArrayList<>(); 335 336 final CountDownLatch latch = new CountDownLatch(expected); 337 Handler handler = new Handler(Looper.getMainLooper()); 338 339 WallpaperManager.OnColorsChangedListener listener = getTestableListener(); 340 WallpaperManager.OnColorsChangedListener counter = (colors, whichWp) -> { 341 handler.post(()-> { 342 received.add(whichWp); 343 boolean ok = false; 344 if ((whichWp & WallpaperManager.FLAG_LOCK) != 0 && 345 (whichExpected & WallpaperManager.FLAG_LOCK) != 0) { 346 latch.countDown(); 347 ok = true; 348 } 349 if ((whichWp & WallpaperManager.FLAG_SYSTEM) != 0 && 350 (whichExpected & WallpaperManager.FLAG_SYSTEM) != 0) { 351 latch.countDown(); 352 ok = true; 353 } 354 if (!ok) { 355 throw new AssertionError("Unexpected which flag: " + whichWp + 356 " should be: " + whichExpected); 357 } 358 }); 359 }; 360 361 mWallpaperManager.addOnColorsChangedListener(listener, mHandler); 362 mWallpaperManager.addOnColorsChangedListener(counter, mHandler); 363 364 try { 365 mWallpaperManager.setResource(R.drawable.robot, which); 366 if (!latch.await(5, TimeUnit.SECONDS)) { 367 throw new AssertionError("Didn't receive all color events. Expected: " + 368 whichExpected + " received: " + received); 369 } 370 } catch (InterruptedException | IOException e) { 371 throw new RuntimeException(e); 372 } 373 374 mWallpaperManager.removeOnColorsChangedListener(listener); 375 mWallpaperManager.removeOnColorsChangedListener(counter); 376 } 377 378 /** 379 * Helper to clear a wallpaper synchronously. 380 * 381 * @param which FLAG_LOCK, FLAG_SYSTEM or a combination of both. 382 */ 383 private void verifyColorListenerInvokedClearing(int which) { 384 ensureCleanState(); 385 386 final CountDownLatch latch = new CountDownLatch(1); 387 388 WallpaperManager.OnColorsChangedListener listener = getTestableListener(); 389 WallpaperManager.OnColorsChangedListener counter = (colors, whichWp) -> { 390 latch.countDown(); 391 }; 392 393 mWallpaperManager.addOnColorsChangedListener(listener, mHandler); 394 mWallpaperManager.addOnColorsChangedListener(counter, mHandler); 395 396 try { 397 mWallpaperManager.clear(which); 398 latch.await(5, TimeUnit.SECONDS); 399 } catch (InterruptedException | IOException e) { 400 throw new RuntimeException(e); 401 } 402 403 verify(listener, atLeast(1)) 404 .onColorsChanged(nullable(WallpaperColors.class), anyInt()); 405 406 mWallpaperManager.removeOnColorsChangedListener(listener); 407 mWallpaperManager.removeOnColorsChangedListener(counter); 408 } 409 410 /** 411 * Helper method to make sure a wallpaper is set for both FLAG_SYSTEM and FLAG_LOCK 412 * and its callbacks were already called. Necessary to cleanup previous tests states. 413 * 414 * This is necessary to avoid race conditions between tests 415 */ 416 private void ensureCleanState() { 417 Bitmap bmp = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 418 // We expect 5 events to happen when we change a wallpaper: 419 // Wallpaper changed 420 // System colors are null 421 // Lock colors are null 422 // System colors are known 423 // Lock colors are known 424 final int expectedEvents = 5; 425 CountDownLatch latch = new CountDownLatch(expectedEvents); 426 if (DEBUG) { 427 Log.d("WP", "Started latch expecting: " + latch.getCount()); 428 } 429 BroadcastReceiver receiver = new BroadcastReceiver() { 430 @Override 431 public void onReceive(Context context, Intent intent) { 432 latch.countDown(); 433 if (DEBUG) { 434 Log.d("WP", "broadcast state count down: " + latch.getCount()); 435 } 436 } 437 }; 438 WallpaperManager.OnColorsChangedListener callback = (colors, which) -> { 439 if ((which & WallpaperManager.FLAG_LOCK) != 0) { 440 latch.countDown(); 441 } 442 if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { 443 latch.countDown(); 444 } 445 if (DEBUG) { 446 Log.d("WP", "color state count down: " + which + " - " + colors); 447 } 448 }; 449 mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); 450 mWallpaperManager.addOnColorsChangedListener(callback, mHandler); 451 452 try { 453 mWallpaperManager.setBitmap(bmp); 454 455 // Wait for up to 10 sec since this is an async call. 456 // Will pass as soon as the expected callbacks are executed. 457 latch.await(10, TimeUnit.SECONDS); 458 if (latch.getCount() != 0) { 459 Log.w(TAG, "Did not receive all events! This is probably a bug."); 460 } 461 } catch (InterruptedException | IOException e) { 462 throw new RuntimeException("Can't ensure a clean state."); 463 } finally { 464 mContext.unregisterReceiver(receiver); 465 mWallpaperManager.removeOnColorsChangedListener(callback); 466 bmp.recycle(); 467 } 468 } 469 470 public WallpaperManager.OnColorsChangedListener getTestableListener() { 471 // Unfortunately mockito cannot mock anonymous classes or lambdas. 472 return spy(new TestableColorListener()); 473 } 474 475 public class TestableColorListener implements WallpaperManager.OnColorsChangedListener { 476 @Override 477 public void onColorsChanged(WallpaperColors colors, int which) { 478 } 479 } 480 }