WallpaperManager.java revision 19382ac1a4e4e7c23a1346d299368763f149de9c
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
47public class WallpaperManager {
48    private static String TAG = "WallpaperManager";
49    private static boolean DEBUG = false;
50
51    /**
52     * Launch an activity for the user to pick the current global live
53     * wallpaper.
54     * @hide Live Wallpaper
55     */
56    public static final String ACTION_LIVE_WALLPAPER_CHOOSER
57            = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER";
58
59    private final Context mContext;
60
61    /**
62     * Special drawable that draws a wallpaper as fast as possible.  Assumes
63     * no scaling or placement off (0,0) of the wallpaper (this should be done
64     * at the time the bitmap is loaded).
65     */
66    static class FastBitmapDrawable extends Drawable {
67        private final Bitmap mBitmap;
68        private final int mWidth;
69        private final int mHeight;
70        private int mDrawLeft;
71        private int mDrawTop;
72
73        private FastBitmapDrawable(Bitmap bitmap) {
74            mBitmap = bitmap;
75            mWidth = bitmap.getWidth();
76            mHeight = bitmap.getHeight();
77            setBounds(0, 0, mWidth, mHeight);
78        }
79
80        @Override
81        public void draw(Canvas canvas) {
82            canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, null);
83        }
84
85        @Override
86        public int getOpacity() {
87            return PixelFormat.OPAQUE;
88        }
89
90        @Override
91        public void setBounds(int left, int top, int right, int bottom) {
92            mDrawLeft = left + (right-left - mWidth) / 2;
93            mDrawTop = top + (bottom-top - mHeight) / 2;
94        }
95
96        @Override
97        public void setBounds(Rect bounds) {
98            // TODO Auto-generated method stub
99            super.setBounds(bounds);
100        }
101
102        @Override
103        public void setAlpha(int alpha) {
104            throw new UnsupportedOperationException(
105                    "Not supported with this drawable");
106        }
107
108        @Override
109        public void setColorFilter(ColorFilter cf) {
110            throw new UnsupportedOperationException(
111                    "Not supported with this drawable");
112        }
113
114        @Override
115        public void setDither(boolean dither) {
116            throw new UnsupportedOperationException(
117                    "Not supported with this drawable");
118        }
119
120        @Override
121        public void setFilterBitmap(boolean filter) {
122            throw new UnsupportedOperationException(
123                    "Not supported with this drawable");
124        }
125
126        @Override
127        public int getIntrinsicWidth() {
128            return mWidth;
129        }
130
131        @Override
132        public int getIntrinsicHeight() {
133            return mHeight;
134        }
135
136        @Override
137        public int getMinimumWidth() {
138            return mWidth;
139        }
140
141        @Override
142        public int getMinimumHeight() {
143            return mHeight;
144        }
145    }
146
147    static class Globals extends IWallpaperManagerCallback.Stub {
148        private IWallpaperManager mService;
149        private Bitmap mWallpaper;
150        private Bitmap mDefaultWallpaper;
151
152        private static final int MSG_CLEAR_WALLPAPER = 1;
153
154        private final Handler mHandler;
155
156        Globals(Looper looper) {
157            IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
158            mService = IWallpaperManager.Stub.asInterface(b);
159            mHandler = new Handler(looper) {
160                @Override
161                public void handleMessage(Message msg) {
162                    switch (msg.what) {
163                        case MSG_CLEAR_WALLPAPER:
164                            synchronized (this) {
165                                mWallpaper = null;
166                                mDefaultWallpaper = null;
167                            }
168                            break;
169                    }
170                }
171            };
172        }
173
174        public void onWallpaperChanged() {
175            /* The wallpaper has changed but we shouldn't eagerly load the
176             * wallpaper as that would be inefficient. Reset the cached wallpaper
177             * to null so if the user requests the wallpaper again then we'll
178             * fetch it.
179             */
180            mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
181        }
182
183        public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
184            synchronized (this) {
185                if (mWallpaper != null) {
186                    return mWallpaper;
187                }
188                if (mDefaultWallpaper != null) {
189                    return mDefaultWallpaper;
190                }
191                mWallpaper = getCurrentWallpaperLocked(context);
192                if (mWallpaper == null && returnDefault) {
193                    mDefaultWallpaper = getDefaultWallpaperLocked(context);
194                    return mDefaultWallpaper;
195                }
196                return mWallpaper;
197            }
198        }
199
200        private Bitmap getCurrentWallpaperLocked(Context context) {
201            try {
202                Bundle params = new Bundle();
203                ParcelFileDescriptor fd = mService.getWallpaper(this, params);
204                if (fd != null) {
205                    int width = params.getInt("width", 0);
206                    int height = params.getInt("height", 0);
207
208                    if (width <= 0 || height <= 0) {
209                        // Degenerate case: no size requested, just load
210                        // bitmap as-is.
211                        Bitmap bm = BitmapFactory.decodeFileDescriptor(
212                                fd.getFileDescriptor(), null, null);
213                        try {
214                            fd.close();
215                        } catch (IOException e) {
216                        }
217                        if (bm != null) {
218                            bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
219                        }
220                        return bm;
221                    }
222
223                    // Load the bitmap with full color depth, to preserve
224                    // quality for later processing.
225                    BitmapFactory.Options options = new BitmapFactory.Options();
226                    options.inDither = false;
227                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
228                    Bitmap bm = BitmapFactory.decodeFileDescriptor(
229                            fd.getFileDescriptor(), null, options);
230                    try {
231                        fd.close();
232                    } catch (IOException e) {
233                    }
234
235                    return generateBitmap(context, bm, width, height);
236                }
237            } catch (RemoteException e) {
238            }
239            return null;
240        }
241
242        private Bitmap getDefaultWallpaperLocked(Context context) {
243            try {
244                InputStream is = context.getResources().openRawResource(
245                        com.android.internal.R.drawable.default_wallpaper);
246                if (is != null) {
247                    int width = mService.getWidthHint();
248                    int height = mService.getHeightHint();
249
250                    if (width <= 0 || height <= 0) {
251                        // Degenerate case: no size requested, just load
252                        // bitmap as-is.
253                        Bitmap bm = BitmapFactory.decodeStream(is, null, null);
254                        try {
255                            is.close();
256                        } catch (IOException e) {
257                        }
258                        if (bm != null) {
259                            bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
260                        }
261                        return bm;
262                    }
263
264                    // Load the bitmap with full color depth, to preserve
265                    // quality for later processing.
266                    BitmapFactory.Options options = new BitmapFactory.Options();
267                    options.inDither = false;
268                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
269                    Bitmap bm = BitmapFactory.decodeStream(is, null, options);
270                    try {
271                        is.close();
272                    } catch (IOException e) {
273                    }
274
275                    return generateBitmap(context, bm, width, height);
276                }
277            } catch (RemoteException e) {
278            }
279            return null;
280        }
281    }
282
283    private static Object mSync = new Object();
284    private static Globals sGlobals;
285
286    static void initGlobals(Looper looper) {
287        synchronized (mSync) {
288            if (sGlobals == null) {
289                sGlobals = new Globals(looper);
290            }
291        }
292    }
293
294    /*package*/ WallpaperManager(Context context, Handler handler) {
295        mContext = context;
296        initGlobals(context.getMainLooper());
297    }
298
299    /**
300     * Retrieve a WallpaperManager associated with the given Context.
301     */
302    public static WallpaperManager getInstance(Context context) {
303        return (WallpaperManager)context.getSystemService(
304                Context.WALLPAPER_SERVICE);
305    }
306
307    /** @hide */
308    public IWallpaperManager getIWallpaperManager() {
309        return sGlobals.mService;
310    }
311
312    /**
313     * Like {@link #peekDrawable}, but always returns a valid Drawable.  If
314     * no wallpaper is set, the system default wallpaper is returned.
315     *
316     * @return Returns a Drawable object that will draw the wallpaper.
317     */
318    public Drawable getDrawable() {
319        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
320        if (bm != null) {
321            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
322            dr.setDither(false);
323            return dr;
324        }
325        return null;
326    }
327
328    /**
329     * Retrieve the current system wallpaper.  This is returned as an
330     * abstract Drawable that you can install in a View to display whatever
331     * wallpaper the user has currently set.  If there is no wallpaper set,
332     * a null pointer is returned.
333     *
334     * @return Returns a Drawable object that will draw the wallpaper or a
335     * null pointer if these is none.
336     */
337    public Drawable peekDrawable() {
338        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
339        if (bm != null) {
340            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
341            dr.setDither(false);
342            return dr;
343        }
344        return null;
345    }
346
347    /**
348     * Like {@link #peekFastDrawable}, but always returns a valid Drawable.  If
349     * no wallpaper is set, the system default wallpaper is returned.
350     *
351     * @return Returns a Drawable object that will draw the wallpaper.
352     */
353    public Drawable getFastDrawable() {
354        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
355        if (bm != null) {
356            Drawable dr = new FastBitmapDrawable(bm);
357            return dr;
358        }
359        return null;
360    }
361
362    /**
363     * Like {@link #peekDrawable()}, but the returned Drawable has a number
364     * of limitations to reduce its overhead as much as possible: it will
365     * never scale the wallpaper (only centering it if the requested bounds
366     * do match the bitmap bounds, which should not be typical), doesn't
367     * allow setting an alpha, color filter, or other attributes, etc.  The
368     * bounds of the returned drawable will be initialized to the same bounds
369     * as the wallpaper, so normally you will not need to touch it.  The
370     * drawable also assumes that it will be used in a context running in
371     * the same density as the screen (not in density compatibility mode).
372     *
373     * @return Returns an optimized Drawable object that will draw the
374     * wallpaper or a null pointer if these is none.
375     */
376    public Drawable peekFastDrawable() {
377        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
378        if (bm != null) {
379            Drawable dr = new FastBitmapDrawable(bm);
380            return dr;
381        }
382        return null;
383    }
384
385    /**
386     * If the current wallpaper is a live wallpaper component, return the
387     * information about that wallpaper.  Otherwise, if it is a static image,
388     * simply return null.
389     * @hide Live Wallpaper
390     */
391    public WallpaperInfo getWallpaperInfo() {
392        try {
393            return sGlobals.mService.getWallpaperInfo();
394        } catch (RemoteException e) {
395            return null;
396        }
397    }
398
399    /**
400     * Change the current system wallpaper to the bitmap in the given resource.
401     * The resource is opened as a raw data stream and copied into the
402     * wallpaper; it must be a valid PNG or JPEG image.  On success, the intent
403     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
404     *
405     * @param resid The bitmap to save.
406     *
407     * @throws IOException If an error occurs reverting to the default
408     * wallpaper.
409     */
410    public void setResource(int resid) throws IOException {
411        try {
412            Resources resources = mContext.getResources();
413            /* Set the wallpaper to the default values */
414            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
415                    "res:" + resources.getResourceName(resid));
416            if (fd != null) {
417                FileOutputStream fos = null;
418                try {
419                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
420                    setWallpaper(resources.openRawResource(resid), fos);
421                } finally {
422                    if (fos != null) {
423                        fos.close();
424                    }
425                }
426            }
427        } catch (RemoteException e) {
428        }
429    }
430
431    /**
432     * Change the current system wallpaper to a bitmap.  The given bitmap is
433     * converted to a PNG and stored as the wallpaper.  On success, the intent
434     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
435     *
436     * @param bitmap The bitmap to save.
437     *
438     * @throws IOException If an error occurs reverting to the default
439     * wallpaper.
440     */
441    public void setBitmap(Bitmap bitmap) throws IOException {
442        try {
443            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
444            if (fd == null) {
445                return;
446            }
447            FileOutputStream fos = null;
448            try {
449                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
450                bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
451            } finally {
452                if (fos != null) {
453                    fos.close();
454                }
455            }
456        } catch (RemoteException e) {
457        }
458    }
459
460    /**
461     * Change the current system wallpaper to a specific byte stream.  The
462     * give InputStream is copied into persistent storage and will now be
463     * used as the wallpaper.  Currently it must be either a JPEG or PNG
464     * image.  On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
465     * is broadcast.
466     *
467     * @param data A stream containing the raw data to install as a wallpaper.
468     *
469     * @throws IOException If an error occurs reverting to the default
470     * wallpaper.
471     */
472    public void setStream(InputStream data) throws IOException {
473        try {
474            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
475            if (fd == null) {
476                return;
477            }
478            FileOutputStream fos = null;
479            try {
480                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
481                setWallpaper(data, fos);
482            } finally {
483                if (fos != null) {
484                    fos.close();
485                }
486            }
487        } catch (RemoteException e) {
488        }
489    }
490
491    private void setWallpaper(InputStream data, FileOutputStream fos)
492            throws IOException {
493        byte[] buffer = new byte[32768];
494        int amt;
495        while ((amt=data.read(buffer)) > 0) {
496            fos.write(buffer, 0, amt);
497        }
498    }
499
500    /**
501     * Returns the desired minimum width for the wallpaper. Callers of
502     * {@link #setBitmap(android.graphics.Bitmap)} or
503     * {@link #setStream(java.io.InputStream)} should check this value
504     * beforehand to make sure the supplied wallpaper respects the desired
505     * minimum width.
506     *
507     * If the returned value is <= 0, the caller should use the width of
508     * the default display instead.
509     *
510     * @return The desired minimum width for the wallpaper. This value should
511     * be honored by applications that set the wallpaper but it is not
512     * mandatory.
513     */
514    public int getDesiredMinimumWidth() {
515        try {
516            return sGlobals.mService.getWidthHint();
517        } catch (RemoteException e) {
518            // Shouldn't happen!
519            return 0;
520        }
521    }
522
523    /**
524     * Returns the desired minimum height for the wallpaper. Callers of
525     * {@link #setBitmap(android.graphics.Bitmap)} or
526     * {@link #setStream(java.io.InputStream)} should check this value
527     * beforehand to make sure the supplied wallpaper respects the desired
528     * minimum height.
529     *
530     * If the returned value is <= 0, the caller should use the height of
531     * the default display instead.
532     *
533     * @return The desired minimum height for the wallpaper. This value should
534     * be honored by applications that set the wallpaper but it is not
535     * mandatory.
536     */
537    public int getDesiredMinimumHeight() {
538        try {
539            return sGlobals.mService.getHeightHint();
540        } catch (RemoteException e) {
541            // Shouldn't happen!
542            return 0;
543        }
544    }
545
546    /**
547     * For use only by the current home application, to specify the size of
548     * wallpaper it would like to use.  This allows such applications to have
549     * a virtual wallpaper that is larger than the physical screen, matching
550     * the size of their workspace.
551     * @param minimumWidth Desired minimum width
552     * @param minimumHeight Desired minimum height
553     */
554    public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
555        try {
556            sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
557        } catch (RemoteException e) {
558        }
559    }
560
561    /**
562     * Set the position of the current wallpaper within any larger space, when
563     * that wallpaper is visible behind the given window.  The X and Y offsets
564     * are floating point numbers ranging from 0 to 1, representing where the
565     * wallpaper should be positioned within the screen space.  These only
566     * make sense when the wallpaper is larger than the screen.
567     *
568     * @param windowToken The window who these offsets should be associated
569     * with, as returned by {@link android.view.View#getWindowVisibility()
570     * View.getWindowToken()}.
571     * @param xOffset The offset olong the X dimension, from 0 to 1.
572     * @param yOffset The offset along the Y dimension, from 0 to 1.
573     */
574    public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
575        try {
576            //Log.v(TAG, "Sending new wallpaper offsets from app...");
577            ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
578                    windowToken, xOffset, yOffset);
579            //Log.v(TAG, "...app returning after sending offsets!");
580        } catch (RemoteException e) {
581            // Ignore.
582        }
583    }
584
585    /**
586     * Clear the offsets previously associated with this window through
587     * {@link #setWallpaperOffsets(IBinder, float, float)}.  This reverts
588     * the window to its default state, where it does not cause the wallpaper
589     * to scroll from whatever its last offsets were.
590     *
591     * @param windowToken The window who these offsets should be associated
592     * with, as returned by {@link android.view.View#getWindowVisibility()
593     * View.getWindowToken()}.
594     */
595    public void clearWallpaperOffsets(IBinder windowToken) {
596        try {
597            ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
598                    windowToken, -1, -1);
599        } catch (RemoteException e) {
600            // Ignore.
601        }
602    }
603
604    /**
605     * Remove any currently set wallpaper, reverting to the system's default
606     * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
607     * is broadcast.
608     *
609     * @throws IOException If an error occurs reverting to the default
610     * wallpaper.
611     */
612    public void clear() throws IOException {
613        setResource(com.android.internal.R.drawable.default_wallpaper);
614    }
615
616    static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) {
617        if (bm == null) {
618            return bm;
619        }
620        bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
621
622        // This is the final bitmap we want to return.
623        // XXX We should get the pixel depth from the system (to match the
624        // physical display depth), when there is a way.
625        Bitmap newbm = Bitmap.createBitmap(width, height,
626                Bitmap.Config.RGB_565);
627        newbm.setDensity(DisplayMetrics.DENSITY_DEVICE);
628        Canvas c = new Canvas(newbm);
629        c.setDensity(DisplayMetrics.DENSITY_DEVICE);
630        Rect targetRect = new Rect();
631        targetRect.left = targetRect.top = 0;
632        targetRect.right = bm.getWidth();
633        targetRect.bottom = bm.getHeight();
634
635        int deltaw = width - targetRect.right;
636        int deltah = height - targetRect.bottom;
637
638        if (deltaw > 0 || deltah > 0) {
639            // We need to scale up so it covers the entire
640            // area.
641            float scale = 1.0f;
642            if (deltaw > deltah) {
643                scale = width / (float)targetRect.right;
644            } else {
645                scale = height / (float)targetRect.bottom;
646            }
647            targetRect.right = (int)(targetRect.right*scale);
648            targetRect.bottom = (int)(targetRect.bottom*scale);
649            deltaw = width - targetRect.right;
650            deltah = height - targetRect.bottom;
651        }
652
653        targetRect.offset(deltaw/2, deltah/2);
654        Paint paint = new Paint();
655        paint.setFilterBitmap(true);
656        paint.setDither(true);
657        c.drawBitmap(bm, null, targetRect, paint);
658
659        bm.recycle();
660        return newbm;
661    }
662}
663