• 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;
18 
19 import android.app.WallpaperManager;
20 import android.content.ComponentCallbacks2;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Rect;
24 import android.graphics.RectF;
25 import android.graphics.Region.Op;
26 import android.os.AsyncTask;
27 import android.os.Handler;
28 import android.os.Trace;
29 import android.service.wallpaper.WallpaperService;
30 import android.util.Log;
31 import android.view.Display;
32 import android.view.DisplayInfo;
33 import android.view.Surface;
34 import android.view.SurfaceHolder;
35 import android.view.WindowManager;
36 
37 import java.io.FileDescriptor;
38 import java.io.IOException;
39 import java.io.PrintWriter;
40 
41 /**
42  * Default built-in wallpaper that simply shows a static image.
43  */
44 @SuppressWarnings({"UnusedDeclaration"})
45 public class ImageWallpaper extends WallpaperService {
46     private static final String TAG = "ImageWallpaper";
47     private static final String GL_LOG_TAG = "ImageWallpaperGL";
48     private static final boolean DEBUG = false;
49     private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
50     private static final long DELAY_FORGET_WALLPAPER = 5000;
51 
52     private WallpaperManager mWallpaperManager;
53     private DrawableEngine mEngine;
54 
55     @Override
onCreate()56     public void onCreate() {
57         super.onCreate();
58         mWallpaperManager = getSystemService(WallpaperManager.class);
59     }
60 
61     @Override
onTrimMemory(int level)62     public void onTrimMemory(int level) {
63         if (mEngine != null) {
64             mEngine.trimMemory(level);
65         }
66     }
67 
68     @Override
onCreateEngine()69     public Engine onCreateEngine() {
70         mEngine = new DrawableEngine();
71         return mEngine;
72     }
73 
74     class DrawableEngine extends Engine {
75         private final Runnable mUnloadWallpaperCallback = () -> {
76             unloadWallpaper(false /* forgetSize */);
77         };
78 
79         Bitmap mBackground;
80         int mBackgroundWidth = -1, mBackgroundHeight = -1;
81         int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
82         int mLastRotation = -1;
83         float mXOffset = 0f;
84         float mYOffset = 0f;
85         float mScale = 1f;
86 
87         private Display mDefaultDisplay;
88         private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
89 
90         boolean mVisible = true;
91         boolean mOffsetsChanged;
92         int mLastXTranslation;
93         int mLastYTranslation;
94 
95         private int mRotationAtLastSurfaceSizeUpdate = -1;
96         private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
97         private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
98 
99         private int mLastRequestedWidth = -1;
100         private int mLastRequestedHeight = -1;
101         private AsyncTask<Void, Void, Bitmap> mLoader;
102         private boolean mNeedsDrawAfterLoadingWallpaper;
103         private boolean mSurfaceValid;
104         private boolean mSurfaceRedrawNeeded;
105 
DrawableEngine()106         DrawableEngine() {
107             super();
108             setFixedSizeAllowed(true);
109         }
110 
trimMemory(int level)111         void trimMemory(int level) {
112             if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
113                     && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
114                     && mBackground != null) {
115                 if (DEBUG) {
116                     Log.d(TAG, "trimMemory");
117                 }
118                 unloadWallpaper(true /* forgetSize */);
119             }
120         }
121 
122         @Override
onCreate(SurfaceHolder surfaceHolder)123         public void onCreate(SurfaceHolder surfaceHolder) {
124             if (DEBUG) {
125                 Log.d(TAG, "onCreate");
126             }
127 
128             super.onCreate(surfaceHolder);
129 
130             //noinspection ConstantConditions
131             mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
132             setOffsetNotificationsEnabled(false);
133 
134             updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
135         }
136 
137         @Override
onDestroy()138         public void onDestroy() {
139             super.onDestroy();
140             mBackground = null;
141             unloadWallpaper(true /* forgetSize */);
142         }
143 
updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo, boolean forDraw)144         boolean updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo,
145                 boolean forDraw) {
146             boolean hasWallpaper = true;
147 
148             // Load background image dimensions, if we haven't saved them yet
149             if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
150                 // Need to load the image to get dimensions
151                 loadWallpaper(forDraw);
152                 if (DEBUG) {
153                     Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
154                 }
155                 hasWallpaper = false;
156             }
157 
158             // Force the wallpaper to cover the screen in both dimensions
159             int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
160             int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
161 
162             // Used a fixed size surface, because we are special.  We can do
163             // this because we know the current design of window animations doesn't
164             // cause this to break.
165             surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
166             mLastRequestedWidth = surfaceWidth;
167             mLastRequestedHeight = surfaceHeight;
168 
169             return hasWallpaper;
170         }
171 
172         @Override
onVisibilityChanged(boolean visible)173         public void onVisibilityChanged(boolean visible) {
174             if (DEBUG) {
175                 Log.d(TAG, "onVisibilityChanged: mVisible, visible=" + mVisible + ", " + visible);
176             }
177 
178             if (mVisible != visible) {
179                 if (DEBUG) {
180                     Log.d(TAG, "Visibility changed to visible=" + visible);
181                 }
182                 mVisible = visible;
183                 if (visible) {
184                     drawFrame();
185                 }
186             }
187         }
188 
189         @Override
onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixels, int yPixels)190         public void onOffsetsChanged(float xOffset, float yOffset,
191                 float xOffsetStep, float yOffsetStep,
192                 int xPixels, int yPixels) {
193             if (DEBUG) {
194                 Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset
195                         + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep
196                         + ", xPixels=" + xPixels + ", yPixels=" + yPixels);
197             }
198 
199             if (mXOffset != xOffset || mYOffset != yOffset) {
200                 if (DEBUG) {
201                     Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ").");
202                 }
203                 mXOffset = xOffset;
204                 mYOffset = yOffset;
205                 mOffsetsChanged = true;
206             }
207             drawFrame();
208         }
209 
210         @Override
onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)211         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
212             if (DEBUG) {
213                 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
214             }
215 
216             super.onSurfaceChanged(holder, format, width, height);
217 
218             drawFrame();
219         }
220 
221         @Override
onSurfaceDestroyed(SurfaceHolder holder)222         public void onSurfaceDestroyed(SurfaceHolder holder) {
223             super.onSurfaceDestroyed(holder);
224             if (DEBUG) {
225                 Log.i(TAG, "onSurfaceDestroyed");
226             }
227 
228             mLastSurfaceWidth = mLastSurfaceHeight = -1;
229             mSurfaceValid = false;
230         }
231 
232         @Override
onSurfaceCreated(SurfaceHolder holder)233         public void onSurfaceCreated(SurfaceHolder holder) {
234             super.onSurfaceCreated(holder);
235             if (DEBUG) {
236                 Log.i(TAG, "onSurfaceCreated");
237             }
238 
239             mLastSurfaceWidth = mLastSurfaceHeight = -1;
240             mSurfaceValid = true;
241         }
242 
243         @Override
onSurfaceRedrawNeeded(SurfaceHolder holder)244         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
245             if (DEBUG) {
246                 Log.d(TAG, "onSurfaceRedrawNeeded");
247             }
248             super.onSurfaceRedrawNeeded(holder);
249             // At the end of this method we should have drawn into the surface.
250             // This means that the bitmap should be loaded synchronously if
251             // it was already unloaded.
252             if (mBackground == null) {
253                 updateBitmap(mWallpaperManager.getBitmap(true /* hardware */));
254             }
255             mSurfaceRedrawNeeded = true;
256             drawFrame();
257         }
258 
getDefaultDisplayInfo()259         private DisplayInfo getDefaultDisplayInfo() {
260             mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
261             return mTmpDisplayInfo;
262         }
263 
drawFrame()264         void drawFrame() {
265             if (!mSurfaceValid) {
266                 return;
267             }
268             try {
269                 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
270                 DisplayInfo displayInfo = getDefaultDisplayInfo();
271                 int newRotation = displayInfo.rotation;
272 
273                 // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
274                 // Call updateSurfaceSize -- it will only actually do the update if the dimensions
275                 // should change
276                 if (newRotation != mLastRotation) {
277                     // Update surface size (if necessary)
278                     if (!updateSurfaceSize(getSurfaceHolder(), displayInfo, true /* forDraw */)) {
279                         return; // had to reload wallpaper, will retry later
280                     }
281                     mRotationAtLastSurfaceSizeUpdate = newRotation;
282                     mDisplayWidthAtLastSurfaceSizeUpdate = displayInfo.logicalWidth;
283                     mDisplayHeightAtLastSurfaceSizeUpdate = displayInfo.logicalHeight;
284                 }
285                 SurfaceHolder sh = getSurfaceHolder();
286                 final Rect frame = sh.getSurfaceFrame();
287                 final int dw = frame.width();
288                 final int dh = frame.height();
289                 boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
290                         || dh != mLastSurfaceHeight;
291 
292                 boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation
293                         || mSurfaceRedrawNeeded;
294                 if (!redrawNeeded && !mOffsetsChanged) {
295                     if (DEBUG) {
296                         Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
297                                 + "and offsets have not changed.");
298                     }
299                     return;
300                 }
301                 mLastRotation = newRotation;
302                 mSurfaceRedrawNeeded = false;
303 
304                 // Load bitmap if it is not yet loaded
305                 if (mBackground == null) {
306                     loadWallpaper(true);
307                     if (DEBUG) {
308                         Log.d(TAG, "Reloading, resuming draw later");
309                     }
310                     return;
311                 }
312 
313                 // Left align the scaled image
314                 mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
315                         dh / (float) mBackground.getHeight()));
316                 final int availw = (int) (mBackground.getWidth() * mScale) - dw;
317                 final int availh = (int) (mBackground.getHeight() * mScale) - dh;
318                 int xPixels = (int) (availw * mXOffset);
319                 int yPixels = (int) (availh * mYOffset);
320 
321                 mOffsetsChanged = false;
322                 if (surfaceDimensionsChanged) {
323                     mLastSurfaceWidth = dw;
324                     mLastSurfaceHeight = dh;
325                 }
326                 if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
327                     if (DEBUG) {
328                         Log.d(TAG, "Suppressed drawFrame since the image has not "
329                                 + "actually moved an integral number of pixels.");
330                     }
331                     return;
332                 }
333                 mLastXTranslation = xPixels;
334                 mLastYTranslation = yPixels;
335 
336                 if (DEBUG) {
337                     Log.d(TAG, "Redrawing wallpaper");
338                 }
339 
340                 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
341                 scheduleUnloadWallpaper();
342             } finally {
343                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
344             }
345         }
346 
347         /**
348          * Loads the wallpaper on background thread and schedules updating the surface frame,
349          * and if {@param needsDraw} is set also draws a frame.
350          *
351          * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
352          * the active request).
353          *
354          * If {@param needsReset} is set also clears the cache in WallpaperManager first.
355          */
loadWallpaper(boolean needsDraw)356         private void loadWallpaper(boolean needsDraw) {
357             mNeedsDrawAfterLoadingWallpaper |= needsDraw;
358             if (mLoader != null) {
359                 if (DEBUG) {
360                     Log.d(TAG, "Skipping loadWallpaper, already in flight ");
361                 }
362                 return;
363             }
364             mLoader = new AsyncTask<Void, Void, Bitmap>() {
365                 @Override
366                 protected Bitmap doInBackground(Void... params) {
367                     Throwable exception;
368                     try {
369                         return mWallpaperManager.getBitmap(true /* hardware */);
370                     } catch (RuntimeException | OutOfMemoryError e) {
371                         exception = e;
372                     }
373 
374                     if (isCancelled()) {
375                         return null;
376                     }
377 
378                     // Note that if we do fail at this, and the default wallpaper can't
379                     // be loaded, we will go into a cycle.  Don't do a build where the
380                     // default wallpaper can't be loaded.
381                     Log.w(TAG, "Unable to load wallpaper!", exception);
382                     try {
383                         mWallpaperManager.clear();
384                     } catch (IOException ex) {
385                         // now we're really screwed.
386                         Log.w(TAG, "Unable reset to default wallpaper!", ex);
387                     }
388 
389                     if (isCancelled()) {
390                         return null;
391                     }
392 
393                     try {
394                         return mWallpaperManager.getBitmap(true /* hardware */);
395                     } catch (RuntimeException | OutOfMemoryError e) {
396                         Log.w(TAG, "Unable to load default wallpaper!", e);
397                     }
398                     return null;
399                 }
400 
401                 @Override
402                 protected void onPostExecute(Bitmap b) {
403                     updateBitmap(b);
404 
405                     if (mNeedsDrawAfterLoadingWallpaper) {
406                         drawFrame();
407                     }
408 
409                     mLoader = null;
410                     mNeedsDrawAfterLoadingWallpaper = false;
411                 }
412             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
413         }
414 
updateBitmap(Bitmap bitmap)415         private void updateBitmap(Bitmap bitmap) {
416             mBackground = null;
417             mBackgroundWidth = -1;
418             mBackgroundHeight = -1;
419 
420             if (bitmap != null) {
421                 mBackground = bitmap;
422                 mBackgroundWidth = mBackground.getWidth();
423                 mBackgroundHeight = mBackground.getHeight();
424             }
425 
426             if (DEBUG) {
427                 Log.d(TAG, "Wallpaper loaded: " + mBackground);
428             }
429             updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
430                     false /* forDraw */);
431         }
432 
unloadWallpaper(boolean forgetSize)433         private void unloadWallpaper(boolean forgetSize) {
434             if (mLoader != null) {
435                 mLoader.cancel(false);
436                 mLoader = null;
437             }
438             mBackground = null;
439             if (forgetSize) {
440                 mBackgroundWidth = -1;
441                 mBackgroundHeight = -1;
442             }
443 
444             final Surface surface = getSurfaceHolder().getSurface();
445             surface.hwuiDestroy();
446 
447             mWallpaperManager.forgetLoadedWallpaper();
448         }
449 
scheduleUnloadWallpaper()450         private void scheduleUnloadWallpaper() {
451             Handler handler = getMainThreadHandler();
452             handler.removeCallbacks(mUnloadWallpaperCallback);
453             handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
454         }
455 
456         @Override
dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args)457         protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
458             super.dump(prefix, fd, out, args);
459 
460             out.print(prefix); out.println("ImageWallpaper.DrawableEngine:");
461             out.print(prefix); out.print(" mBackground="); out.print(mBackground);
462             out.print(" mBackgroundWidth="); out.print(mBackgroundWidth);
463             out.print(" mBackgroundHeight="); out.println(mBackgroundHeight);
464 
465             out.print(prefix); out.print(" mLastRotation="); out.print(mLastRotation);
466             out.print(" mLastSurfaceWidth="); out.print(mLastSurfaceWidth);
467             out.print(" mLastSurfaceHeight="); out.println(mLastSurfaceHeight);
468 
469             out.print(prefix); out.print(" mXOffset="); out.print(mXOffset);
470             out.print(" mYOffset="); out.println(mYOffset);
471 
472             out.print(prefix); out.print(" mVisible="); out.print(mVisible);
473             out.print(" mOffsetsChanged="); out.println(mOffsetsChanged);
474 
475             out.print(prefix); out.print(" mLastXTranslation="); out.print(mLastXTranslation);
476             out.print(" mLastYTranslation="); out.print(mLastYTranslation);
477             out.print(" mScale="); out.println(mScale);
478 
479             out.print(prefix); out.print(" mLastRequestedWidth="); out.print(mLastRequestedWidth);
480             out.print(" mLastRequestedHeight="); out.println(mLastRequestedHeight);
481 
482             out.print(prefix); out.println(" DisplayInfo at last updateSurfaceSize:");
483             out.print(prefix);
484             out.print("  rotation="); out.print(mRotationAtLastSurfaceSizeUpdate);
485             out.print("  width="); out.print(mDisplayWidthAtLastSurfaceSizeUpdate);
486             out.print("  height="); out.println(mDisplayHeightAtLastSurfaceSizeUpdate);
487         }
488 
drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top)489         private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
490             Canvas c = sh.lockHardwareCanvas();
491             if (c != null) {
492                 try {
493                     if (DEBUG) {
494                         Log.d(TAG, "Redrawing: left=" + left + ", top=" + top);
495                     }
496 
497                     final float right = left + mBackground.getWidth() * mScale;
498                     final float bottom = top + mBackground.getHeight() * mScale;
499                     if (w < 0 || h < 0) {
500                         c.save(Canvas.CLIP_SAVE_FLAG);
501                         c.clipRect(left, top, right, bottom,
502                                 Op.DIFFERENCE);
503                         c.drawColor(0xff000000);
504                         c.restore();
505                     }
506                     if (mBackground != null) {
507                         RectF dest = new RectF(left, top, right, bottom);
508                         Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
509                                 + mLastRequestedWidth + "x" + mLastRequestedHeight);
510                         c.drawBitmap(mBackground, null, dest, null);
511                     }
512                 } finally {
513                     sh.unlockCanvasAndPost(c);
514                 }
515             }
516         }
517     }
518 }
519