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