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 17package com.android.systemui; 18 19import android.app.WallpaperManager; 20import android.content.ComponentCallbacks2; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.graphics.RectF; 25import android.graphics.Region.Op; 26import android.os.AsyncTask; 27import android.os.Handler; 28import android.os.Trace; 29import android.service.wallpaper.WallpaperService; 30import android.util.Log; 31import android.view.Display; 32import android.view.DisplayInfo; 33import android.view.Surface; 34import android.view.SurfaceHolder; 35import android.view.WindowManager; 36 37import java.io.FileDescriptor; 38import java.io.IOException; 39import java.io.PrintWriter; 40 41/** 42 * Default built-in wallpaper that simply shows a static image. 43 */ 44@SuppressWarnings({"UnusedDeclaration"}) 45public 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 56 public void onCreate() { 57 super.onCreate(); 58 mWallpaperManager = getSystemService(WallpaperManager.class); 59 } 60 61 @Override 62 public void onTrimMemory(int level) { 63 if (mEngine != null) { 64 mEngine.trimMemory(level); 65 } 66 } 67 68 @Override 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 106 DrawableEngine() { 107 super(); 108 setFixedSizeAllowed(true); 109 } 110 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 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 138 public void onDestroy() { 139 super.onDestroy(); 140 mBackground = null; 141 unloadWallpaper(true /* forgetSize */); 142 } 143 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 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 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 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 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 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 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 259 private DisplayInfo getDefaultDisplayInfo() { 260 mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo); 261 return mTmpDisplayInfo; 262 } 263 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 */ 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 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 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 450 private void scheduleUnloadWallpaper() { 451 Handler handler = getMainThreadHandler(); 452 handler.removeCallbacks(mUnloadWallpaperCallback); 453 handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER); 454 } 455 456 @Override 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 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