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