WallpaperManager.java revision eb034652c2037a47ebfd99779e8383bb8bb528af
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.Paint; 26import android.graphics.Rect; 27import android.graphics.drawable.BitmapDrawable; 28import android.graphics.drawable.Drawable; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.IBinder; 32import android.os.Looper; 33import android.os.Message; 34import android.os.ParcelFileDescriptor; 35import android.os.RemoteException; 36import android.os.ServiceManager; 37import android.util.DisplayMetrics; 38import android.util.Log; 39import android.view.ViewRoot; 40 41import java.io.FileOutputStream; 42import java.io.IOException; 43import java.io.InputStream; 44 45public class WallpaperManager { 46 private static String TAG = "WallpaperManager"; 47 private static boolean DEBUG = false; 48 49 /** 50 * Launch an activity for the user to pick the current global live 51 * wallpaper. 52 * @hide Live Wallpaper 53 */ 54 public static final String ACTION_LIVE_WALLPAPER_CHOOSER 55 = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; 56 57 private final Context mContext; 58 59 static class Globals extends IWallpaperManagerCallback.Stub { 60 private IWallpaperManager mService; 61 private Bitmap mWallpaper; 62 63 private static final int MSG_CLEAR_WALLPAPER = 1; 64 65 private final Handler mHandler; 66 67 Globals(Looper looper) { 68 IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); 69 mService = IWallpaperManager.Stub.asInterface(b); 70 mHandler = new Handler(looper) { 71 @Override 72 public void handleMessage(Message msg) { 73 switch (msg.what) { 74 case MSG_CLEAR_WALLPAPER: 75 synchronized (this) { 76 mWallpaper = null; 77 } 78 break; 79 } 80 } 81 }; 82 } 83 84 public void onWallpaperChanged() { 85 /* The wallpaper has changed but we shouldn't eagerly load the 86 * wallpaper as that would be inefficient. Reset the cached wallpaper 87 * to null so if the user requests the wallpaper again then we'll 88 * fetch it. 89 */ 90 mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); 91 } 92 93 public Bitmap peekWallpaperBitmap(Context context) { 94 synchronized (this) { 95 if (mWallpaper != null) { 96 return mWallpaper; 97 } 98 mWallpaper = getCurrentWallpaperLocked(context); 99 return mWallpaper; 100 } 101 } 102 103 private Bitmap getCurrentWallpaperLocked(Context context) { 104 try { 105 Bundle params = new Bundle(); 106 ParcelFileDescriptor fd = mService.getWallpaper(this, params); 107 if (fd != null) { 108 int width = params.getInt("width", 0); 109 int height = params.getInt("height", 0); 110 111 if (width <= 0 || height <= 0) { 112 // Degenerate case: no size requested, just load 113 // bitmap as-is. 114 Bitmap bm = BitmapFactory.decodeFileDescriptor( 115 fd.getFileDescriptor(), null, null); 116 try { 117 fd.close(); 118 } catch (IOException e) { 119 } 120 if (bm != null) { 121 bm.setDensity(DisplayMetrics.DENSITY_DEVICE); 122 } 123 return bm; 124 } 125 126 // Load the bitmap with full color depth, to preserve 127 // quality for later processing. 128 BitmapFactory.Options options = new BitmapFactory.Options(); 129 options.inDither = false; 130 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 131 Bitmap bm = BitmapFactory.decodeFileDescriptor( 132 fd.getFileDescriptor(), null, options); 133 try { 134 fd.close(); 135 } catch (IOException e) { 136 } 137 if (bm == null) { 138 return bm; 139 } 140 bm.setDensity(DisplayMetrics.DENSITY_DEVICE); 141 142 // This is the final bitmap we want to return. 143 Bitmap newbm = Bitmap.createBitmap(width, height, 144 bm.getConfig()); 145 newbm.setDensity(DisplayMetrics.DENSITY_DEVICE); 146 Canvas c = new Canvas(newbm); 147 c.setDensity(DisplayMetrics.DENSITY_DEVICE); 148 Rect targetRect = new Rect(); 149 targetRect.left = targetRect.top = 0; 150 targetRect.right = bm.getWidth(); 151 targetRect.bottom = bm.getHeight(); 152 153 int deltaw = width - targetRect.right; 154 int deltah = height - targetRect.bottom; 155 156 if (deltaw > 0 || deltah > 0) { 157 // We need to scale up so it covers the entire 158 // area. 159 float scale = 1.0f; 160 if (deltaw > deltah) { 161 scale = width / (float)targetRect.right; 162 } else { 163 scale = height / (float)targetRect.bottom; 164 } 165 targetRect.right = (int)(targetRect.right*scale); 166 targetRect.bottom = (int)(targetRect.bottom*scale); 167 deltaw = width - targetRect.right; 168 deltah = height - targetRect.bottom; 169 } 170 171 targetRect.offset(deltaw/2, deltah/2); 172 Paint paint = new Paint(); 173 paint.setFilterBitmap(true); 174 paint.setDither(true); 175 c.drawBitmap(bm, null, targetRect, paint); 176 177 bm.recycle(); 178 return newbm; 179 } 180 } catch (RemoteException e) { 181 } 182 return null; 183 } 184 } 185 186 private static Object mSync = new Object(); 187 private static Globals sGlobals; 188 189 static void initGlobals(Looper looper) { 190 synchronized (mSync) { 191 if (sGlobals == null) { 192 sGlobals = new Globals(looper); 193 } 194 } 195 } 196 197 /*package*/ WallpaperManager(Context context, Handler handler) { 198 mContext = context; 199 initGlobals(context.getMainLooper()); 200 } 201 202 /** 203 * Retrieve a WallpaperManager associated with the given Context. 204 */ 205 public static WallpaperManager getInstance(Context context) { 206 return (WallpaperManager)context.getSystemService( 207 Context.WALLPAPER_SERVICE); 208 } 209 210 /** @hide */ 211 public IWallpaperManager getIWallpaperManager() { 212 return sGlobals.mService; 213 } 214 215 /** 216 * Like {@link #peekDrawable}, but always returns a valid Drawable. If 217 * no wallpaper is set, the system default wallpaper is returned. 218 * 219 * @return Returns a Drawable object that will draw the wallpaper. 220 */ 221 public Drawable getDrawable() { 222 Drawable dr = peekDrawable(); 223 return dr != null ? dr : Resources.getSystem().getDrawable( 224 com.android.internal.R.drawable.default_wallpaper); 225 } 226 227 /** 228 * Retrieve the current system wallpaper. This is returned as an 229 * abstract Drawable that you can install in a View to display whatever 230 * wallpaper the user has currently set. If there is no wallpaper set, 231 * a null pointer is returned. 232 * 233 * @return Returns a Drawable object that will draw the wallpaper or a 234 * null pointer if these is none. 235 */ 236 public Drawable peekDrawable() { 237 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext); 238 return bm != null ? new BitmapDrawable(mContext.getResources(), bm) : null; 239 } 240 241 /** 242 * If the current wallpaper is a live wallpaper component, return the 243 * information about that wallpaper. Otherwise, if it is a static image, 244 * simply return null. 245 * @hide Live Wallpaper 246 */ 247 public WallpaperInfo getWallpaperInfo() { 248 try { 249 return sGlobals.mService.getWallpaperInfo(); 250 } catch (RemoteException e) { 251 return null; 252 } 253 } 254 255 /** 256 * Change the current system wallpaper to the bitmap in the given resource. 257 * The resource is opened as a raw data stream and copied into the 258 * wallpaper; it must be a valid PNG or JPEG image. On success, the intent 259 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. 260 * 261 * @param resid The bitmap to save. 262 * 263 * @throws IOException If an error occurs reverting to the default 264 * wallpaper. 265 */ 266 public void setResource(int resid) throws IOException { 267 try { 268 Resources resources = mContext.getResources(); 269 /* Set the wallpaper to the default values */ 270 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( 271 "res:" + resources.getResourceName(resid)); 272 if (fd != null) { 273 FileOutputStream fos = null; 274 try { 275 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 276 setWallpaper(resources.openRawResource(resid), fos); 277 } finally { 278 if (fos != null) { 279 fos.close(); 280 } 281 } 282 } 283 } catch (RemoteException e) { 284 } 285 } 286 287 /** 288 * Change the current system wallpaper to a bitmap. The given bitmap is 289 * converted to a PNG and stored as the wallpaper. On success, the intent 290 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. 291 * 292 * @param bitmap The bitmap to save. 293 * 294 * @throws IOException If an error occurs reverting to the default 295 * wallpaper. 296 */ 297 public void setBitmap(Bitmap bitmap) throws IOException { 298 try { 299 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); 300 if (fd == null) { 301 return; 302 } 303 FileOutputStream fos = null; 304 try { 305 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 306 bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); 307 } finally { 308 if (fos != null) { 309 fos.close(); 310 } 311 } 312 } catch (RemoteException e) { 313 } 314 } 315 316 /** 317 * Change the current system wallpaper to a specific byte stream. The 318 * give InputStream is copied into persistent storage and will now be 319 * used as the wallpaper. Currently it must be either a JPEG or PNG 320 * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} 321 * is broadcast. 322 * 323 * @param data A stream containing the raw data to install as a wallpaper. 324 * 325 * @throws IOException If an error occurs reverting to the default 326 * wallpaper. 327 */ 328 public void setStream(InputStream data) throws IOException { 329 try { 330 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); 331 if (fd == null) { 332 return; 333 } 334 FileOutputStream fos = null; 335 try { 336 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); 337 setWallpaper(data, fos); 338 } finally { 339 if (fos != null) { 340 fos.close(); 341 } 342 } 343 } catch (RemoteException e) { 344 } 345 } 346 347 private void setWallpaper(InputStream data, FileOutputStream fos) 348 throws IOException { 349 byte[] buffer = new byte[32768]; 350 int amt; 351 while ((amt=data.read(buffer)) > 0) { 352 fos.write(buffer, 0, amt); 353 } 354 } 355 356 /** 357 * Returns the desired minimum width for the wallpaper. Callers of 358 * {@link #setBitmap(android.graphics.Bitmap)} or 359 * {@link #setStream(java.io.InputStream)} should check this value 360 * beforehand to make sure the supplied wallpaper respects the desired 361 * minimum width. 362 * 363 * If the returned value is <= 0, the caller should use the width of 364 * the default display instead. 365 * 366 * @return The desired minimum width for the wallpaper. This value should 367 * be honored by applications that set the wallpaper but it is not 368 * mandatory. 369 */ 370 public int getDesiredMinimumWidth() { 371 try { 372 return sGlobals.mService.getWidthHint(); 373 } catch (RemoteException e) { 374 // Shouldn't happen! 375 return 0; 376 } 377 } 378 379 /** 380 * Returns the desired minimum height for the wallpaper. Callers of 381 * {@link #setBitmap(android.graphics.Bitmap)} or 382 * {@link #setStream(java.io.InputStream)} should check this value 383 * beforehand to make sure the supplied wallpaper respects the desired 384 * minimum height. 385 * 386 * If the returned value is <= 0, the caller should use the height of 387 * the default display instead. 388 * 389 * @return The desired minimum height for the wallpaper. This value should 390 * be honored by applications that set the wallpaper but it is not 391 * mandatory. 392 */ 393 public int getDesiredMinimumHeight() { 394 try { 395 return sGlobals.mService.getHeightHint(); 396 } catch (RemoteException e) { 397 // Shouldn't happen! 398 return 0; 399 } 400 } 401 402 /** 403 * For use only by the current home application, to specify the size of 404 * wallpaper it would like to use. This allows such applications to have 405 * a virtual wallpaper that is larger than the physical screen, matching 406 * the size of their workspace. 407 * @param minimumWidth Desired minimum width 408 * @param minimumHeight Desired minimum height 409 */ 410 public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { 411 try { 412 sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); 413 } catch (RemoteException e) { 414 } 415 } 416 417 /** 418 * Set the position of the current wallpaper within any larger space, when 419 * that wallpaper is visible behind the given window. The X and Y offsets 420 * are floating point numbers ranging from 0 to 1, representing where the 421 * wallpaper should be positioned within the screen space. These only 422 * make sense when the wallpaper is larger than the screen. 423 * 424 * @param windowToken The window who these offsets should be associated 425 * with, as returned by {@link android.view.View#getWindowVisibility() 426 * View.getWindowToken()}. 427 * @param xOffset The offset olong the X dimension, from 0 to 1. 428 * @param yOffset The offset along the Y dimension, from 0 to 1. 429 */ 430 public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { 431 try { 432 ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( 433 windowToken, xOffset, yOffset); 434 } catch (RemoteException e) { 435 // Ignore. 436 } 437 } 438 439 /** 440 * Clear the offsets previously associated with this window through 441 * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts 442 * the window to its default state, where it does not cause the wallpaper 443 * to scroll from whatever its last offsets were. 444 * 445 * @param windowToken The window who these offsets should be associated 446 * with, as returned by {@link android.view.View#getWindowVisibility() 447 * View.getWindowToken()}. 448 */ 449 public void clearWallpaperOffsets(IBinder windowToken) { 450 try { 451 ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( 452 windowToken, -1, -1); 453 } catch (RemoteException e) { 454 // Ignore. 455 } 456 } 457 458 /** 459 * Remove any currently set wallpaper, reverting to the system's default 460 * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} 461 * is broadcast. 462 * 463 * @throws IOException If an error occurs reverting to the default 464 * wallpaper. 465 */ 466 public void clear() throws IOException { 467 setResource(com.android.internal.R.drawable.default_wallpaper); 468 } 469} 470