1 /* 2 * Copyright (C) 2009 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 com.android.systemui.wallpapers; 18 19 import static android.app.WallpaperManager.FLAG_LOCK; 20 import static android.app.WallpaperManager.FLAG_SYSTEM; 21 import static android.app.WallpaperManager.SetWallpaperFlags; 22 23 import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased; 24 import static com.android.window.flags.Flags.multiCrop; 25 import static com.android.window.flags.Flags.offloadColorExtraction; 26 27 import android.annotation.Nullable; 28 import android.app.WallpaperColors; 29 import android.app.WallpaperManager; 30 import android.content.Context; 31 import android.graphics.Bitmap; 32 import android.graphics.Canvas; 33 import android.graphics.RecordingCanvas; 34 import android.graphics.Rect; 35 import android.graphics.RectF; 36 import android.hardware.display.DisplayManager; 37 import android.hardware.display.DisplayManager.DisplayListener; 38 import android.os.HandlerThread; 39 import android.os.Looper; 40 import android.os.Trace; 41 import android.service.wallpaper.WallpaperService; 42 import android.util.Log; 43 import android.view.Surface; 44 import android.view.SurfaceHolder; 45 46 import androidx.annotation.NonNull; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.systemui.dagger.qualifiers.LongRunning; 50 import com.android.systemui.settings.UserTracker; 51 import com.android.systemui.util.concurrency.DelayableExecutor; 52 import com.android.systemui.utils.windowmanager.WindowManagerProvider; 53 54 import java.io.FileDescriptor; 55 import java.io.PrintWriter; 56 import java.util.List; 57 58 import javax.inject.Inject; 59 60 /** 61 * Default built-in wallpaper that simply shows a static image. 62 */ 63 @SuppressWarnings({"UnusedDeclaration"}) 64 public class ImageWallpaper extends WallpaperService { 65 66 private static final String TAG = ImageWallpaper.class.getSimpleName(); 67 private static final boolean DEBUG = false; 68 69 // keep track of the number of pages of the launcher for local color extraction purposes 70 private volatile int mPages = 1; 71 private boolean mPagesComputed = false; 72 73 private final UserTracker mUserTracker; 74 private final WindowManagerProvider mWindowManagerProvider; 75 76 // used to handle WallpaperService messages (e.g. DO_ATTACH, MSG_UPDATE_SURFACE) 77 // and to receive WallpaperService callbacks (e.g. onCreateEngine, onSurfaceRedrawNeeded) 78 private HandlerThread mWorker; 79 80 // used for most tasks (call canvas.drawBitmap, load/unload the bitmap) 81 @LongRunning 82 private final DelayableExecutor mLongExecutor; 83 84 // wait at least this duration before unloading the bitmap 85 private static final int DELAY_UNLOAD_BITMAP = 2000; 86 87 @Inject ImageWallpaper(@ongRunning DelayableExecutor longExecutor, UserTracker userTracker, WindowManagerProvider windowManagerProvider)88 public ImageWallpaper(@LongRunning DelayableExecutor longExecutor, UserTracker userTracker, 89 WindowManagerProvider windowManagerProvider) { 90 super(); 91 mLongExecutor = longExecutor; 92 mUserTracker = userTracker; 93 mWindowManagerProvider = windowManagerProvider; 94 } 95 96 @Override onProvideEngineLooper()97 public Looper onProvideEngineLooper() { 98 // Receive messages on mWorker thread instead of SystemUI's main handler. 99 // All other wallpapers have their own process, and they can receive messages on their own 100 // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance 101 // of the image wallpaper could be negatively affected when SystemUI's main handler is busy. 102 return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper(); 103 } 104 105 @Override onCreate()106 public void onCreate() { 107 super.onCreate(); 108 mWorker = new HandlerThread(TAG); 109 mWorker.start(); 110 } 111 112 @Override onCreateEngine()113 public Engine onCreateEngine() { 114 return new CanvasEngine(); 115 } 116 117 class CanvasEngine extends WallpaperService.Engine implements DisplayListener { 118 private WallpaperManager mWallpaperManager; 119 private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor; 120 private SurfaceHolder mSurfaceHolder; 121 private boolean mDrawn = false; 122 @VisibleForTesting 123 static final int MIN_SURFACE_WIDTH = 128; 124 @VisibleForTesting 125 static final int MIN_SURFACE_HEIGHT = 128; 126 private Bitmap mBitmap; 127 private boolean mWideColorGamut = false; 128 129 /* 130 * Counter to unload the bitmap as soon as possible. 131 * Before any bitmap operation, this is incremented. 132 * After an operation completion, this is decremented (synchronously), 133 * and if the count is 0, unload the bitmap 134 */ 135 private int mBitmapUsages = 0; 136 137 /** 138 * Main lock for long operations (loading the bitmap or processing colors). 139 */ 140 private final Object mLock = new Object(); 141 142 /** 143 * Lock for SurfaceHolder operations. Should only be acquired after the main lock. 144 */ 145 private final Object mSurfaceLock = new Object(); 146 CanvasEngine()147 CanvasEngine() { 148 super(); 149 setFixedSizeAllowed(true); 150 setShowForAllUsers(true); 151 mWallpaperLocalColorExtractor = new WallpaperLocalColorExtractor( 152 mLongExecutor, 153 mLock, 154 new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() { 155 156 @Override 157 public void onColorsProcessed() { 158 CanvasEngine.this.notifyColorsChanged(); 159 } 160 161 @Override 162 public void onColorsProcessed(List<RectF> regions, 163 List<WallpaperColors> colors) { 164 CanvasEngine.this.onColorsProcessed(regions, colors); 165 } 166 167 @Override 168 public void onMiniBitmapUpdated() { 169 CanvasEngine.this.onMiniBitmapUpdated(); 170 } 171 172 @Override 173 public void onActivated() { 174 setOffsetNotificationsEnabled(true); 175 } 176 177 @Override 178 public void onDeactivated() { 179 setOffsetNotificationsEnabled(false); 180 } 181 }); 182 183 // if the number of pages is already computed, transmit it to the color extractor 184 if (mPagesComputed) { 185 mWallpaperLocalColorExtractor.onPageChanged(mPages); 186 } 187 } 188 189 @Override onCreate(SurfaceHolder surfaceHolder)190 public void onCreate(SurfaceHolder surfaceHolder) { 191 Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate"); 192 if (DEBUG) { 193 Log.d(TAG, "onCreate"); 194 } 195 mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class); 196 mSurfaceHolder = surfaceHolder; 197 Rect dimensions = !multiCrop() 198 ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true) 199 : mWallpaperManager.peekBitmapDimensionsAsUser(getSourceFlag(), true, 200 mUserTracker.getUserId()); 201 int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width()); 202 int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height()); 203 mSurfaceHolder.setFixedSize(width, height); 204 205 getDisplayContext().getSystemService(DisplayManager.class) 206 .registerDisplayListener(this, null); 207 getDisplaySizeAndUpdateColorExtractor(); 208 Trace.endSection(); 209 } 210 211 @Override onDestroy()212 public void onDestroy() { 213 Context context = getDisplayContext(); 214 if (context != null) { 215 DisplayManager displayManager = context.getSystemService(DisplayManager.class); 216 if (displayManager != null) displayManager.unregisterDisplayListener(this); 217 } 218 mWallpaperLocalColorExtractor.cleanUp(); 219 } 220 221 @Override shouldZoomOutWallpaper()222 public boolean shouldZoomOutWallpaper() { 223 return true; 224 } 225 226 @Override shouldWaitForEngineShown()227 public boolean shouldWaitForEngineShown() { 228 return true; 229 } 230 231 @Override onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)232 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 233 if (DEBUG) { 234 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height); 235 } 236 } 237 238 @Override onSurfaceDestroyed(SurfaceHolder holder)239 public void onSurfaceDestroyed(SurfaceHolder holder) { 240 if (DEBUG) { 241 Log.i(TAG, "onSurfaceDestroyed"); 242 } 243 if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { 244 synchronized (mSurfaceLock) { 245 mSurfaceHolder = null; 246 } 247 return; 248 } 249 mLongExecutor.execute(this::onSurfaceDestroyedSynchronized); 250 } 251 onSurfaceDestroyedSynchronized()252 private void onSurfaceDestroyedSynchronized() { 253 synchronized (mLock) { 254 mSurfaceHolder = null; 255 } 256 } 257 258 @Override onSurfaceCreated(SurfaceHolder holder)259 public void onSurfaceCreated(SurfaceHolder holder) { 260 if (DEBUG) { 261 Log.i(TAG, "onSurfaceCreated"); 262 } 263 } 264 265 @Override onSurfaceRedrawNeeded(SurfaceHolder holder)266 public void onSurfaceRedrawNeeded(SurfaceHolder holder) { 267 if (DEBUG) { 268 Log.d(TAG, "onSurfaceRedrawNeeded"); 269 } 270 drawFrame(); 271 } 272 drawFrame()273 private void drawFrame() { 274 mLongExecutor.execute(this::drawFrameSynchronized); 275 } 276 drawFrameSynchronized()277 private void drawFrameSynchronized() { 278 synchronized (mLock) { 279 if (mDrawn) return; 280 drawFrameInternal(); 281 } 282 } 283 drawFrameInternal()284 private void drawFrameInternal() { 285 if (mSurfaceHolder == null && !fixImageWallpaperCrashSurfaceAlreadyReleased()) { 286 Log.i(TAG, "attempt to draw a frame without a valid surface"); 287 return; 288 } 289 290 // load the wallpaper if not already done 291 if (!isBitmapLoaded()) { 292 loadWallpaperAndDrawFrameInternal(); 293 } else { 294 if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { 295 synchronized (mSurfaceLock) { 296 if (mSurfaceHolder == null) { 297 Log.i(TAG, "Surface released before the image could be drawn"); 298 return; 299 } 300 mBitmapUsages++; 301 drawFrameOnCanvas(mBitmap); 302 reportEngineShown(false); 303 unloadBitmapIfNotUsedInternal(); 304 return; 305 } 306 } 307 mBitmapUsages++; 308 drawFrameOnCanvas(mBitmap); 309 reportEngineShown(false); 310 unloadBitmapIfNotUsedInternal(); 311 } 312 } 313 314 @VisibleForTesting drawFrameOnCanvas(Bitmap bitmap)315 void drawFrameOnCanvas(Bitmap bitmap) { 316 Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame"); 317 Surface surface = mSurfaceHolder.getSurface(); 318 Canvas canvas = null; 319 try { 320 canvas = mWideColorGamut 321 ? surface.lockHardwareWideColorGamutCanvas() 322 : surface.lockHardwareCanvas(); 323 } catch (IllegalStateException e) { 324 Log.w(TAG, "Unable to lock canvas", e); 325 } 326 if (canvas != null) { 327 Rect dest = mSurfaceHolder.getSurfaceFrame(); 328 try { 329 canvas.drawBitmap(bitmap, null, dest, null); 330 mDrawn = true; 331 } finally { 332 surface.unlockCanvasAndPost(canvas); 333 } 334 } 335 Trace.endSection(); 336 } 337 338 @VisibleForTesting isBitmapLoaded()339 boolean isBitmapLoaded() { 340 return mBitmap != null && !mBitmap.isRecycled(); 341 } 342 unloadBitmapIfNotUsed()343 private void unloadBitmapIfNotUsed() { 344 mLongExecutor.execute(this::unloadBitmapIfNotUsedSynchronized); 345 } 346 unloadBitmapIfNotUsedSynchronized()347 private void unloadBitmapIfNotUsedSynchronized() { 348 synchronized (mLock) { 349 unloadBitmapIfNotUsedInternal(); 350 } 351 } 352 unloadBitmapIfNotUsedInternal()353 private void unloadBitmapIfNotUsedInternal() { 354 mBitmapUsages -= 1; 355 if (mBitmapUsages <= 0) { 356 mBitmapUsages = 0; 357 unloadBitmapInternal(); 358 } 359 } 360 unloadBitmapInternal()361 private void unloadBitmapInternal() { 362 Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap"); 363 if (mBitmap != null) { 364 mBitmap.recycle(); 365 } 366 mBitmap = null; 367 if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { 368 synchronized (mSurfaceLock) { 369 if (mSurfaceHolder != null) mSurfaceHolder.getSurface().hwuiDestroy(); 370 } 371 } else { 372 final Surface surface = getSurfaceHolder().getSurface(); 373 surface.hwuiDestroy(); 374 } 375 mWallpaperManager.forgetLoadedWallpaper(); 376 Trace.endSection(); 377 } 378 loadWallpaperAndDrawFrameInternal()379 private void loadWallpaperAndDrawFrameInternal() { 380 Trace.beginSection("WPMS.ImageWallpaper.CanvasEngine#loadWallpaper"); 381 boolean loadSuccess = false; 382 Bitmap bitmap; 383 try { 384 Trace.beginSection("WPMS.getBitmapAsUser"); 385 bitmap = mWallpaperManager.getBitmapAsUser( 386 mUserTracker.getUserId(), false, getSourceFlag(), true); 387 if (bitmap != null 388 && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) { 389 throw new RuntimeException("Wallpaper is too large to draw!"); 390 } 391 } catch (RuntimeException | OutOfMemoryError exception) { 392 393 // Note that if we do fail at this, and the default wallpaper can't 394 // be loaded, we will go into a cycle. Don't do a build where the 395 // default wallpaper can't be loaded. 396 Log.w(TAG, "Unable to load wallpaper!", exception); 397 Trace.beginSection("WPMS.clearWallpaper"); 398 mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId()); 399 Trace.endSection(); 400 401 try { 402 Trace.beginSection("WPMS.getBitmapAsUser_defaultWallpaper"); 403 bitmap = mWallpaperManager.getBitmapAsUser( 404 mUserTracker.getUserId(), false, getSourceFlag(), true); 405 } catch (RuntimeException | OutOfMemoryError e) { 406 Log.w(TAG, "Unable to load default wallpaper!", e); 407 bitmap = null; 408 } finally { 409 Trace.endSection(); 410 } 411 } finally { 412 Trace.endSection(); 413 } 414 415 if (bitmap == null) { 416 Log.w(TAG, "Could not load bitmap"); 417 } else if (bitmap.isRecycled()) { 418 Log.e(TAG, "Attempt to load a recycled bitmap"); 419 } else if (mBitmap == bitmap) { 420 Log.e(TAG, "Loaded a bitmap that was already loaded"); 421 } else { 422 // at this point, loading is done correctly. 423 loadSuccess = true; 424 // recycle the previously loaded bitmap 425 if (mBitmap != null) { 426 Trace.beginSection("WPMS.mBitmap.recycle"); 427 mBitmap.recycle(); 428 Trace.endSection(); 429 } 430 mBitmap = bitmap; 431 Trace.beginSection("WPMS.wallpaperSupportsWcg"); 432 mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(getSourceFlag()); 433 Trace.endSection(); 434 435 // +2 usages for the color extraction and the delayed unload. 436 mBitmapUsages += 2; 437 Trace.beginSection("WPMS.recomputeColorExtractorMiniBitmap"); 438 recomputeColorExtractorMiniBitmap(); 439 Trace.endSection(); 440 Trace.beginSection("WPMS.drawFrameInternal"); 441 drawFrameInternal(); 442 Trace.endSection(); 443 444 /* 445 * after loading, the bitmap will be unloaded after all these conditions: 446 * - the frame is redrawn 447 * - the mini bitmap from color extractor is recomputed 448 * - the DELAY_UNLOAD_BITMAP has passed 449 */ 450 mLongExecutor.executeDelayed( 451 this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP); 452 } 453 // even if the bitmap cannot be loaded, call reportEngineShown 454 if (!loadSuccess) reportEngineShown(false); 455 Trace.endSection(); 456 } 457 onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors)458 private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) { 459 try { 460 notifyLocalColorsChanged(regions, colors); 461 } catch (RuntimeException e) { 462 Log.e(TAG, e.getMessage(), e); 463 } 464 } 465 466 /** 467 * Helper to return the flag from where the source bitmap is from. 468 * Similar to {@link #getWallpaperFlags()}, but returns (FLAG_SYSTEM) instead of 469 * (FLAG_LOCK | FLAG_SYSTEM) if this engine is used for both lock screen & home screen. 470 */ getSourceFlag()471 private @SetWallpaperFlags int getSourceFlag() { 472 return getWallpaperFlags() == FLAG_LOCK ? FLAG_LOCK : FLAG_SYSTEM; 473 } 474 475 @VisibleForTesting recomputeColorExtractorMiniBitmap()476 void recomputeColorExtractorMiniBitmap() { 477 mWallpaperLocalColorExtractor.onBitmapChanged(mBitmap); 478 } 479 480 @VisibleForTesting onMiniBitmapUpdated()481 void onMiniBitmapUpdated() { 482 unloadBitmapIfNotUsed(); 483 } 484 485 @Override onComputeColors()486 public @Nullable WallpaperColors onComputeColors() { 487 if (!offloadColorExtraction()) return null; 488 return mWallpaperLocalColorExtractor.onComputeColors(); 489 } 490 491 @Override supportsLocalColorExtraction()492 public boolean supportsLocalColorExtraction() { 493 return true; 494 } 495 496 @Override addLocalColorsAreas(@onNull List<RectF> regions)497 public void addLocalColorsAreas(@NonNull List<RectF> regions) { 498 // this call will activate the offset notifications 499 // if no colors were being processed before 500 mWallpaperLocalColorExtractor.addLocalColorsAreas(regions); 501 } 502 503 @Override removeLocalColorsAreas(@onNull List<RectF> regions)504 public void removeLocalColorsAreas(@NonNull List<RectF> regions) { 505 // this call will deactivate the offset notifications 506 // if we are no longer processing colors 507 mWallpaperLocalColorExtractor.removeLocalColorAreas(regions); 508 } 509 510 @Override onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset)511 public void onOffsetsChanged(float xOffset, float yOffset, 512 float xOffsetStep, float yOffsetStep, 513 int xPixelOffset, int yPixelOffset) { 514 final int pages; 515 if (xOffsetStep > 0 && xOffsetStep <= 1) { 516 pages = Math.round(1 / xOffsetStep) + 1; 517 } else { 518 pages = 1; 519 } 520 if (pages != mPages || !mPagesComputed) { 521 mPages = pages; 522 mPagesComputed = true; 523 mWallpaperLocalColorExtractor.onPageChanged(mPages); 524 } 525 } 526 527 @Override onDimAmountChanged(float dimAmount)528 public void onDimAmountChanged(float dimAmount) { 529 if (!offloadColorExtraction()) return; 530 mWallpaperLocalColorExtractor.onDimAmountChanged(dimAmount); 531 } 532 533 @Override onDisplayAdded(int displayId)534 public void onDisplayAdded(int displayId) { 535 536 } 537 538 @Override onDisplayRemoved(int displayId)539 public void onDisplayRemoved(int displayId) { 540 541 } 542 543 @Override onDisplayChanged(int displayId)544 public void onDisplayChanged(int displayId) { 545 Trace.beginSection("ImageWallpaper.CanvasEngine#onDisplayChanged"); 546 try { 547 // changes the display in the color extractor 548 // the new display dimensions will be used in the next color computation 549 if (displayId == getDisplayContext().getDisplayId()) { 550 getDisplaySizeAndUpdateColorExtractor(); 551 } 552 } finally { 553 Trace.endSection(); 554 } 555 } 556 getDisplaySizeAndUpdateColorExtractor()557 private void getDisplaySizeAndUpdateColorExtractor() { 558 Rect window = mWindowManagerProvider.getWindowManager(getDisplayContext()) 559 .getCurrentWindowMetrics() 560 .getBounds(); 561 mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height()); 562 } 563 564 @Override dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args)565 protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { 566 super.dump(prefix, fd, out, args); 567 out.print(prefix); out.print("Engine="); out.println(this); 568 out.print(prefix); out.print("valid surface="); 569 out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null 570 ? getSurfaceHolder().getSurface().isValid() 571 : "null"); 572 573 out.print(prefix); out.print("surface frame="); 574 out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null"); 575 576 out.print(prefix); out.print("bitmap="); 577 out.println(mBitmap == null ? "null" 578 : mBitmap.isRecycled() ? "recycled" 579 : mBitmap.getWidth() + "x" + mBitmap.getHeight()); 580 581 mWallpaperLocalColorExtractor.dump(prefix, fd, out, args); 582 } 583 } 584 } 585