• 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 android.app.WallpaperColors;
20 import android.app.WallpaperManager;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.RecordingCanvas;
24 import android.graphics.Rect;
25 import android.graphics.RectF;
26 import android.hardware.display.DisplayManager;
27 import android.hardware.display.DisplayManager.DisplayListener;
28 import android.os.Trace;
29 import android.service.wallpaper.WallpaperService;
30 import android.util.Log;
31 import android.view.Surface;
32 import android.view.SurfaceHolder;
33 import android.view.WindowManager;
34 
35 import androidx.annotation.NonNull;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.systemui.dagger.qualifiers.LongRunning;
39 import com.android.systemui.settings.UserTracker;
40 import com.android.systemui.util.concurrency.DelayableExecutor;
41 
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.util.List;
45 
46 import javax.inject.Inject;
47 
48 /**
49  * Default built-in wallpaper that simply shows a static image.
50  */
51 @SuppressWarnings({"UnusedDeclaration"})
52 public class ImageWallpaper extends WallpaperService {
53 
54     private static final String TAG = ImageWallpaper.class.getSimpleName();
55     private static final boolean DEBUG = false;
56 
57     // keep track of the number of pages of the launcher for local color extraction purposes
58     private volatile int mPages = 1;
59     private boolean mPagesComputed = false;
60 
61     private final UserTracker mUserTracker;
62 
63     // used for most tasks (call canvas.drawBitmap, load/unload the bitmap)
64     @LongRunning
65     private final DelayableExecutor mLongExecutor;
66 
67     // wait at least this duration before unloading the bitmap
68     private static final int DELAY_UNLOAD_BITMAP = 2000;
69 
70     @Inject
ImageWallpaper(@ongRunning DelayableExecutor longExecutor, UserTracker userTracker)71     public ImageWallpaper(@LongRunning DelayableExecutor longExecutor, UserTracker userTracker) {
72         super();
73         mLongExecutor = longExecutor;
74         mUserTracker = userTracker;
75     }
76 
77     @Override
onCreateEngine()78     public Engine onCreateEngine() {
79         return new CanvasEngine();
80     }
81 
82     class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
83         private WallpaperManager mWallpaperManager;
84         private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
85         private SurfaceHolder mSurfaceHolder;
86         @VisibleForTesting
87         static final int MIN_SURFACE_WIDTH = 128;
88         @VisibleForTesting
89         static final int MIN_SURFACE_HEIGHT = 128;
90         private Bitmap mBitmap;
91         private boolean mWideColorGamut = false;
92 
93         /*
94          * Counter to unload the bitmap as soon as possible.
95          * Before any bitmap operation, this is incremented.
96          * After an operation completion, this is decremented (synchronously),
97          * and if the count is 0, unload the bitmap
98          */
99         private int mBitmapUsages = 0;
100         private final Object mLock = new Object();
101 
CanvasEngine()102         CanvasEngine() {
103             super();
104             setFixedSizeAllowed(true);
105             setShowForAllUsers(true);
106             mWallpaperLocalColorExtractor = new WallpaperLocalColorExtractor(
107                     mLongExecutor,
108                     new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
109                         @Override
110                         public void onColorsProcessed(List<RectF> regions,
111                                 List<WallpaperColors> colors) {
112                             CanvasEngine.this.onColorsProcessed(regions, colors);
113                         }
114 
115                         @Override
116                         public void onMiniBitmapUpdated() {
117                             CanvasEngine.this.onMiniBitmapUpdated();
118                         }
119 
120                         @Override
121                         public void onActivated() {
122                             setOffsetNotificationsEnabled(true);
123                         }
124 
125                         @Override
126                         public void onDeactivated() {
127                             setOffsetNotificationsEnabled(false);
128                         }
129                     });
130 
131             // if the number of pages is already computed, transmit it to the color extractor
132             if (mPagesComputed) {
133                 mWallpaperLocalColorExtractor.onPageChanged(mPages);
134             }
135         }
136 
137         @Override
onCreate(SurfaceHolder surfaceHolder)138         public void onCreate(SurfaceHolder surfaceHolder) {
139             Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
140             if (DEBUG) {
141                 Log.d(TAG, "onCreate");
142             }
143             mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
144             mSurfaceHolder = surfaceHolder;
145             Rect dimensions = mWallpaperManager.peekBitmapDimensions();
146             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
147             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
148             mSurfaceHolder.setFixedSize(width, height);
149 
150             getDisplayContext().getSystemService(DisplayManager.class)
151                     .registerDisplayListener(this, null);
152             getDisplaySizeAndUpdateColorExtractor();
153             Trace.endSection();
154         }
155 
156         @Override
onDestroy()157         public void onDestroy() {
158             getDisplayContext().getSystemService(DisplayManager.class)
159                     .unregisterDisplayListener(this);
160             mWallpaperLocalColorExtractor.cleanUp();
161         }
162 
163         @Override
shouldZoomOutWallpaper()164         public boolean shouldZoomOutWallpaper() {
165             return true;
166         }
167 
168         @Override
shouldWaitForEngineShown()169         public boolean shouldWaitForEngineShown() {
170             return true;
171         }
172 
173         @Override
onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)174         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
175             if (DEBUG) {
176                 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
177             }
178         }
179 
180         @Override
onSurfaceDestroyed(SurfaceHolder holder)181         public void onSurfaceDestroyed(SurfaceHolder holder) {
182             if (DEBUG) {
183                 Log.i(TAG, "onSurfaceDestroyed");
184             }
185             mSurfaceHolder = null;
186         }
187 
188         @Override
onSurfaceCreated(SurfaceHolder holder)189         public void onSurfaceCreated(SurfaceHolder holder) {
190             if (DEBUG) {
191                 Log.i(TAG, "onSurfaceCreated");
192             }
193         }
194 
195         @Override
onSurfaceRedrawNeeded(SurfaceHolder holder)196         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
197             if (DEBUG) {
198                 Log.d(TAG, "onSurfaceRedrawNeeded");
199             }
200             drawFrame();
201         }
202 
drawFrame()203         private void drawFrame() {
204             mLongExecutor.execute(this::drawFrameSynchronized);
205         }
206 
drawFrameSynchronized()207         private void drawFrameSynchronized() {
208             synchronized (mLock) {
209                 drawFrameInternal();
210             }
211         }
212 
drawFrameInternal()213         private void drawFrameInternal() {
214             if (mSurfaceHolder == null) {
215                 Log.e(TAG, "attempt to draw a frame without a valid surface");
216                 return;
217             }
218 
219             // load the wallpaper if not already done
220             if (!isBitmapLoaded()) {
221                 loadWallpaperAndDrawFrameInternal();
222             } else {
223                 mBitmapUsages++;
224                 drawFrameOnCanvas(mBitmap);
225                 reportEngineShown(false);
226                 unloadBitmapIfNotUsedInternal();
227             }
228         }
229 
230         @VisibleForTesting
drawFrameOnCanvas(Bitmap bitmap)231         void drawFrameOnCanvas(Bitmap bitmap) {
232             Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
233             Surface surface = mSurfaceHolder.getSurface();
234             Canvas canvas = null;
235             try {
236                 canvas = mWideColorGamut
237                         ? surface.lockHardwareWideColorGamutCanvas()
238                         : surface.lockHardwareCanvas();
239             } catch (IllegalStateException e) {
240                 Log.w(TAG, "Unable to lock canvas", e);
241             }
242             if (canvas != null) {
243                 Rect dest = mSurfaceHolder.getSurfaceFrame();
244                 try {
245                     canvas.drawBitmap(bitmap, null, dest, null);
246                 } finally {
247                     surface.unlockCanvasAndPost(canvas);
248                 }
249             }
250             Trace.endSection();
251         }
252 
253         @VisibleForTesting
isBitmapLoaded()254         boolean isBitmapLoaded() {
255             return mBitmap != null && !mBitmap.isRecycled();
256         }
257 
unloadBitmapIfNotUsed()258         private void unloadBitmapIfNotUsed() {
259             mLongExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
260         }
261 
unloadBitmapIfNotUsedSynchronized()262         private void unloadBitmapIfNotUsedSynchronized() {
263             synchronized (mLock) {
264                 unloadBitmapIfNotUsedInternal();
265             }
266         }
267 
unloadBitmapIfNotUsedInternal()268         private void unloadBitmapIfNotUsedInternal() {
269             mBitmapUsages -= 1;
270             if (mBitmapUsages <= 0) {
271                 mBitmapUsages = 0;
272                 unloadBitmapInternal();
273             }
274         }
275 
unloadBitmapInternal()276         private void unloadBitmapInternal() {
277             Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
278             if (mBitmap != null) {
279                 mBitmap.recycle();
280             }
281             mBitmap = null;
282 
283             final Surface surface = getSurfaceHolder().getSurface();
284             surface.hwuiDestroy();
285             mWallpaperManager.forgetLoadedWallpaper();
286             Trace.endSection();
287         }
288 
loadWallpaperAndDrawFrameInternal()289         private void loadWallpaperAndDrawFrameInternal() {
290             Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
291             boolean loadSuccess = false;
292             Bitmap bitmap;
293             try {
294                 bitmap = mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
295                 if (bitmap != null
296                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
297                     throw new RuntimeException("Wallpaper is too large to draw!");
298                 }
299             } catch (RuntimeException | OutOfMemoryError exception) {
300 
301                 // Note that if we do fail at this, and the default wallpaper can't
302                 // be loaded, we will go into a cycle. Don't do a build where the
303                 // default wallpaper can't be loaded.
304                 Log.w(TAG, "Unable to load wallpaper!", exception);
305                 mWallpaperManager.clearWallpaper(
306                         WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId());
307                 try {
308                     bitmap = mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
309                 } catch (RuntimeException | OutOfMemoryError e) {
310                     Log.w(TAG, "Unable to load default wallpaper!", e);
311                     bitmap = null;
312                 }
313             }
314 
315             if (bitmap == null) {
316                 Log.w(TAG, "Could not load bitmap");
317             } else if (bitmap.isRecycled()) {
318                 Log.e(TAG, "Attempt to load a recycled bitmap");
319             } else if (mBitmap == bitmap) {
320                 Log.e(TAG, "Loaded a bitmap that was already loaded");
321             } else {
322                 // at this point, loading is done correctly.
323                 loadSuccess = true;
324                 // recycle the previously loaded bitmap
325                 if (mBitmap != null) {
326                     mBitmap.recycle();
327                 }
328                 mBitmap = bitmap;
329                 mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(
330                         WallpaperManager.FLAG_SYSTEM);
331 
332                 // +2 usages for the color extraction and the delayed unload.
333                 mBitmapUsages += 2;
334                 recomputeColorExtractorMiniBitmap();
335                 drawFrameInternal();
336 
337                 /*
338                  * after loading, the bitmap will be unloaded after all these conditions:
339                  *   - the frame is redrawn
340                  *   - the mini bitmap from color extractor is recomputed
341                  *   - the DELAY_UNLOAD_BITMAP has passed
342                  */
343                 mLongExecutor.executeDelayed(
344                         this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
345             }
346             // even if the bitmap cannot be loaded, call reportEngineShown
347             if (!loadSuccess) reportEngineShown(false);
348             Trace.endSection();
349         }
350 
onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors)351         private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
352             try {
353                 notifyLocalColorsChanged(regions, colors);
354             } catch (RuntimeException e) {
355                 Log.e(TAG, e.getMessage(), e);
356             }
357         }
358 
359         @VisibleForTesting
recomputeColorExtractorMiniBitmap()360         void recomputeColorExtractorMiniBitmap() {
361             mWallpaperLocalColorExtractor.onBitmapChanged(mBitmap);
362         }
363 
364         @VisibleForTesting
onMiniBitmapUpdated()365         void onMiniBitmapUpdated() {
366             unloadBitmapIfNotUsed();
367         }
368 
369         @Override
supportsLocalColorExtraction()370         public boolean supportsLocalColorExtraction() {
371             return true;
372         }
373 
374         @Override
addLocalColorsAreas(@onNull List<RectF> regions)375         public void addLocalColorsAreas(@NonNull List<RectF> regions) {
376             // this call will activate the offset notifications
377             // if no colors were being processed before
378             mWallpaperLocalColorExtractor.addLocalColorsAreas(regions);
379         }
380 
381         @Override
removeLocalColorsAreas(@onNull List<RectF> regions)382         public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
383             // this call will deactivate the offset notifications
384             // if we are no longer processing colors
385             mWallpaperLocalColorExtractor.removeLocalColorAreas(regions);
386         }
387 
388         @Override
onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset)389         public void onOffsetsChanged(float xOffset, float yOffset,
390                 float xOffsetStep, float yOffsetStep,
391                 int xPixelOffset, int yPixelOffset) {
392             final int pages;
393             if (xOffsetStep > 0 && xOffsetStep <= 1) {
394                 pages = Math.round(1 / xOffsetStep) + 1;
395             } else {
396                 pages = 1;
397             }
398             if (pages != mPages || !mPagesComputed) {
399                 mPages = pages;
400                 mPagesComputed = true;
401                 mWallpaperLocalColorExtractor.onPageChanged(mPages);
402             }
403         }
404 
405         @Override
onDisplayAdded(int displayId)406         public void onDisplayAdded(int displayId) {
407 
408         }
409 
410         @Override
onDisplayRemoved(int displayId)411         public void onDisplayRemoved(int displayId) {
412 
413         }
414 
415         @Override
onDisplayChanged(int displayId)416         public void onDisplayChanged(int displayId) {
417             // changes the display in the color extractor
418             // the new display dimensions will be used in the next color computation
419             if (displayId == getDisplayContext().getDisplayId()) {
420                 getDisplaySizeAndUpdateColorExtractor();
421             }
422         }
423 
getDisplaySizeAndUpdateColorExtractor()424         private void getDisplaySizeAndUpdateColorExtractor() {
425             Rect window = getDisplayContext()
426                     .getSystemService(WindowManager.class)
427                     .getCurrentWindowMetrics()
428                     .getBounds();
429             mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
430         }
431 
432         @Override
dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args)433         protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
434             super.dump(prefix, fd, out, args);
435             out.print(prefix); out.print("Engine="); out.println(this);
436             out.print(prefix); out.print("valid surface=");
437             out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
438                     ? getSurfaceHolder().getSurface().isValid()
439                     : "null");
440 
441             out.print(prefix); out.print("surface frame=");
442             out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
443 
444             out.print(prefix); out.print("bitmap=");
445             out.println(mBitmap == null ? "null"
446                     : mBitmap.isRecycled() ? "recycled"
447                     : mBitmap.getWidth() + "x" + mBitmap.getHeight());
448 
449             mWallpaperLocalColorExtractor.dump(prefix, fd, out, args);
450         }
451     }
452 }
453