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