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