WallpaperManager.java revision 8b2e000c43f5a93209be269a0b9e08943fad8d3c
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 android.app; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.res.Resources; 22import android.graphics.Bitmap; 23import android.graphics.BitmapFactory; 24import android.graphics.Canvas; 25import android.graphics.ColorFilter; 26import android.graphics.Paint; 27import android.graphics.PixelFormat; 28import android.graphics.Rect; 29import android.graphics.drawable.BitmapDrawable; 30import android.graphics.drawable.Drawable; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.IBinder; 34import android.os.Looper; 35import android.os.Message; 36import android.os.ParcelFileDescriptor; 37import android.os.RemoteException; 38import android.os.ServiceManager; 39import android.util.DisplayMetrics; 40import android.util.Log; 41import android.view.ViewRoot; 42 43import java.io.FileOutputStream; 44import java.io.IOException; 45import java.io.InputStream; 46 47/** 48 * Provides access to the system wallpaper. With WallpaperManager, you can 49 * get the current wallpaper, get the desired dimensions for the wallpaper, set 50 * the wallpaper, and more. Get an instance of WallpaperManager with 51 * {@link #getInstance(android.content.Context) getInstance()}. 52 */ 53public class WallpaperManager { 54 private static String TAG = "WallpaperManager"; 55 private static boolean DEBUG = false; 56 57 /** 58 * Launch an activity for the user to pick the current global live 59 * wallpaper. 60 * @hide Live Wallpaper 61 */ 62 public static final String ACTION_LIVE_WALLPAPER_CHOOSER 63 = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; 64 65 private final Context mContext; 66 67 /** 68 * Special drawable that draws a wallpaper as fast as possible. Assumes 69 * no scaling or placement off (0,0) of the wallpaper (this should be done 70 * at the time the bitmap is loaded). 71 */ 72 static class FastBitmapDrawable extends Drawable { 73 private final Bitmap mBitmap; 74 private final int mWidth; 75 private final int mHeight; 76 private int mDrawLeft; 77 private int mDrawTop; 78 79 private FastBitmapDrawable(Bitmap bitmap) { 80 mBitmap = bitmap; 81 mWidth = bitmap.getWidth(); 82 mHeight = bitmap.getHeight(); 83 setBounds(0, 0, mWidth, mHeight); 84 } 85 86 @Override 87 public void draw(Canvas canvas) { 88 canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, null); 89 } 90 91 @Override 92 public int getOpacity() { 93 return PixelFormat.OPAQUE; 94 } 95 96 @Override 97 public void setBounds(int left, int top, int right, int bottom) { 98 mDrawLeft = left + (right-left - mWidth) / 2; 99 mDrawTop = top + (bottom-top - mHeight) / 2; 100 } 101 102 @Override 103 public void setBounds(Rect bounds) { 104 // TODO Auto-generated method stub 105 super.setBounds(bounds); 106 } 107 108 @Override 109 public void setAlpha(int alpha) { 110 throw new UnsupportedOperationException( 111 "Not supported with this drawable"); 112 } 113 114 @Override 115 public void setColorFilter(ColorFilter cf) { 116 throw new UnsupportedOperationException( 117 "Not supported with this drawable"); 118 } 119 120 @Override 121 public void setDither(boolean dither) { 122 throw new UnsupportedOperationException( 123 "Not supported with this drawable"); 124 } 125 126 @Override 127 public void setFilterBitmap(boolean filter) { 128 throw new UnsupportedOperationException( 129 "Not supported with this drawable"); 130 } 131 132 @Override 133 public int getIntrinsicWidth() { 134 return mWidth; 135 } 136 137 @Override 138 public int getIntrinsicHeight() { 139 return mHeight; 140 } 141 142 @Override 143 public int getMinimumWidth() { 144 return mWidth; 145 } 146 147 @Override 148 public int getMinimumHeight() { 149 return mHeight; 150 } 151 } 152 153 static class Globals extends IWallpaperManagerCallback.Stub { 154 private IWallpaperManager mService; 155 private Bitmap mWallpaper; 156 private Bitmap mDefaultWallpaper; 157 158 private static final int MSG_CLEAR_WALLPAPER = 1; 159 160 private final Handler mHandler; 161 162 Globals(Looper looper) { 163 IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); 164 mService = IWallpaperManager.Stub.asInterface(b); 165 mHandler = new Handler(looper) { 166 @Override 167 public void handleMessage(Message msg) { 168 switch (msg.what) { 169 case MSG_CLEAR_WALLPAPER: 170 synchronized (this) { 171 mWallpaper = null; 172 mDefaultWallpaper = null; 173 } 174 break; 175 } 176 } 177 }; 178 } 179 180 public void onWallpaperChanged() { 181 /* The wallpaper has changed but we shouldn't eagerly load the 182 * wallpaper as that would be inefficient. Reset the cached wallpaper 183 * to null so if the user requests the wallpaper again then we'll 184 * fetch it. 185 */ 186 mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); 187 } 188 189 public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { 190 synchronized (this) { 191 if (mWallpaper != null) { 192 return mWallpaper; 193 } 194 if (mDefaultWallpaper != null) { 195 return mDefaultWallpaper; 196 } 197 mWallpaper = getCurrentWallpaperLocked(context); 198 if (mWallpaper == null && returnDefault) { 199 mDefaultWallpaper = getDefaultWallpaperLocked(context); 200 return mDefaultWallpaper; 201 } 202 return mWallpaper; 203 } 204 } 205 206 private Bitmap getCurrentWallpaperLocked(Context context) { 207 try { 208 Bundle params = new Bundle(); 209 ParcelFileDescriptor fd = mService.getWallpaper(this, params); 210 if (fd != null) { 211 int width = params.getInt("width", 0); 212 int height = params.getInt("height", 0); 213 214 if (width <= 0 || height <= 0) { 215 // Degenerate case: no size requested, just load 216 // bitmap as-is. 217 Bitmap bm = BitmapFactory.decodeFileDescriptor( 218 fd.getFileDescriptor(), null, null); 219 try { 220 fd.close(); 221 } catch (IOException e) { 222 } 223 if (bm != null) { 224 bm.setDensity(DisplayMetrics.DENSITY_DEVICE); 225 } 226 return bm; 227 } 228 229 // Load the bitmap with full color depth, to preserve 230 // quality for later processing. 231 BitmapFactory.Options options = new BitmapFactory.Options(); 232 options.inDither = false; 233 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 234 Bitmap bm = BitmapFactory.decodeFileDescriptor( 235 fd.getFileDescriptor(), null, options); 236 try { 237 fd.close(); 238 } catch (IOException e) { 239 } 240 241 return generateBitmap(context, bm, width, height); 242 } 243 } catch (RemoteException e) { 244 } 245 return null; 246 } 247 248 private Bitmap getDefaultWallpaperLocked(Context context) { 249 try { 250 InputStream is = context.getResources().openRawResource( 251 com.android.internal.R.drawable.default_wallpaper); 252 if (is != null) { 253 int width = mService.getWidthHint(); 254 int height = mService.getHeightHint(); 255 256 if (width <= 0 || height <= 0) { 257 // Degenerate case: no size requested, just load 258 // bitmap as-is. 259 Bitmap bm = BitmapFactory.decodeStream(is, null, null); 260 try { 261 is.close(); 262 } catch (IOException e) { 263 } 264 if (bm != null) { 265 bm.setDensity(DisplayMetrics.DENSITY_DEVICE); 266 } 267 return bm; 268 } 269 270 // Load the bitmap with full color depth, to preserve 271 // quality for later processing. 272 BitmapFactory.Options options = new BitmapFactory.Options(); 273 options.inDither = false; 274 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 275 Bitmap bm = BitmapFactory.decodeStream(is, null, options); 276 try { 277 is.close(); 278 } catch (IOException e) { 279 } 280 281 return generateBitmap(context, bm, width, height); 282 } 283 } catch (RemoteException e) { 284 } 285 return null; 286 } 287 } 288 289 private static Object mSync = new Object(); 290 private static Globals sGlobals; 291 292 static void initGlobals(Looper looper) { 293 synchronized (mSync) { 294 if (sGlobals == null) { 295 sGlobals = new Globals(looper); 296 } 297 } 298 } 299 300 /*package*/ WallpaperManager(Context context, Handler handler) { 301 mContext = context; 302 initGlobals(context.getMainLooper()); 303 } 304 305 /** 306 * Retrieve a WallpaperManager associated with the given Context. 307 */ 308 public static WallpaperManager getInstance(Context context) { 309 return (WallpaperManager)context.getSystemService( 310 Context.WALLPAPER_SERVICE); 311 } 312 313 /** @hide */ 314 public IWallpaperManager getIWallpaperManager() { 315 return sGlobals.mService; 316 } 317 318 /** 319 * Retrieve the current system wallpaper; if 320 * no wallpaper is set, the system default wallpaper is returned. 321 * This is returned as an 322 * abstract Drawable that you can install in a View to display whatever 323 * wallpaper the user has currently set. 324 * 325 * @return Returns a Drawable object that will draw the wallpaper. 326 */ 327 public Drawable getDrawable() { 328 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); 329 if (bm != null) { 330 Drawable dr = new BitmapDrawable(mContext.getResources(), bm); 331 dr.setDither(false); 332 return dr; 333 } 334 return null; 335 } 336 337 /** 338 * Retrieve the current system wallpaper; if there is no wallpaper set, 339 * a null pointer is returned. This is returned as an 340 * abstract Drawable that you can install in a View to display whatever 341 * wallpaper the user has currently set. 342 * 343 * @return Returns a Drawable object that will draw the wallpaper or a 344 * null pointer if these is none. 345 */ 346 public Drawable peekDrawable() { 347 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); 348 if (bm != null) { 349 Drawable dr = new BitmapDrawable(mContext.getResources(), bm); 350 dr.setDither(false); 351 return dr; 352 } 353 return null; 354 } 355 356 /** 357 * Like {@link #getDrawable()}, but the returned Drawable has a number 358 * of limitations to reduce its overhead as much as possible. It will 359 * never scale the wallpaper (only centering it if the requested bounds 360 * do match the bitmap bounds, which should not be typical), doesn't 361 * allow setting an alpha, color filter, or other attributes, etc. The 362 * bounds of the returned drawable will be initialized to the same bounds 363 * as the wallpaper, so normally you will not need to touch it. The 364 * drawable also assumes that it will be used in a context running in 365 * the same density as the screen (not in density compatibility mode). 366 * 367 * @return Returns a Drawable object that will draw the wallpaper. 368 */ 369 public Drawable getFastDrawable() { 370 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); 371 if (bm != null) { 372 Drawable dr = new FastBitmapDrawable(bm); 373 return dr; 374 } 375 return null; 376 } 377 378 /** 379 * Like {@link #getFastDrawable()}, but if there is no wallpaper set, 380 * a null pointer is returned. 381 * 382 * @return Returns an optimized Drawable object that will draw the 383 * wallpaper or a null pointer if these is none. 384 */ 385 public Drawable peekFastDrawable() { 386 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); 387 if (bm != null) { 388 Drawable dr = new FastBitmapDrawable(bm); 389 return dr; 390 } 391 return null; 392 } 393 394 /** 395 * If the current wallpaper is a live wallpaper component, return the 396 * information about that wallpaper. Otherwise, if it is a static image, 397 * simply return null. 398 * @hide Live Wallpaper 399 */ 400 public WallpaperInfo getWallpaperInfo() { 401 try { 402 return sGlobals.mService.getWallpaperInfo(); 403 } catch (RemoteException e) { 404 return null; 405 } 406 } 407 408 /** 409 * Change the current system wallpaper to the bitmap in the given resource. 410 * The resource is opened as a raw data stream and copied into the 411 * wallpaper; it must be a valid PNG or JPEG image. On success, the intent 412 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. 413 * 414 * @param resid The bitmap to save. 415 * 416 * @throws IOException If an error occurs reverting to the default 417 * wallpaper. 418 */ 419 public void setResource(int resid) throws IOException { 420 try { 421 Resources resources = mContext.getResources(); 422 /* Set the wallpaper to the default values */ 423 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( 424 "res:" + resources.getResourceName(resid)); 425 if (fd != null) { 426 FileOutputStream fos = null; 427 try { 428 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 429 setWallpaper(resources.openRawResource(resid), fos); 430 } finally { 431 if (fos != null) { 432 fos.close(); 433 } 434 } 435 } 436 } catch (RemoteException e) { 437 } 438 } 439 440 /** 441 * Change the current system wallpaper to a bitmap. The given bitmap is 442 * converted to a PNG and stored as the wallpaper. On success, the intent 443 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. 444 * 445 * @param bitmap The bitmap to save. 446 * 447 * @throws IOException If an error occurs reverting to the default 448 * wallpaper. 449 */ 450 public void setBitmap(Bitmap bitmap) throws IOException { 451 try { 452 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); 453 if (fd == null) { 454 return; 455 } 456 FileOutputStream fos = null; 457 try { 458 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 459 bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); 460 } finally { 461 if (fos != null) { 462 fos.close(); 463 } 464 } 465 } catch (RemoteException e) { 466 } 467 } 468 469 /** 470 * Change the current system wallpaper to a specific byte stream. The 471 * give InputStream is copied into persistent storage and will now be 472 * used as the wallpaper. Currently it must be either a JPEG or PNG 473 * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} 474 * is broadcast. 475 * 476 * @param data A stream containing the raw data to install as a wallpaper. 477 * 478 * @throws IOException If an error occurs reverting to the default 479 * wallpaper. 480 */ 481 public void setStream(InputStream data) throws IOException { 482 try { 483 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); 484 if (fd == null) { 485 return; 486 } 487 FileOutputStream fos = null; 488 try { 489 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 490 setWallpaper(data, fos); 491 } finally { 492 if (fos != null) { 493 fos.close(); 494 } 495 } 496 } catch (RemoteException e) { 497 } 498 } 499 500 private void setWallpaper(InputStream data, FileOutputStream fos) 501 throws IOException { 502 byte[] buffer = new byte[32768]; 503 int amt; 504 while ((amt=data.read(buffer)) > 0) { 505 fos.write(buffer, 0, amt); 506 } 507 } 508 509 /** 510 * Returns the desired minimum width for the wallpaper. Callers of 511 * {@link #setBitmap(android.graphics.Bitmap)} or 512 * {@link #setStream(java.io.InputStream)} should check this value 513 * beforehand to make sure the supplied wallpaper respects the desired 514 * minimum width. 515 * 516 * If the returned value is <= 0, the caller should use the width of 517 * the default display instead. 518 * 519 * @return The desired minimum width for the wallpaper. This value should 520 * be honored by applications that set the wallpaper but it is not 521 * mandatory. 522 */ 523 public int getDesiredMinimumWidth() { 524 try { 525 return sGlobals.mService.getWidthHint(); 526 } catch (RemoteException e) { 527 // Shouldn't happen! 528 return 0; 529 } 530 } 531 532 /** 533 * Returns the desired minimum height for the wallpaper. Callers of 534 * {@link #setBitmap(android.graphics.Bitmap)} or 535 * {@link #setStream(java.io.InputStream)} should check this value 536 * beforehand to make sure the supplied wallpaper respects the desired 537 * minimum height. 538 * 539 * If the returned value is <= 0, the caller should use the height of 540 * the default display instead. 541 * 542 * @return The desired minimum height for the wallpaper. This value should 543 * be honored by applications that set the wallpaper but it is not 544 * mandatory. 545 */ 546 public int getDesiredMinimumHeight() { 547 try { 548 return sGlobals.mService.getHeightHint(); 549 } catch (RemoteException e) { 550 // Shouldn't happen! 551 return 0; 552 } 553 } 554 555 /** 556 * For use only by the current home application, to specify the size of 557 * wallpaper it would like to use. This allows such applications to have 558 * a virtual wallpaper that is larger than the physical screen, matching 559 * the size of their workspace. 560 * @param minimumWidth Desired minimum width 561 * @param minimumHeight Desired minimum height 562 */ 563 public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { 564 try { 565 sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); 566 } catch (RemoteException e) { 567 } 568 } 569 570 /** 571 * Set the position of the current wallpaper within any larger space, when 572 * that wallpaper is visible behind the given window. The X and Y offsets 573 * are floating point numbers ranging from 0 to 1, representing where the 574 * wallpaper should be positioned within the screen space. These only 575 * make sense when the wallpaper is larger than the screen. 576 * 577 * @param windowToken The window who these offsets should be associated 578 * with, as returned by {@link android.view.View#getWindowToken() 579 * View.getWindowToken()}. 580 * @param xOffset The offset olong the X dimension, from 0 to 1. 581 * @param yOffset The offset along the Y dimension, from 0 to 1. 582 */ 583 public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { 584 try { 585 //Log.v(TAG, "Sending new wallpaper offsets from app..."); 586 ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( 587 windowToken, xOffset, yOffset); 588 //Log.v(TAG, "...app returning after sending offsets!"); 589 } catch (RemoteException e) { 590 // Ignore. 591 } 592 } 593 594 /** 595 * Clear the offsets previously associated with this window through 596 * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts 597 * the window to its default state, where it does not cause the wallpaper 598 * to scroll from whatever its last offsets were. 599 * 600 * @param windowToken The window who these offsets should be associated 601 * with, as returned by {@link android.view.View#getWindowToken() 602 * View.getWindowToken()}. 603 */ 604 public void clearWallpaperOffsets(IBinder windowToken) { 605 try { 606 ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( 607 windowToken, -1, -1); 608 } catch (RemoteException e) { 609 // Ignore. 610 } 611 } 612 613 /** 614 * Remove any currently set wallpaper, reverting to the system's default 615 * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} 616 * is broadcast. 617 * 618 * @throws IOException If an error occurs reverting to the default 619 * wallpaper. 620 */ 621 public void clear() throws IOException { 622 setResource(com.android.internal.R.drawable.default_wallpaper); 623 } 624 625 static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { 626 if (bm == null) { 627 return bm; 628 } 629 bm.setDensity(DisplayMetrics.DENSITY_DEVICE); 630 631 // This is the final bitmap we want to return. 632 // XXX We should get the pixel depth from the system (to match the 633 // physical display depth), when there is a way. 634 Bitmap newbm = Bitmap.createBitmap(width, height, 635 Bitmap.Config.RGB_565); 636 newbm.setDensity(DisplayMetrics.DENSITY_DEVICE); 637 Canvas c = new Canvas(newbm); 638 c.setDensity(DisplayMetrics.DENSITY_DEVICE); 639 Rect targetRect = new Rect(); 640 targetRect.left = targetRect.top = 0; 641 targetRect.right = bm.getWidth(); 642 targetRect.bottom = bm.getHeight(); 643 644 int deltaw = width - targetRect.right; 645 int deltah = height - targetRect.bottom; 646 647 if (deltaw > 0 || deltah > 0) { 648 // We need to scale up so it covers the entire 649 // area. 650 float scale = 1.0f; 651 if (deltaw > deltah) { 652 scale = width / (float)targetRect.right; 653 } else { 654 scale = height / (float)targetRect.bottom; 655 } 656 targetRect.right = (int)(targetRect.right*scale); 657 targetRect.bottom = (int)(targetRect.bottom*scale); 658 deltaw = width - targetRect.right; 659 deltah = height - targetRect.bottom; 660 } 661 662 targetRect.offset(deltaw/2, deltah/2); 663 Paint paint = new Paint(); 664 paint.setFilterBitmap(true); 665 paint.setDither(true); 666 c.drawBitmap(bm, null, targetRect, paint); 667 668 bm.recycle(); 669 return newbm; 670 } 671} 672