WallpaperManager.java revision b668d0ba7e3c18dd0e9ee9654b4ffdc6c6a8a71f
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.pm.PackageManager; 22import android.content.pm.ResolveInfo; 23import android.content.res.Resources; 24import android.graphics.Bitmap; 25import android.graphics.BitmapFactory; 26import android.graphics.BitmapRegionDecoder; 27import android.graphics.Canvas; 28import android.graphics.ColorFilter; 29import android.graphics.Matrix; 30import android.graphics.Paint; 31import android.graphics.PixelFormat; 32import android.graphics.PorterDuff; 33import android.graphics.PorterDuffXfermode; 34import android.graphics.Rect; 35import android.graphics.RectF; 36import android.graphics.drawable.BitmapDrawable; 37import android.graphics.drawable.Drawable; 38import android.net.Uri; 39import android.os.Bundle; 40import android.os.Handler; 41import android.os.IBinder; 42import android.os.Looper; 43import android.os.Message; 44import android.os.ParcelFileDescriptor; 45import android.os.RemoteException; 46import android.os.ServiceManager; 47import android.util.DisplayMetrics; 48import android.util.Log; 49import android.view.WindowManager; 50import android.view.WindowManagerGlobal; 51 52import java.io.BufferedInputStream; 53import java.io.FileOutputStream; 54import java.io.IOException; 55import java.io.InputStream; 56import java.util.List; 57 58/** 59 * Provides access to the system wallpaper. With WallpaperManager, you can 60 * get the current wallpaper, get the desired dimensions for the wallpaper, set 61 * the wallpaper, and more. Get an instance of WallpaperManager with 62 * {@link #getInstance(android.content.Context) getInstance()}. 63 */ 64public class WallpaperManager { 65 private static String TAG = "WallpaperManager"; 66 private static boolean DEBUG = false; 67 private float mWallpaperXStep = -1; 68 private float mWallpaperYStep = -1; 69 70 /** 71 * Activity Action: Show settings for choosing wallpaper. Do not use directly to construct 72 * an intent; instead, use {@link #getCropAndSetWallpaperIntent}. 73 * <p>Input: {@link Intent#getData} is the URI of the image to crop and set as wallpaper. 74 * <p>Output: RESULT_OK if user decided to crop/set the wallpaper, RESULT_CANCEL otherwise 75 */ 76 public static final String ACTION_CROP_AND_SET_WALLPAPER = 77 "android.service.wallpaper.CROP_AND_SET_WALLPAPER"; 78 79 /** 80 * Launch an activity for the user to pick the current global live 81 * wallpaper. 82 */ 83 public static final String ACTION_LIVE_WALLPAPER_CHOOSER 84 = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; 85 86 /** 87 * Directly launch live wallpaper preview, allowing the user to immediately 88 * confirm to switch to a specific live wallpaper. You must specify 89 * {@link #EXTRA_LIVE_WALLPAPER_COMPONENT} with the ComponentName of 90 * a live wallpaper component that is to be shown. 91 */ 92 public static final String ACTION_CHANGE_LIVE_WALLPAPER 93 = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER"; 94 95 /** 96 * Extra in {@link #ACTION_CHANGE_LIVE_WALLPAPER} that specifies the 97 * ComponentName of a live wallpaper that should be shown as a preview, 98 * for the user to confirm. 99 */ 100 public static final String EXTRA_LIVE_WALLPAPER_COMPONENT 101 = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT"; 102 103 /** 104 * Manifest entry for activities that respond to {@link Intent#ACTION_SET_WALLPAPER} 105 * which allows them to provide a custom large icon associated with this action. 106 */ 107 public static final String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview"; 108 109 /** 110 * Command for {@link #sendWallpaperCommand}: reported by the wallpaper 111 * host when the user taps on an empty area (not performing an action 112 * in the host). The x and y arguments are the location of the tap in 113 * screen coordinates. 114 */ 115 public static final String COMMAND_TAP = "android.wallpaper.tap"; 116 117 /** 118 * Command for {@link #sendWallpaperCommand}: reported by the wallpaper 119 * host when the user releases a secondary pointer on an empty area 120 * (not performing an action in the host). The x and y arguments are 121 * the location of the secondary tap in screen coordinates. 122 */ 123 public static final String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap"; 124 125 /** 126 * Command for {@link #sendWallpaperCommand}: reported by the wallpaper 127 * host when the user drops an object into an area of the host. The x 128 * and y arguments are the location of the drop. 129 */ 130 public static final String COMMAND_DROP = "android.home.drop"; 131 132 private final Context mContext; 133 134 /** 135 * Special drawable that draws a wallpaper as fast as possible. Assumes 136 * no scaling or placement off (0,0) of the wallpaper (this should be done 137 * at the time the bitmap is loaded). 138 */ 139 static class FastBitmapDrawable extends Drawable { 140 private final Bitmap mBitmap; 141 private final int mWidth; 142 private final int mHeight; 143 private int mDrawLeft; 144 private int mDrawTop; 145 private final Paint mPaint; 146 147 private FastBitmapDrawable(Bitmap bitmap) { 148 mBitmap = bitmap; 149 mWidth = bitmap.getWidth(); 150 mHeight = bitmap.getHeight(); 151 152 setBounds(0, 0, mWidth, mHeight); 153 154 mPaint = new Paint(); 155 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 156 } 157 158 @Override 159 public void draw(Canvas canvas) { 160 canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, mPaint); 161 } 162 163 @Override 164 public int getOpacity() { 165 return PixelFormat.OPAQUE; 166 } 167 168 @Override 169 public void setBounds(int left, int top, int right, int bottom) { 170 mDrawLeft = left + (right-left - mWidth) / 2; 171 mDrawTop = top + (bottom-top - mHeight) / 2; 172 } 173 174 @Override 175 public void setAlpha(int alpha) { 176 throw new UnsupportedOperationException("Not supported with this drawable"); 177 } 178 179 @Override 180 public void setColorFilter(ColorFilter cf) { 181 throw new UnsupportedOperationException("Not supported with this drawable"); 182 } 183 184 @Override 185 public void setDither(boolean dither) { 186 throw new UnsupportedOperationException("Not supported with this drawable"); 187 } 188 189 @Override 190 public void setFilterBitmap(boolean filter) { 191 throw new UnsupportedOperationException("Not supported with this drawable"); 192 } 193 194 @Override 195 public int getIntrinsicWidth() { 196 return mWidth; 197 } 198 199 @Override 200 public int getIntrinsicHeight() { 201 return mHeight; 202 } 203 204 @Override 205 public int getMinimumWidth() { 206 return mWidth; 207 } 208 209 @Override 210 public int getMinimumHeight() { 211 return mHeight; 212 } 213 } 214 215 static class Globals extends IWallpaperManagerCallback.Stub { 216 private IWallpaperManager mService; 217 private Bitmap mWallpaper; 218 private Bitmap mDefaultWallpaper; 219 220 private static final int MSG_CLEAR_WALLPAPER = 1; 221 222 private final Handler mHandler; 223 224 Globals(Looper looper) { 225 IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); 226 mService = IWallpaperManager.Stub.asInterface(b); 227 mHandler = new Handler(looper) { 228 @Override 229 public void handleMessage(Message msg) { 230 switch (msg.what) { 231 case MSG_CLEAR_WALLPAPER: 232 synchronized (this) { 233 mWallpaper = null; 234 mDefaultWallpaper = null; 235 } 236 break; 237 } 238 } 239 }; 240 } 241 242 public void onWallpaperChanged() { 243 /* The wallpaper has changed but we shouldn't eagerly load the 244 * wallpaper as that would be inefficient. Reset the cached wallpaper 245 * to null so if the user requests the wallpaper again then we'll 246 * fetch it. 247 */ 248 mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); 249 } 250 251 public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { 252 synchronized (this) { 253 if (mWallpaper != null) { 254 return mWallpaper; 255 } 256 if (mDefaultWallpaper != null) { 257 return mDefaultWallpaper; 258 } 259 mWallpaper = null; 260 try { 261 mWallpaper = getCurrentWallpaperLocked(context); 262 } catch (OutOfMemoryError e) { 263 Log.w(TAG, "No memory load current wallpaper", e); 264 } 265 if (returnDefault) { 266 if (mWallpaper == null) { 267 mDefaultWallpaper = getDefaultWallpaperLocked(context); 268 return mDefaultWallpaper; 269 } else { 270 mDefaultWallpaper = null; 271 } 272 } 273 return mWallpaper; 274 } 275 } 276 277 public void forgetLoadedWallpaper() { 278 synchronized (this) { 279 mWallpaper = null; 280 mDefaultWallpaper = null; 281 } 282 } 283 284 private Bitmap getCurrentWallpaperLocked(Context context) { 285 try { 286 Bundle params = new Bundle(); 287 ParcelFileDescriptor fd = mService.getWallpaper(this, params); 288 if (fd != null) { 289 int width = params.getInt("width", 0); 290 int height = params.getInt("height", 0); 291 292 try { 293 BitmapFactory.Options options = new BitmapFactory.Options(); 294 Bitmap bm = BitmapFactory.decodeFileDescriptor( 295 fd.getFileDescriptor(), null, options); 296 return generateBitmap(context, bm, width, height); 297 } catch (OutOfMemoryError e) { 298 Log.w(TAG, "Can't decode file", e); 299 } finally { 300 try { 301 fd.close(); 302 } catch (IOException e) { 303 // Ignore 304 } 305 } 306 } 307 } catch (RemoteException e) { 308 // Ignore 309 } 310 return null; 311 } 312 313 private Bitmap getDefaultWallpaperLocked(Context context) { 314 try { 315 InputStream is = context.getResources().openRawResource( 316 com.android.internal.R.drawable.default_wallpaper); 317 if (is != null) { 318 int width = mService.getWidthHint(); 319 int height = mService.getHeightHint(); 320 321 try { 322 BitmapFactory.Options options = new BitmapFactory.Options(); 323 Bitmap bm = BitmapFactory.decodeStream(is, null, options); 324 return generateBitmap(context, bm, width, height); 325 } catch (OutOfMemoryError e) { 326 Log.w(TAG, "Can't decode stream", e); 327 } finally { 328 try { 329 is.close(); 330 } catch (IOException e) { 331 // Ignore 332 } 333 } 334 } 335 } catch (RemoteException e) { 336 // Ignore 337 } 338 return null; 339 } 340 } 341 342 private static final Object sSync = new Object[0]; 343 private static Globals sGlobals; 344 345 static void initGlobals(Looper looper) { 346 synchronized (sSync) { 347 if (sGlobals == null) { 348 sGlobals = new Globals(looper); 349 } 350 } 351 } 352 353 /*package*/ WallpaperManager(Context context, Handler handler) { 354 mContext = context; 355 initGlobals(context.getMainLooper()); 356 } 357 358 /** 359 * Retrieve a WallpaperManager associated with the given Context. 360 */ 361 public static WallpaperManager getInstance(Context context) { 362 return (WallpaperManager)context.getSystemService( 363 Context.WALLPAPER_SERVICE); 364 } 365 366 /** @hide */ 367 public IWallpaperManager getIWallpaperManager() { 368 return sGlobals.mService; 369 } 370 371 /** 372 * Retrieve the current system wallpaper; if 373 * no wallpaper is set, the system built-in static wallpaper is returned. 374 * This is returned as an 375 * abstract Drawable that you can install in a View to display whatever 376 * wallpaper the user has currently set. 377 * 378 * @return Returns a Drawable object that will draw the wallpaper. 379 */ 380 public Drawable getDrawable() { 381 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); 382 if (bm != null) { 383 Drawable dr = new BitmapDrawable(mContext.getResources(), bm); 384 dr.setDither(false); 385 return dr; 386 } 387 return null; 388 } 389 390 /** 391 * Returns a drawable for the system built-in static wallpaper . 392 * 393 */ 394 public Drawable getBuiltInDrawable() { 395 return getBuiltInDrawable(0, 0, false, 0, 0); 396 } 397 398 /** 399 * Returns a drawable for the system built-in static wallpaper. Based on the parameters, the 400 * drawable can be cropped and scaled 401 * 402 * @param outWidth The width of the returned drawable 403 * @param outWidth The height of the returned drawable 404 * @param scaleToFit If true, scale the wallpaper down rather than just cropping it 405 * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image; 406 * 0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned 407 * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image; 408 * 0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned 409 * 410 */ 411 public Drawable getBuiltInDrawable(int outWidth, int outHeight, 412 boolean scaleToFit, float horizontalAlignment, float verticalAlignment) { 413 if (sGlobals.mService == null) { 414 Log.w(TAG, "WallpaperService not running"); 415 return null; 416 } 417 Resources resources = mContext.getResources(); 418 horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment)); 419 verticalAlignment = Math.max(0, Math.min(1, verticalAlignment)); 420 421 InputStream is = new BufferedInputStream( 422 resources.openRawResource(com.android.internal.R.drawable.default_wallpaper)); 423 424 if (is == null) { 425 Log.e(TAG, "default wallpaper input stream is null"); 426 return null; 427 } else { 428 if (outWidth <= 0 || outHeight <= 0) { 429 Bitmap fullSize = BitmapFactory.decodeStream(is, null, null); 430 return new BitmapDrawable(resources, fullSize); 431 } else { 432 int inWidth; 433 int inHeight; 434 { 435 BitmapFactory.Options options = new BitmapFactory.Options(); 436 options.inJustDecodeBounds = true; 437 BitmapFactory.decodeStream(is, null, options); 438 if (options.outWidth != 0 && options.outHeight != 0) { 439 inWidth = options.outWidth; 440 inHeight = options.outHeight; 441 } else { 442 Log.e(TAG, "default wallpaper dimensions are 0"); 443 return null; 444 } 445 } 446 447 is = new BufferedInputStream(resources.openRawResource( 448 com.android.internal.R.drawable.default_wallpaper)); 449 450 RectF cropRectF; 451 452 outWidth = Math.min(inWidth, outWidth); 453 outHeight = Math.min(inHeight, outHeight); 454 if (scaleToFit) { 455 cropRectF = getMaxCropRect(inWidth, inHeight, outWidth, outHeight, 456 horizontalAlignment, verticalAlignment); 457 } else { 458 float left = (inWidth - outWidth) * horizontalAlignment; 459 float right = left + outWidth; 460 float top = (inHeight - outHeight) * verticalAlignment; 461 float bottom = top + outHeight; 462 cropRectF = new RectF(bottom, left, right, top); 463 } 464 Rect roundedTrueCrop = new Rect(); 465 cropRectF.roundOut(roundedTrueCrop); 466 467 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { 468 Log.w(TAG, "crop has bad values for full size image"); 469 return null; 470 } 471 472 // See how much we're reducing the size of the image 473 int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / outWidth, 474 roundedTrueCrop.height() / outHeight); 475 476 // Attempt to open a region decoder 477 BitmapRegionDecoder decoder = null; 478 try { 479 decoder = BitmapRegionDecoder.newInstance(is, true); 480 } catch (IOException e) { 481 Log.w(TAG, "cannot open region decoder for default wallpaper"); 482 } 483 484 Bitmap crop = null; 485 if (decoder != null) { 486 // Do region decoding to get crop bitmap 487 BitmapFactory.Options options = new BitmapFactory.Options(); 488 if (scaleDownSampleSize > 1) { 489 options.inSampleSize = scaleDownSampleSize; 490 } 491 crop = decoder.decodeRegion(roundedTrueCrop, options); 492 decoder.recycle(); 493 } 494 495 if (crop == null) { 496 // BitmapRegionDecoder has failed, try to crop in-memory 497 is = new BufferedInputStream(resources.openRawResource( 498 com.android.internal.R.drawable.default_wallpaper)); 499 Bitmap fullSize = null; 500 if (is != null) { 501 BitmapFactory.Options options = new BitmapFactory.Options(); 502 if (scaleDownSampleSize > 1) { 503 options.inSampleSize = scaleDownSampleSize; 504 } 505 fullSize = BitmapFactory.decodeStream(is, null, options); 506 } 507 if (fullSize != null) { 508 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, 509 roundedTrueCrop.top, roundedTrueCrop.width(), 510 roundedTrueCrop.height()); 511 } 512 } 513 514 if (crop == null) { 515 Log.w(TAG, "cannot decode default wallpaper"); 516 return null; 517 } 518 519 // Scale down if necessary 520 if (outWidth > 0 && outHeight > 0 && 521 (crop.getWidth() != outWidth || crop.getHeight() != outHeight)) { 522 Matrix m = new Matrix(); 523 RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight()); 524 RectF returnRect = new RectF(0, 0, outWidth, outHeight); 525 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 526 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), 527 (int) returnRect.height(), Bitmap.Config.ARGB_8888); 528 if (tmp != null) { 529 Canvas c = new Canvas(tmp); 530 Paint p = new Paint(); 531 p.setFilterBitmap(true); 532 c.drawBitmap(crop, m, p); 533 crop = tmp; 534 } 535 } 536 537 return new BitmapDrawable(resources, crop); 538 } 539 } 540 } 541 542 private static RectF getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight, 543 float horizontalAlignment, float verticalAlignment) { 544 RectF cropRect = new RectF(); 545 // Get a crop rect that will fit this 546 if (inWidth / (float) inHeight > outWidth / (float) outHeight) { 547 cropRect.top = 0; 548 cropRect.bottom = inHeight; 549 float cropWidth = outWidth * (inHeight / (float) outHeight); 550 cropRect.left = (inWidth - cropWidth) * horizontalAlignment; 551 cropRect.right = cropRect.left + cropWidth; 552 } else { 553 cropRect.left = 0; 554 cropRect.right = inWidth; 555 float cropHeight = outHeight * (inWidth / (float) outWidth); 556 cropRect.top = (inHeight - cropHeight) * verticalAlignment; 557 cropRect.bottom = cropRect.top + cropHeight; 558 } 559 return cropRect; 560 } 561 562 /** 563 * Retrieve the current system wallpaper; if there is no wallpaper set, 564 * a null pointer is returned. This is returned as an 565 * abstract Drawable that you can install in a View to display whatever 566 * wallpaper the user has currently set. 567 * 568 * @return Returns a Drawable object that will draw the wallpaper or a 569 * null pointer if these is none. 570 */ 571 public Drawable peekDrawable() { 572 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); 573 if (bm != null) { 574 Drawable dr = new BitmapDrawable(mContext.getResources(), bm); 575 dr.setDither(false); 576 return dr; 577 } 578 return null; 579 } 580 581 /** 582 * Like {@link #getDrawable()}, but the returned Drawable has a number 583 * of limitations to reduce its overhead as much as possible. It will 584 * never scale the wallpaper (only centering it if the requested bounds 585 * do match the bitmap bounds, which should not be typical), doesn't 586 * allow setting an alpha, color filter, or other attributes, etc. The 587 * bounds of the returned drawable will be initialized to the same bounds 588 * as the wallpaper, so normally you will not need to touch it. The 589 * drawable also assumes that it will be used in a context running in 590 * the same density as the screen (not in density compatibility mode). 591 * 592 * @return Returns a Drawable object that will draw the wallpaper. 593 */ 594 public Drawable getFastDrawable() { 595 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); 596 if (bm != null) { 597 return new FastBitmapDrawable(bm); 598 } 599 return null; 600 } 601 602 /** 603 * Like {@link #getFastDrawable()}, but if there is no wallpaper set, 604 * a null pointer is returned. 605 * 606 * @return Returns an optimized Drawable object that will draw the 607 * wallpaper or a null pointer if these is none. 608 */ 609 public Drawable peekFastDrawable() { 610 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); 611 if (bm != null) { 612 return new FastBitmapDrawable(bm); 613 } 614 return null; 615 } 616 617 /** 618 * Like {@link #getDrawable()} but returns a Bitmap. 619 * 620 * @hide 621 */ 622 public Bitmap getBitmap() { 623 return sGlobals.peekWallpaperBitmap(mContext, true); 624 } 625 626 /** 627 * Remove all internal references to the last loaded wallpaper. Useful 628 * for apps that want to reduce memory usage when they only temporarily 629 * need to have the wallpaper. After calling, the next request for the 630 * wallpaper will require reloading it again from disk. 631 */ 632 public void forgetLoadedWallpaper() { 633 sGlobals.forgetLoadedWallpaper(); 634 } 635 636 /** 637 * If the current wallpaper is a live wallpaper component, return the 638 * information about that wallpaper. Otherwise, if it is a static image, 639 * simply return null. 640 */ 641 public WallpaperInfo getWallpaperInfo() { 642 try { 643 if (sGlobals.mService == null) { 644 Log.w(TAG, "WallpaperService not running"); 645 return null; 646 } else { 647 return sGlobals.mService.getWallpaperInfo(); 648 } 649 } catch (RemoteException e) { 650 return null; 651 } 652 } 653 654 /** 655 * Gets an Intent that will launch an activity that crops the given 656 * image and sets the device's wallpaper. If there is a default HOME activity 657 * that supports cropping wallpapers, it will be preferred as the default. 658 * Use this method instead of directly creating a {@link #ACTION_CROP_AND_SET_WALLPAPER} 659 * intent. 660 */ 661 public Intent getCropAndSetWallpaperIntent(Uri imageUri) { 662 final PackageManager packageManager = mContext.getPackageManager(); 663 Intent cropAndSetWallpaperIntent = 664 new Intent(ACTION_CROP_AND_SET_WALLPAPER, imageUri); 665 cropAndSetWallpaperIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 666 667 // Find out if the default HOME activity supports CROP_AND_SET_WALLPAPER 668 Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME); 669 ResolveInfo resolvedHome = packageManager.resolveActivity(homeIntent, 670 PackageManager.MATCH_DEFAULT_ONLY); 671 if (resolvedHome != null) { 672 cropAndSetWallpaperIntent.setPackage(resolvedHome.activityInfo.packageName); 673 674 List<ResolveInfo> cropAppList = packageManager.queryIntentActivities( 675 cropAndSetWallpaperIntent, 0); 676 if (cropAppList.size() > 0) { 677 return cropAndSetWallpaperIntent; 678 } 679 } 680 681 // fallback crop activity 682 cropAndSetWallpaperIntent.setPackage("com.android.wallpapercropper"); 683 return cropAndSetWallpaperIntent; 684 } 685 686 /** 687 * Change the current system wallpaper to the bitmap in the given resource. 688 * The resource is opened as a raw data stream and copied into the 689 * wallpaper; it must be a valid PNG or JPEG image. On success, the intent 690 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. 691 * 692 * <p>This method requires the caller to hold the permission 693 * {@link android.Manifest.permission#SET_WALLPAPER}. 694 * 695 * @param resid The bitmap to save. 696 * 697 * @throws IOException If an error occurs reverting to the built-in 698 * wallpaper. 699 */ 700 public void setResource(int resid) throws IOException { 701 if (sGlobals.mService == null) { 702 Log.w(TAG, "WallpaperService not running"); 703 return; 704 } 705 try { 706 Resources resources = mContext.getResources(); 707 /* Set the wallpaper to the default values */ 708 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( 709 "res:" + resources.getResourceName(resid)); 710 if (fd != null) { 711 FileOutputStream fos = null; 712 try { 713 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 714 setWallpaper(resources.openRawResource(resid), fos); 715 } finally { 716 if (fos != null) { 717 fos.close(); 718 } 719 } 720 } 721 } catch (RemoteException e) { 722 // Ignore 723 } 724 } 725 726 /** 727 * Change the current system wallpaper to a bitmap. The given bitmap is 728 * converted to a PNG and stored as the wallpaper. On success, the intent 729 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. 730 * 731 * <p>This method requires the caller to hold the permission 732 * {@link android.Manifest.permission#SET_WALLPAPER}. 733 * 734 * @param bitmap The bitmap to save. 735 * 736 * @throws IOException If an error occurs reverting to the built-in 737 * wallpaper. 738 */ 739 public void setBitmap(Bitmap bitmap) throws IOException { 740 if (sGlobals.mService == null) { 741 Log.w(TAG, "WallpaperService not running"); 742 return; 743 } 744 try { 745 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); 746 if (fd == null) { 747 return; 748 } 749 FileOutputStream fos = null; 750 try { 751 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 752 bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); 753 } finally { 754 if (fos != null) { 755 fos.close(); 756 } 757 } 758 } catch (RemoteException e) { 759 // Ignore 760 } 761 } 762 763 /** 764 * Change the current system wallpaper to a specific byte stream. The 765 * give InputStream is copied into persistent storage and will now be 766 * used as the wallpaper. Currently it must be either a JPEG or PNG 767 * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} 768 * is broadcast. 769 * 770 * <p>This method requires the caller to hold the permission 771 * {@link android.Manifest.permission#SET_WALLPAPER}. 772 * 773 * @param data A stream containing the raw data to install as a wallpaper. 774 * 775 * @throws IOException If an error occurs reverting to the built-in 776 * wallpaper. 777 */ 778 public void setStream(InputStream data) throws IOException { 779 if (sGlobals.mService == null) { 780 Log.w(TAG, "WallpaperService not running"); 781 return; 782 } 783 try { 784 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); 785 if (fd == null) { 786 return; 787 } 788 FileOutputStream fos = null; 789 try { 790 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 791 setWallpaper(data, fos); 792 } finally { 793 if (fos != null) { 794 fos.close(); 795 } 796 } 797 } catch (RemoteException e) { 798 // Ignore 799 } 800 } 801 802 private void setWallpaper(InputStream data, FileOutputStream fos) 803 throws IOException { 804 byte[] buffer = new byte[32768]; 805 int amt; 806 while ((amt=data.read(buffer)) > 0) { 807 fos.write(buffer, 0, amt); 808 } 809 } 810 811 /** 812 * Return whether any users are currently set to use the wallpaper 813 * with the given resource ID. That is, their wallpaper has been 814 * set through {@link #setResource(int)} with the same resource id. 815 */ 816 public boolean hasResourceWallpaper(int resid) { 817 if (sGlobals.mService == null) { 818 Log.w(TAG, "WallpaperService not running"); 819 return false; 820 } 821 try { 822 Resources resources = mContext.getResources(); 823 String name = "res:" + resources.getResourceName(resid); 824 return sGlobals.mService.hasNamedWallpaper(name); 825 } catch (RemoteException e) { 826 return false; 827 } 828 } 829 830 /** 831 * Returns the desired minimum width for the wallpaper. Callers of 832 * {@link #setBitmap(android.graphics.Bitmap)} or 833 * {@link #setStream(java.io.InputStream)} should check this value 834 * beforehand to make sure the supplied wallpaper respects the desired 835 * minimum width. 836 * 837 * If the returned value is <= 0, the caller should use the width of 838 * the default display instead. 839 * 840 * @return The desired minimum width for the wallpaper. This value should 841 * be honored by applications that set the wallpaper but it is not 842 * mandatory. 843 */ 844 public int getDesiredMinimumWidth() { 845 if (sGlobals.mService == null) { 846 Log.w(TAG, "WallpaperService not running"); 847 return 0; 848 } 849 try { 850 return sGlobals.mService.getWidthHint(); 851 } catch (RemoteException e) { 852 // Shouldn't happen! 853 return 0; 854 } 855 } 856 857 /** 858 * Returns the desired minimum height for the wallpaper. Callers of 859 * {@link #setBitmap(android.graphics.Bitmap)} or 860 * {@link #setStream(java.io.InputStream)} should check this value 861 * beforehand to make sure the supplied wallpaper respects the desired 862 * minimum height. 863 * 864 * If the returned value is <= 0, the caller should use the height of 865 * the default display instead. 866 * 867 * @return The desired minimum height for the wallpaper. This value should 868 * be honored by applications that set the wallpaper but it is not 869 * mandatory. 870 */ 871 public int getDesiredMinimumHeight() { 872 if (sGlobals.mService == null) { 873 Log.w(TAG, "WallpaperService not running"); 874 return 0; 875 } 876 try { 877 return sGlobals.mService.getHeightHint(); 878 } catch (RemoteException e) { 879 // Shouldn't happen! 880 return 0; 881 } 882 } 883 884 /** 885 * For use only by the current home application, to specify the size of 886 * wallpaper it would like to use. This allows such applications to have 887 * a virtual wallpaper that is larger than the physical screen, matching 888 * the size of their workspace. 889 * 890 * <p>Note developers, who don't seem to be reading this. This is 891 * for <em>home screens</em> to tell what size wallpaper they would like. 892 * Nobody else should be calling this! Certainly not other non-home-screen 893 * apps that change the wallpaper. Those apps are supposed to 894 * <b>retrieve</b> the suggested size so they can construct a wallpaper 895 * that matches it. 896 * 897 * <p>This method requires the caller to hold the permission 898 * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}. 899 * 900 * @param minimumWidth Desired minimum width 901 * @param minimumHeight Desired minimum height 902 */ 903 public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { 904 try { 905 if (sGlobals.mService == null) { 906 Log.w(TAG, "WallpaperService not running"); 907 } else { 908 sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); 909 } 910 } catch (RemoteException e) { 911 // Ignore 912 } 913 } 914 915 /** 916 * Set the position of the current wallpaper within any larger space, when 917 * that wallpaper is visible behind the given window. The X and Y offsets 918 * are floating point numbers ranging from 0 to 1, representing where the 919 * wallpaper should be positioned within the screen space. These only 920 * make sense when the wallpaper is larger than the screen. 921 * 922 * @param windowToken The window who these offsets should be associated 923 * with, as returned by {@link android.view.View#getWindowToken() 924 * View.getWindowToken()}. 925 * @param xOffset The offset along the X dimension, from 0 to 1. 926 * @param yOffset The offset along the Y dimension, from 0 to 1. 927 */ 928 public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { 929 try { 930 //Log.v(TAG, "Sending new wallpaper offsets from app..."); 931 WindowManagerGlobal.getWindowSession().setWallpaperPosition( 932 windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); 933 //Log.v(TAG, "...app returning after sending offsets!"); 934 } catch (RemoteException e) { 935 // Ignore. 936 } 937 } 938 939 /** 940 * For applications that use multiple virtual screens showing a wallpaper, 941 * specify the step size between virtual screens. For example, if the 942 * launcher has 3 virtual screens, it would specify an xStep of 0.5, 943 * since the X offset for those screens are 0.0, 0.5 and 1.0 944 * @param xStep The X offset delta from one screen to the next one 945 * @param yStep The Y offset delta from one screen to the next one 946 */ 947 public void setWallpaperOffsetSteps(float xStep, float yStep) { 948 mWallpaperXStep = xStep; 949 mWallpaperYStep = yStep; 950 } 951 952 /** 953 * Send an arbitrary command to the current active wallpaper. 954 * 955 * @param windowToken The window who these offsets should be associated 956 * with, as returned by {@link android.view.View#getWindowToken() 957 * View.getWindowToken()}. 958 * @param action Name of the command to perform. This must be a scoped 959 * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT". 960 * @param x Arbitrary integer argument based on command. 961 * @param y Arbitrary integer argument based on command. 962 * @param z Arbitrary integer argument based on command. 963 * @param extras Optional additional information for the command, or null. 964 */ 965 public void sendWallpaperCommand(IBinder windowToken, String action, 966 int x, int y, int z, Bundle extras) { 967 try { 968 //Log.v(TAG, "Sending new wallpaper offsets from app..."); 969 WindowManagerGlobal.getWindowSession().sendWallpaperCommand( 970 windowToken, action, x, y, z, extras, false); 971 //Log.v(TAG, "...app returning after sending offsets!"); 972 } catch (RemoteException e) { 973 // Ignore. 974 } 975 } 976 977 /** 978 * Clear the offsets previously associated with this window through 979 * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts 980 * the window to its default state, where it does not cause the wallpaper 981 * to scroll from whatever its last offsets were. 982 * 983 * @param windowToken The window who these offsets should be associated 984 * with, as returned by {@link android.view.View#getWindowToken() 985 * View.getWindowToken()}. 986 */ 987 public void clearWallpaperOffsets(IBinder windowToken) { 988 try { 989 WindowManagerGlobal.getWindowSession().setWallpaperPosition( 990 windowToken, -1, -1, -1, -1); 991 } catch (RemoteException e) { 992 // Ignore. 993 } 994 } 995 996 /** 997 * Remove any currently set wallpaper, reverting to the system's built-in 998 * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} 999 * is broadcast. 1000 * 1001 * <p>This method requires the caller to hold the permission 1002 * {@link android.Manifest.permission#SET_WALLPAPER}. 1003 * 1004 * @throws IOException If an error occurs reverting to the built-in 1005 * wallpaper. 1006 */ 1007 public void clear() throws IOException { 1008 setResource(com.android.internal.R.drawable.default_wallpaper); 1009 } 1010 1011 static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { 1012 if (bm == null) { 1013 return null; 1014 } 1015 1016 WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 1017 DisplayMetrics metrics = new DisplayMetrics(); 1018 wm.getDefaultDisplay().getMetrics(metrics); 1019 bm.setDensity(metrics.noncompatDensityDpi); 1020 1021 if (width <= 0 || height <= 0 1022 || (bm.getWidth() == width && bm.getHeight() == height)) { 1023 return bm; 1024 } 1025 1026 // This is the final bitmap we want to return. 1027 try { 1028 Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 1029 newbm.setDensity(metrics.noncompatDensityDpi); 1030 1031 Canvas c = new Canvas(newbm); 1032 Rect targetRect = new Rect(); 1033 targetRect.right = bm.getWidth(); 1034 targetRect.bottom = bm.getHeight(); 1035 1036 int deltaw = width - targetRect.right; 1037 int deltah = height - targetRect.bottom; 1038 1039 if (deltaw > 0 || deltah > 0) { 1040 // We need to scale up so it covers the entire area. 1041 float scale; 1042 if (deltaw > deltah) { 1043 scale = width / (float)targetRect.right; 1044 } else { 1045 scale = height / (float)targetRect.bottom; 1046 } 1047 targetRect.right = (int)(targetRect.right*scale); 1048 targetRect.bottom = (int)(targetRect.bottom*scale); 1049 deltaw = width - targetRect.right; 1050 deltah = height - targetRect.bottom; 1051 } 1052 1053 targetRect.offset(deltaw/2, deltah/2); 1054 1055 Paint paint = new Paint(); 1056 paint.setFilterBitmap(true); 1057 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 1058 c.drawBitmap(bm, null, targetRect, paint); 1059 1060 bm.recycle(); 1061 c.setBitmap(null); 1062 return newbm; 1063 } catch (OutOfMemoryError e) { 1064 Log.w(TAG, "Can't generate default bitmap", e); 1065 return bm; 1066 } 1067 } 1068} 1069