2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.Canvas;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.PixelFormat;
28 import android.graphics.Rect;
29 import android.graphics.drawable.BitmapDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.Looper;
35 import android.os.Message;
36 import android.os.ParcelFileDescriptor;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.util.DisplayMetrics;
40 import android.util.Log;
41 import android.view.ViewRoot;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
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()}.
53 public class WallpaperManager {
54 private static String TAG = "WallpaperManager";
55 private static boolean DEBUG = false;
56 private float mWallpaperXStep = -1;
57 private float mWallpaperYStep = -1;
60 * Launch an activity for the user to pick the current global live
63 public static final String ACTION_LIVE_WALLPAPER_CHOOSER
64 = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER";
67 * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
68 * host when the user taps on an empty area (not performing an action
69 * in the host). The x and y arguments are the location of the tap in
72 public static final String COMMAND_TAP = "android.wallpaper.tap";
75 * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
76 * host when the user drops an object into an area of the host. The x
77 * and y arguments are the location of the drop.
79 public static final String COMMAND_DROP = "android.home.drop";
81 private final Context mContext;
84 * Special drawable that draws a wallpaper as fast as possible. Assumes
85 * no scaling or placement off (0,0) of the wallpaper (this should be done
86 * at the time the bitmap is loaded).
88 static class FastBitmapDrawable extends Drawable {
89 private final Bitmap mBitmap;
90 private final int mWidth;
91 private final int mHeight;
92 private int mDrawLeft;
95 private FastBitmapDrawable(Bitmap bitmap) {
97 mWidth = bitmap.getWidth();
98 mHeight = bitmap.getHeight();
99 setBounds(0, 0, mWidth, mHeight);
103 public void draw(Canvas canvas) {
104 canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, null);
108 public int getOpacity() {
109 return PixelFormat.OPAQUE;
113 public void setBounds(int left, int top, int right, int bottom) {
114 mDrawLeft = left + (right-left - mWidth) / 2;
115 mDrawTop = top + (bottom-top - mHeight) / 2;
119 public void setBounds(Rect bounds) {
120 // TODO Auto-generated method stub
121 super.setBounds(bounds);
125 public void setAlpha(int alpha) {
126 throw new UnsupportedOperationException(
127 "Not supported with this drawable");
131 public void setColorFilter(ColorFilter cf) {
132 throw new UnsupportedOperationException(
133 "Not supported with this drawable");
137 public void setDither(boolean dither) {
138 throw new UnsupportedOperationException(
139 "Not supported with this drawable");
143 public void setFilterBitmap(boolean filter) {
144 throw new UnsupportedOperationException(
145 "Not supported with this drawable");
149 public int getIntrinsicWidth() {
154 public int getIntrinsicHeight() {
159 public int getMinimumWidth() {
164 public int getMinimumHeight() {
169 static class Globals extends IWallpaperManagerCallback.Stub {
170 private IWallpaperManager mService;
171 private Bitmap mWallpaper;
172 private Bitmap mDefaultWallpaper;
174 private static final int MSG_CLEAR_WALLPAPER = 1;
176 private final Handler mHandler;
178 Globals(Looper looper) {
179 IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
180 mService = IWallpaperManager.Stub.asInterface(b);
181 mHandler = new Handler(looper) {
183 public void handleMessage(Message msg) {
185 case MSG_CLEAR_WALLPAPER:
186 synchronized (this) {
188 mDefaultWallpaper = null;
196 public void onWallpaperChanged() {
197 /* The wallpaper has changed but we shouldn't eagerly load the
198 * wallpaper as that would be inefficient. Reset the cached wallpaper
199 * to null so if the user requests the wallpaper again then we'll
202 mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
205 public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
206 synchronized (this) {
207 if (mWallpaper != null) {
210 if (mDefaultWallpaper != null) {
211 return mDefaultWallpaper;
215 mWallpaper = getCurrentWallpaperLocked(context);
216 } catch (OutOfMemoryError e) {
217 Log.w(TAG, "No memory load current wallpaper", e);
219 if (mWallpaper == null && returnDefault) {
220 mDefaultWallpaper = getDefaultWallpaperLocked(context);
221 return mDefaultWallpaper;
227 private Bitmap getCurrentWallpaperLocked(Context context) {
229 Bundle params = new Bundle();
230 ParcelFileDescriptor fd = mService.getWallpaper(this, params);
232 int width = params.getInt("width", 0);
233 int height = params.getInt("height", 0);
235 if (width <= 0 || height <= 0) {
236 // Degenerate case: no size requested, just load
238 Bitmap bm = BitmapFactory.decodeFileDescriptor(
239 fd.getFileDescriptor(), null, null);
242 } catch (IOException e) {
245 bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
250 // Load the bitmap with full color depth, to preserve
251 // quality for later processing.
252 BitmapFactory.Options options = new BitmapFactory.Options();
253 options.inDither = false;
254 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
255 Bitmap bm = BitmapFactory.decodeFileDescriptor(
256 fd.getFileDescriptor(), null, options);
259 } catch (IOException e) {
262 return generateBitmap(context, bm, width, height);
264 } catch (RemoteException e) {
269 private Bitmap getDefaultWallpaperLocked(Context context) {
271 InputStream is = context.getResources().openRawResource(
272 com.android.internal.R.drawable.default_wallpaper);
274 int width = mService.getWidthHint();
275 int height = mService.getHeightHint();
277 if (width <= 0 || height <= 0) {
278 // Degenerate case: no size requested, just load
280 Bitmap bm = BitmapFactory.decodeStream(is, null, null);
283 } catch (IOException e) {
286 bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
291 // Load the bitmap with full color depth, to preserve
292 // quality for later processing.
293 BitmapFactory.Options options = new BitmapFactory.Options();
294 options.inDither = false;
295 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
296 Bitmap bm = BitmapFactory.decodeStream(is, null, options);
299 } catch (IOException e) {
303 return generateBitmap(context, bm, width, height);
304 } catch (OutOfMemoryError e) {
305 Log.w(TAG, "Can't generate default bitmap", e);
309 } catch (RemoteException e) {
315 private static Object mSync = new Object();
316 private static Globals sGlobals;
318 static void initGlobals(Looper looper) {
319 synchronized (mSync) {
320 if (sGlobals == null) {
321 sGlobals = new Globals(looper);
326 /*package*/ WallpaperManager(Context context, Handler handler) {
328 initGlobals(context.getMainLooper());
332 * Retrieve a WallpaperManager associated with the given Context.
334 public static WallpaperManager getInstance(Context context) {
335 return (WallpaperManager)context.getSystemService(
336 Context.WALLPAPER_SERVICE);
340 public IWallpaperManager getIWallpaperManager() {
341 return sGlobals.mService;
345 * Retrieve the current system wallpaper; if
346 * no wallpaper is set, the system default wallpaper is returned.
347 * This is returned as an
348 * abstract Drawable that you can install in a View to display whatever
349 * wallpaper the user has currently set.
351 * @return Returns a Drawable object that will draw the wallpaper.
353 public Drawable getDrawable() {
354 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
356 Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
364 * Retrieve the current system wallpaper; if there is no wallpaper set,
365 * a null pointer is returned. This is returned as an
366 * abstract Drawable that you can install in a View to display whatever
367 * wallpaper the user has currently set.
369 * @return Returns a Drawable object that will draw the wallpaper or a
370 * null pointer if these is none.
372 public Drawable peekDrawable() {
373 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
375 Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
383 * Like {@link #getDrawable()}, but the returned Drawable has a number
384 * of limitations to reduce its overhead as much as possible. It will
385 * never scale the wallpaper (only centering it if the requested bounds
386 * do match the bitmap bounds, which should not be typical), doesn't
387 * allow setting an alpha, color filter, or other attributes, etc. The
388 * bounds of the returned drawable will be initialized to the same bounds
389 * as the wallpaper, so normally you will not need to touch it. The
390 * drawable also assumes that it will be used in a context running in
391 * the same density as the screen (not in density compatibility mode).
393 * @return Returns a Drawable object that will draw the wallpaper.
395 public Drawable getFastDrawable() {
396 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
398 Drawable dr = new FastBitmapDrawable(bm);
405 * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
406 * a null pointer is returned.
408 * @return Returns an optimized Drawable object that will draw the
409 * wallpaper or a null pointer if these is none.
411 public Drawable peekFastDrawable() {
412 Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
414 Drawable dr = new FastBitmapDrawable(bm);
421 * If the current wallpaper is a live wallpaper component, return the
422 * information about that wallpaper. Otherwise, if it is a static image,
423 * simply return null.
425 public WallpaperInfo getWallpaperInfo() {
427 return sGlobals.mService.getWallpaperInfo();
428 } catch (RemoteException e) {
434 * Change the current system wallpaper to the bitmap in the given resource.
435 * The resource is opened as a raw data stream and copied into the
436 * wallpaper; it must be a valid PNG or JPEG image. On success, the intent
437 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
439 * @param resid The bitmap to save.
441 * @throws IOException If an error occurs reverting to the default
444 public void setResource(int resid) throws IOException {
446 Resources resources = mContext.getResources();
447 /* Set the wallpaper to the default values */
448 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
449 "res:" + resources.getResourceName(resid));
451 FileOutputStream fos = null;
453 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
454 setWallpaper(resources.openRawResource(resid), fos);
461 } catch (RemoteException e) {
466 * Change the current system wallpaper to a bitmap. The given bitmap is
467 * converted to a PNG and stored as the wallpaper. On success, the intent
468 * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
470 * @param bitmap The bitmap to save.
472 * @throws IOException If an error occurs reverting to the default
475 public void setBitmap(Bitmap bitmap) throws IOException {
477 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
481 FileOutputStream fos = null;
483 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
484 bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
490 } catch (RemoteException e) {
495 * Change the current system wallpaper to a specific byte stream. The
496 * give InputStream is copied into persistent storage and will now be
497 * used as the wallpaper. Currently it must be either a JPEG or PNG
498 * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
501 * @param data A stream containing the raw data to install as a wallpaper.
503 * @throws IOException If an error occurs reverting to the default
506 public void setStream(InputStream data) throws IOException {
508 ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
512 FileOutputStream fos = null;
514 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
515 setWallpaper(data, fos);
521 } catch (RemoteException e) {
525 private void setWallpaper(InputStream data, FileOutputStream fos)
527 byte[] buffer = new byte[32768];
529 while ((amt=data.read(buffer)) > 0) {
530 fos.write(buffer, 0, amt);
535 * Returns the desired minimum width for the wallpaper. Callers of
536 * {@link #setBitmap(android.graphics.Bitmap)} or
537 * {@link #setStream(java.io.InputStream)} should check this value
538 * beforehand to make sure the supplied wallpaper respects the desired
541 * If the returned value is <= 0, the caller should use the width of
542 * the default display instead.
544 * @return The desired minimum width for the wallpaper. This value should
545 * be honored by applications that set the wallpaper but it is not
548 public int getDesiredMinimumWidth() {
550 return sGlobals.mService.getWidthHint();
551 } catch (RemoteException e) {
558 * Returns the desired minimum height for the wallpaper. Callers of
559 * {@link #setBitmap(android.graphics.Bitmap)} or
560 * {@link #setStream(java.io.InputStream)} should check this value
561 * beforehand to make sure the supplied wallpaper respects the desired
564 * If the returned value is <= 0, the caller should use the height of
565 * the default display instead.
567 * @return The desired minimum height for the wallpaper. This value should
568 * be honored by applications that set the wallpaper but it is not
571 public int getDesiredMinimumHeight() {
573 return sGlobals.mService.getHeightHint();
574 } catch (RemoteException e) {
581 * For use only by the current home application, to specify the size of
582 * wallpaper it would like to use. This allows such applications to have
583 * a virtual wallpaper that is larger than the physical screen, matching
584 * the size of their workspace.
585 * @param minimumWidth Desired minimum width
586 * @param minimumHeight Desired minimum height
588 public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
590 sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
591 } catch (RemoteException e) {
596 * Set the position of the current wallpaper within any larger space, when
597 * that wallpaper is visible behind the given window. The X and Y offsets
598 * are floating point numbers ranging from 0 to 1, representing where the
599 * wallpaper should be positioned within the screen space. These only
600 * make sense when the wallpaper is larger than the screen.
602 * @param windowToken The window who these offsets should be associated
603 * with, as returned by {@link android.view.View#getWindowToken()
604 * View.getWindowToken()}.
605 * @param xOffset The offset along the X dimension, from 0 to 1.
606 * @param yOffset The offset along the Y dimension, from 0 to 1.
608 public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
610 //Log.v(TAG, "Sending new wallpaper offsets from app...");
611 ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
612 windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
613 //Log.v(TAG, "...app returning after sending offsets!");
614 } catch (RemoteException e) {
620 * For applications that use multiple virtual screens showing a wallpaper,
621 * specify the step size between virtual screens. For example, if the
622 * launcher has 3 virtual screens, it would specify an xStep of 0.5,
623 * since the X offset for those screens are 0.0, 0.5 and 1.0
624 * @param xStep The X offset delta from one screen to the next one
625 * @param yStep The Y offset delta from one screen to the next one
627 public void setWallpaperOffsetSteps(float xStep, float yStep) {
628 mWallpaperXStep = xStep;
629 mWallpaperYStep = yStep;
633 * Send an arbitrary command to the current active wallpaper.
635 * @param windowToken The window who these offsets should be associated
636 * with, as returned by {@link android.view.View#getWindowToken()
637 * View.getWindowToken()}.
638 * @param action Name of the command to perform. This must be a scoped
639 * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT".
640 * @param x Arbitrary integer argument based on command.
641 * @param y Arbitrary integer argument based on command.
642 * @param z Arbitrary integer argument based on command.
643 * @param extras Optional additional information for the command, or null.
645 public void sendWallpaperCommand(IBinder windowToken, String action,
646 int x, int y, int z, Bundle extras) {
648 //Log.v(TAG, "Sending new wallpaper offsets from app...");
649 ViewRoot.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
650 windowToken, action, x, y, z, extras, false);
651 //Log.v(TAG, "...app returning after sending offsets!");
652 } catch (RemoteException e) {
658 * Clear the offsets previously associated with this window through
659 * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts
660 * the window to its default state, where it does not cause the wallpaper
661 * to scroll from whatever its last offsets were.
663 * @param windowToken The window who these offsets should be associated
664 * with, as returned by {@link android.view.View#getWindowToken()
665 * View.getWindowToken()}.
667 public void clearWallpaperOffsets(IBinder windowToken) {
669 ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
670 windowToken, -1, -1, -1, -1);
671 } catch (RemoteException e) {
677 * Remove any currently set wallpaper, reverting to the system's default
678 * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
681 * @throws IOException If an error occurs reverting to the default
684 public void clear() throws IOException {
685 setResource(com.android.internal.R.drawable.default_wallpaper);
688 static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) {
692 bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
694 // This is the final bitmap we want to return.
695 // XXX We should get the pixel depth from the system (to match the
696 // physical display depth), when there is a way.
697 Bitmap newbm = Bitmap.createBitmap(width, height,
698 Bitmap.Config.RGB_565);
699 newbm.setDensity(DisplayMetrics.DENSITY_DEVICE);
700 Canvas c = new Canvas(newbm);
701 c.setDensity(DisplayMetrics.DENSITY_DEVICE);
702 Rect targetRect = new Rect();
703 targetRect.left = targetRect.top = 0;
704 targetRect.right = bm.getWidth();
705 targetRect.bottom = bm.getHeight();
707 int deltaw = width - targetRect.right;
708 int deltah = height - targetRect.bottom;
710 if (deltaw > 0 || deltah > 0) {
711 // We need to scale up so it covers the entire
714 if (deltaw > deltah) {
715 scale = width / (float)targetRect.right;
717 scale = height / (float)targetRect.bottom;
719 targetRect.right = (int)(targetRect.right*scale);
720 targetRect.bottom = (int)(targetRect.bottom*scale);
721 deltaw = width - targetRect.right;
722 deltah = height - targetRect.bottom;
725 targetRect.offset(deltaw/2, deltah/2);
726 Paint paint = new Paint();
727 paint.setFilterBitmap(true);
728 paint.setDither(true);
729 c.drawBitmap(bm, null, targetRect, paint);