• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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