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