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.PorterDuff;
29import android.graphics.PorterDuffXfermode;
30import android.graphics.Rect;
31import android.graphics.drawable.BitmapDrawable;
32import android.graphics.drawable.Drawable;
33import android.os.Binder;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.Looper;
38import android.os.Message;
39import android.os.ParcelFileDescriptor;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.util.DisplayMetrics;
43import android.util.Log;
44import android.view.ViewRootImpl;
45
46import java.io.FileOutputStream;
47import java.io.IOException;
48import java.io.InputStream;
49
50/**
51 * Provides access to the system wallpaper. With WallpaperManager, you can
52 * get the current wallpaper, get the desired dimensions for the wallpaper, set
53 * the wallpaper, and more. Get an instance of WallpaperManager with
54 * {@link #getInstance(android.content.Context) getInstance()}.
55 */
56public class WallpaperManager {
57    private static String TAG = "WallpaperManager";
58    private static boolean DEBUG = false;
59    private float mWallpaperXStep = -1;
60    private float mWallpaperYStep = -1;
61
62    /**
63     * Launch an activity for the user to pick the current global live
64     * wallpaper.
65     */
66    public static final String ACTION_LIVE_WALLPAPER_CHOOSER
67            = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER";
68
69    /**
70     * Directly launch live wallpaper preview, allowing the user to immediately
71     * confirm to switch to a specific live wallpaper.  You must specify
72     * {@link #EXTRA_LIVE_WALLPAPER_COMPONENT} with the ComponentName of
73     * a live wallpaper component that is to be shown.
74     */
75    public static final String ACTION_CHANGE_LIVE_WALLPAPER
76            = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER";
77
78    /**
79     * Extra in {@link #ACTION_CHANGE_LIVE_WALLPAPER} that specifies the
80     * ComponentName of a live wallpaper that should be shown as a preview,
81     * for the user to confirm.
82     */
83    public static final String EXTRA_LIVE_WALLPAPER_COMPONENT
84            = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT";
85
86    /**
87     * Manifest entry for activities that respond to {@link Intent#ACTION_SET_WALLPAPER}
88     * which allows them to provide a custom large icon associated with this action.
89     */
90    public static final String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
91
92    /**
93     * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
94     * host when the user taps on an empty area (not performing an action
95     * in the host).  The x and y arguments are the location of the tap in
96     * screen coordinates.
97     */
98    public static final String COMMAND_TAP = "android.wallpaper.tap";
99
100    /**
101     * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
102     * host when the user releases a secondary pointer on an empty area
103     * (not performing an action in the host).  The x and y arguments are
104     * the location of the secondary tap in screen coordinates.
105     */
106    public static final String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap";
107
108    /**
109     * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
110     * host when the user drops an object into an area of the host.  The x
111     * and y arguments are the location of the drop.
112     */
113    public static final String COMMAND_DROP = "android.home.drop";
114
115    private final Context mContext;
116
117    /**
118     * Special drawable that draws a wallpaper as fast as possible.  Assumes
119     * no scaling or placement off (0,0) of the wallpaper (this should be done
120     * at the time the bitmap is loaded).
121     */
122    static class FastBitmapDrawable extends Drawable {
123        private final Bitmap mBitmap;
124        private final int mWidth;
125        private final int mHeight;
126        private int mDrawLeft;
127        private int mDrawTop;
128        private final Paint mPaint;
129
130        private FastBitmapDrawable(Bitmap bitmap) {
131            mBitmap = bitmap;
132            mWidth = bitmap.getWidth();
133            mHeight = bitmap.getHeight();
134
135            setBounds(0, 0, mWidth, mHeight);
136
137            mPaint = new Paint();
138            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
139        }
140
141        @Override
142        public void draw(Canvas canvas) {
143            canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, mPaint);
144        }
145
146        @Override
147        public int getOpacity() {
148            return PixelFormat.OPAQUE;
149        }
150
151        @Override
152        public void setBounds(int left, int top, int right, int bottom) {
153            mDrawLeft = left + (right-left - mWidth) / 2;
154            mDrawTop = top + (bottom-top - mHeight) / 2;
155        }
156
157        @Override
158        public void setAlpha(int alpha) {
159            throw new UnsupportedOperationException("Not supported with this drawable");
160        }
161
162        @Override
163        public void setColorFilter(ColorFilter cf) {
164            throw new UnsupportedOperationException("Not supported with this drawable");
165        }
166
167        @Override
168        public void setDither(boolean dither) {
169            throw new UnsupportedOperationException("Not supported with this drawable");
170        }
171
172        @Override
173        public void setFilterBitmap(boolean filter) {
174            throw new UnsupportedOperationException("Not supported with this drawable");
175        }
176
177        @Override
178        public int getIntrinsicWidth() {
179            return mWidth;
180        }
181
182        @Override
183        public int getIntrinsicHeight() {
184            return mHeight;
185        }
186
187        @Override
188        public int getMinimumWidth() {
189            return mWidth;
190        }
191
192        @Override
193        public int getMinimumHeight() {
194            return mHeight;
195        }
196    }
197
198    static class Globals extends IWallpaperManagerCallback.Stub {
199        private IWallpaperManager mService;
200        private Bitmap mWallpaper;
201        private Bitmap mDefaultWallpaper;
202
203        private static final int MSG_CLEAR_WALLPAPER = 1;
204
205        private final Handler mHandler;
206
207        Globals(Looper looper) {
208            IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
209            mService = IWallpaperManager.Stub.asInterface(b);
210            mHandler = new Handler(looper) {
211                @Override
212                public void handleMessage(Message msg) {
213                    switch (msg.what) {
214                        case MSG_CLEAR_WALLPAPER:
215                            synchronized (this) {
216                                mWallpaper = null;
217                                mDefaultWallpaper = null;
218                            }
219                            break;
220                    }
221                }
222            };
223        }
224
225        public void onWallpaperChanged() {
226            /* The wallpaper has changed but we shouldn't eagerly load the
227             * wallpaper as that would be inefficient. Reset the cached wallpaper
228             * to null so if the user requests the wallpaper again then we'll
229             * fetch it.
230             */
231            mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
232        }
233
234        public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
235            synchronized (this) {
236                if (mWallpaper != null) {
237                    return mWallpaper;
238                }
239                if (mDefaultWallpaper != null) {
240                    return mDefaultWallpaper;
241                }
242                mWallpaper = null;
243                try {
244                    mWallpaper = getCurrentWallpaperLocked();
245                } catch (OutOfMemoryError e) {
246                    Log.w(TAG, "No memory load current wallpaper", e);
247                }
248                if (returnDefault) {
249                    if (mWallpaper == null) {
250                        mDefaultWallpaper = getDefaultWallpaperLocked(context);
251                        return mDefaultWallpaper;
252                    } else {
253                        mDefaultWallpaper = null;
254                    }
255                }
256                return mWallpaper;
257            }
258        }
259
260        public void forgetLoadedWallpaper() {
261            synchronized (this) {
262                mWallpaper = null;
263                mDefaultWallpaper = null;
264            }
265        }
266
267        private Bitmap getCurrentWallpaperLocked() {
268            try {
269                Bundle params = new Bundle();
270                ParcelFileDescriptor fd = mService.getWallpaper(this, params);
271                if (fd != null) {
272                    int width = params.getInt("width", 0);
273                    int height = params.getInt("height", 0);
274
275                    try {
276                        BitmapFactory.Options options = new BitmapFactory.Options();
277                        Bitmap bm = BitmapFactory.decodeFileDescriptor(
278                                fd.getFileDescriptor(), null, options);
279                        return generateBitmap(bm, width, height);
280                    } catch (OutOfMemoryError e) {
281                        Log.w(TAG, "Can't decode file", e);
282                    } finally {
283                        try {
284                            fd.close();
285                        } catch (IOException e) {
286                            // Ignore
287                        }
288                    }
289                }
290            } catch (RemoteException e) {
291                // Ignore
292            }
293            return null;
294        }
295
296        private Bitmap getDefaultWallpaperLocked(Context context) {
297            try {
298                InputStream is = context.getResources().openRawResource(
299                        com.android.internal.R.drawable.default_wallpaper);
300                if (is != null) {
301                    int width = mService.getWidthHint();
302                    int height = mService.getHeightHint();
303
304                    try {
305                        BitmapFactory.Options options = new BitmapFactory.Options();
306                        Bitmap bm = BitmapFactory.decodeStream(is, null, options);
307                        return generateBitmap(bm, width, height);
308                    } catch (OutOfMemoryError e) {
309                        Log.w(TAG, "Can't decode stream", e);
310                    } finally {
311                        try {
312                            is.close();
313                        } catch (IOException e) {
314                            // Ignore
315                        }
316                    }
317                }
318            } catch (RemoteException e) {
319                // Ignore
320            }
321            return null;
322        }
323    }
324
325    private static final Object sSync = new Object[0];
326    private static Globals sGlobals;
327
328    static void initGlobals(Looper looper) {
329        synchronized (sSync) {
330            if (sGlobals == null) {
331                sGlobals = new Globals(looper);
332            }
333        }
334    }
335
336    /*package*/ WallpaperManager(Context context, Handler handler) {
337        mContext = context;
338        initGlobals(context.getMainLooper());
339    }
340
341    /**
342     * Retrieve a WallpaperManager associated with the given Context.
343     */
344    public static WallpaperManager getInstance(Context context) {
345        return (WallpaperManager)context.getSystemService(
346                Context.WALLPAPER_SERVICE);
347    }
348
349    /** @hide */
350    public IWallpaperManager getIWallpaperManager() {
351        return sGlobals.mService;
352    }
353
354    /**
355     * Retrieve the current system wallpaper; if
356     * no wallpaper is set, the system default wallpaper is returned.
357     * This is returned as an
358     * abstract Drawable that you can install in a View to display whatever
359     * wallpaper the user has currently set.
360     *
361     * @return Returns a Drawable object that will draw the wallpaper.
362     */
363    public Drawable getDrawable() {
364        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
365        if (bm != null) {
366            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
367            dr.setDither(false);
368            return dr;
369        }
370        return null;
371    }
372
373    /**
374     * Retrieve the current system wallpaper; if there is no wallpaper set,
375     * a null pointer is returned. This is returned as an
376     * abstract Drawable that you can install in a View to display whatever
377     * wallpaper the user has currently set.
378     *
379     * @return Returns a Drawable object that will draw the wallpaper or a
380     * null pointer if these is none.
381     */
382    public Drawable peekDrawable() {
383        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
384        if (bm != null) {
385            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
386            dr.setDither(false);
387            return dr;
388        }
389        return null;
390    }
391
392    /**
393     * Like {@link #getDrawable()}, but the returned Drawable has a number
394     * of limitations to reduce its overhead as much as possible. It will
395     * never scale the wallpaper (only centering it if the requested bounds
396     * do match the bitmap bounds, which should not be typical), doesn't
397     * allow setting an alpha, color filter, or other attributes, etc.  The
398     * bounds of the returned drawable will be initialized to the same bounds
399     * as the wallpaper, so normally you will not need to touch it.  The
400     * drawable also assumes that it will be used in a context running in
401     * the same density as the screen (not in density compatibility mode).
402     *
403     * @return Returns a Drawable object that will draw the wallpaper.
404     */
405    public Drawable getFastDrawable() {
406        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
407        if (bm != null) {
408            return new FastBitmapDrawable(bm);
409        }
410        return null;
411    }
412
413    /**
414     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
415     * a null pointer is returned.
416     *
417     * @return Returns an optimized Drawable object that will draw the
418     * wallpaper or a null pointer if these is none.
419     */
420    public Drawable peekFastDrawable() {
421        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
422        if (bm != null) {
423            return new FastBitmapDrawable(bm);
424        }
425        return null;
426    }
427
428    /**
429     * Like {@link #getDrawable()} but returns a Bitmap.
430     *
431     * @hide
432     */
433    public Bitmap getBitmap() {
434        return sGlobals.peekWallpaperBitmap(mContext, true);
435    }
436
437    /**
438     * Remove all internal references to the last loaded wallpaper.  Useful
439     * for apps that want to reduce memory usage when they only temporarily
440     * need to have the wallpaper.  After calling, the next request for the
441     * wallpaper will require reloading it again from disk.
442     */
443    public void forgetLoadedWallpaper() {
444        sGlobals.forgetLoadedWallpaper();
445    }
446
447    /**
448     * If the current wallpaper is a live wallpaper component, return the
449     * information about that wallpaper.  Otherwise, if it is a static image,
450     * simply return null.
451     */
452    public WallpaperInfo getWallpaperInfo() {
453        try {
454            if (sGlobals.mService == null) {
455                Log.w(TAG, "WallpaperService not running");
456                return null;
457            } else {
458                return sGlobals.mService.getWallpaperInfo();
459            }
460        } catch (RemoteException e) {
461            return null;
462        }
463    }
464
465    /**
466     * Change the current system wallpaper to the bitmap in the given resource.
467     * The resource is opened as a raw data stream and copied into the
468     * wallpaper; it must be a valid PNG or JPEG image.  On success, the intent
469     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
470     *
471     * <p>This method requires the caller to hold the permission
472     * {@link android.Manifest.permission#SET_WALLPAPER}.
473     *
474     * @param resid The bitmap to save.
475     *
476     * @throws IOException If an error occurs reverting to the default
477     * wallpaper.
478     */
479    public void setResource(int resid) throws IOException {
480        if (sGlobals.mService == null) {
481            Log.w(TAG, "WallpaperService not running");
482            return;
483        }
484        try {
485            Resources resources = mContext.getResources();
486            /* Set the wallpaper to the default values */
487            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
488                    "res:" + resources.getResourceName(resid));
489            if (fd != null) {
490                FileOutputStream fos = null;
491                try {
492                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
493                    setWallpaper(resources.openRawResource(resid), fos);
494                } finally {
495                    if (fos != null) {
496                        fos.close();
497                    }
498                }
499            }
500        } catch (RemoteException e) {
501            // Ignore
502        }
503    }
504
505    /**
506     * Change the current system wallpaper to a bitmap.  The given bitmap is
507     * converted to a PNG and stored as the wallpaper.  On success, the intent
508     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
509     *
510     * <p>This method requires the caller to hold the permission
511     * {@link android.Manifest.permission#SET_WALLPAPER}.
512     *
513     * @param bitmap The bitmap to save.
514     *
515     * @throws IOException If an error occurs reverting to the default
516     * wallpaper.
517     */
518    public void setBitmap(Bitmap bitmap) throws IOException {
519        if (sGlobals.mService == null) {
520            Log.w(TAG, "WallpaperService not running");
521            return;
522        }
523        try {
524            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
525            if (fd == null) {
526                return;
527            }
528            FileOutputStream fos = null;
529            try {
530                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
531                bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
532            } finally {
533                if (fos != null) {
534                    fos.close();
535                }
536            }
537        } catch (RemoteException e) {
538            // Ignore
539        }
540    }
541
542    /**
543     * Change the current system wallpaper to a specific byte stream.  The
544     * give InputStream is copied into persistent storage and will now be
545     * used as the wallpaper.  Currently it must be either a JPEG or PNG
546     * image.  On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
547     * is broadcast.
548     *
549     * <p>This method requires the caller to hold the permission
550     * {@link android.Manifest.permission#SET_WALLPAPER}.
551     *
552     * @param data A stream containing the raw data to install as a wallpaper.
553     *
554     * @throws IOException If an error occurs reverting to the default
555     * wallpaper.
556     */
557    public void setStream(InputStream data) throws IOException {
558        if (sGlobals.mService == null) {
559            Log.w(TAG, "WallpaperService not running");
560            return;
561        }
562        try {
563            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
564            if (fd == null) {
565                return;
566            }
567            FileOutputStream fos = null;
568            try {
569                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
570                setWallpaper(data, fos);
571            } finally {
572                if (fos != null) {
573                    fos.close();
574                }
575            }
576        } catch (RemoteException e) {
577            // Ignore
578        }
579    }
580
581    private void setWallpaper(InputStream data, FileOutputStream fos)
582            throws IOException {
583        byte[] buffer = new byte[32768];
584        int amt;
585        while ((amt=data.read(buffer)) > 0) {
586            fos.write(buffer, 0, amt);
587        }
588    }
589
590    /**
591     * Returns the desired minimum width for the wallpaper. Callers of
592     * {@link #setBitmap(android.graphics.Bitmap)} or
593     * {@link #setStream(java.io.InputStream)} should check this value
594     * beforehand to make sure the supplied wallpaper respects the desired
595     * minimum width.
596     *
597     * If the returned value is <= 0, the caller should use the width of
598     * the default display instead.
599     *
600     * @return The desired minimum width for the wallpaper. This value should
601     * be honored by applications that set the wallpaper but it is not
602     * mandatory.
603     */
604    public int getDesiredMinimumWidth() {
605        if (sGlobals.mService == null) {
606            Log.w(TAG, "WallpaperService not running");
607            return 0;
608        }
609        try {
610            return sGlobals.mService.getWidthHint();
611        } catch (RemoteException e) {
612            // Shouldn't happen!
613            return 0;
614        }
615    }
616
617    /**
618     * Returns the desired minimum height for the wallpaper. Callers of
619     * {@link #setBitmap(android.graphics.Bitmap)} or
620     * {@link #setStream(java.io.InputStream)} should check this value
621     * beforehand to make sure the supplied wallpaper respects the desired
622     * minimum height.
623     *
624     * If the returned value is <= 0, the caller should use the height of
625     * the default display instead.
626     *
627     * @return The desired minimum height for the wallpaper. This value should
628     * be honored by applications that set the wallpaper but it is not
629     * mandatory.
630     */
631    public int getDesiredMinimumHeight() {
632        if (sGlobals.mService == null) {
633            Log.w(TAG, "WallpaperService not running");
634            return 0;
635        }
636        try {
637            return sGlobals.mService.getHeightHint();
638        } catch (RemoteException e) {
639            // Shouldn't happen!
640            return 0;
641        }
642    }
643
644    /**
645     * For use only by the current home application, to specify the size of
646     * wallpaper it would like to use.  This allows such applications to have
647     * a virtual wallpaper that is larger than the physical screen, matching
648     * the size of their workspace.
649     *
650     * <p>Note developers, who don't seem to be reading this.  This is
651     * for <em>home screens</em> to tell what size wallpaper they would like.
652     * Nobody else should be calling this!  Certainly not other non-home-screen
653     * apps that change the wallpaper.  Those apps are supposed to
654     * <b>retrieve</b> the suggested size so they can construct a wallpaper
655     * that matches it.
656     *
657     * <p>This method requires the caller to hold the permission
658     * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}.
659     *
660     * @param minimumWidth Desired minimum width
661     * @param minimumHeight Desired minimum height
662     */
663    public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
664        try {
665            if (sGlobals.mService == null) {
666                Log.w(TAG, "WallpaperService not running");
667            } else {
668                sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
669            }
670        } catch (RemoteException e) {
671            // Ignore
672        }
673    }
674
675    /**
676     * Set the position of the current wallpaper within any larger space, when
677     * that wallpaper is visible behind the given window.  The X and Y offsets
678     * are floating point numbers ranging from 0 to 1, representing where the
679     * wallpaper should be positioned within the screen space.  These only
680     * make sense when the wallpaper is larger than the screen.
681     *
682     * @param windowToken The window who these offsets should be associated
683     * with, as returned by {@link android.view.View#getWindowToken()
684     * View.getWindowToken()}.
685     * @param xOffset The offset along the X dimension, from 0 to 1.
686     * @param yOffset The offset along the Y dimension, from 0 to 1.
687     */
688    public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
689        try {
690            //Log.v(TAG, "Sending new wallpaper offsets from app...");
691            ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
692                    windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
693            //Log.v(TAG, "...app returning after sending offsets!");
694        } catch (RemoteException e) {
695            // Ignore.
696        }
697    }
698
699    /**
700     * For applications that use multiple virtual screens showing a wallpaper,
701     * specify the step size between virtual screens. For example, if the
702     * launcher has 3 virtual screens, it would specify an xStep of 0.5,
703     * since the X offset for those screens are 0.0, 0.5 and 1.0
704     * @param xStep The X offset delta from one screen to the next one
705     * @param yStep The Y offset delta from one screen to the next one
706     */
707    public void setWallpaperOffsetSteps(float xStep, float yStep) {
708        mWallpaperXStep = xStep;
709        mWallpaperYStep = yStep;
710    }
711
712    /**
713     * Send an arbitrary command to the current active wallpaper.
714     *
715     * @param windowToken The window who these offsets should be associated
716     * with, as returned by {@link android.view.View#getWindowToken()
717     * View.getWindowToken()}.
718     * @param action Name of the command to perform.  This must be a scoped
719     * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT".
720     * @param x Arbitrary integer argument based on command.
721     * @param y Arbitrary integer argument based on command.
722     * @param z Arbitrary integer argument based on command.
723     * @param extras Optional additional information for the command, or null.
724     */
725    public void sendWallpaperCommand(IBinder windowToken, String action,
726            int x, int y, int z, Bundle extras) {
727        try {
728            //Log.v(TAG, "Sending new wallpaper offsets from app...");
729            ViewRootImpl.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
730                    windowToken, action, x, y, z, extras, false);
731            //Log.v(TAG, "...app returning after sending offsets!");
732        } catch (RemoteException e) {
733            // Ignore.
734        }
735    }
736
737    /**
738     * Clear the offsets previously associated with this window through
739     * {@link #setWallpaperOffsets(IBinder, float, float)}.  This reverts
740     * the window to its default state, where it does not cause the wallpaper
741     * to scroll from whatever its last offsets were.
742     *
743     * @param windowToken The window who these offsets should be associated
744     * with, as returned by {@link android.view.View#getWindowToken()
745     * View.getWindowToken()}.
746     */
747    public void clearWallpaperOffsets(IBinder windowToken) {
748        try {
749            ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
750                    windowToken, -1, -1, -1, -1);
751        } catch (RemoteException e) {
752            // Ignore.
753        }
754    }
755
756    /**
757     * Remove any currently set wallpaper, reverting to the system's default
758     * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
759     * is broadcast.
760     *
761     * <p>This method requires the caller to hold the permission
762     * {@link android.Manifest.permission#SET_WALLPAPER}.
763     *
764     * @throws IOException If an error occurs reverting to the default
765     * wallpaper.
766     */
767    public void clear() throws IOException {
768        setResource(com.android.internal.R.drawable.default_wallpaper);
769    }
770
771    static Bitmap generateBitmap(Bitmap bm, int width, int height) {
772        if (bm == null) {
773            return null;
774        }
775
776        bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
777
778        if (width <= 0 || height <= 0
779                || (bm.getWidth() == width && bm.getHeight() == height)) {
780            return bm;
781        }
782
783        // This is the final bitmap we want to return.
784        try {
785            Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
786            newbm.setDensity(DisplayMetrics.DENSITY_DEVICE);
787
788            Canvas c = new Canvas(newbm);
789            Rect targetRect = new Rect();
790            targetRect.right = bm.getWidth();
791            targetRect.bottom = bm.getHeight();
792
793            int deltaw = width - targetRect.right;
794            int deltah = height - targetRect.bottom;
795
796            if (deltaw > 0 || deltah > 0) {
797                // We need to scale up so it covers the entire area.
798                float scale;
799                if (deltaw > deltah) {
800                    scale = width / (float)targetRect.right;
801                } else {
802                    scale = height / (float)targetRect.bottom;
803                }
804                targetRect.right = (int)(targetRect.right*scale);
805                targetRect.bottom = (int)(targetRect.bottom*scale);
806                deltaw = width - targetRect.right;
807                deltah = height - targetRect.bottom;
808            }
809
810            targetRect.offset(deltaw/2, deltah/2);
811
812            Paint paint = new Paint();
813            paint.setFilterBitmap(true);
814            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
815            c.drawBitmap(bm, null, targetRect, paint);
816
817            bm.recycle();
818            c.setBitmap(null);
819            return newbm;
820        } catch (OutOfMemoryError e) {
821            Log.w(TAG, "Can't generate default bitmap", e);
822            return bm;
823        }
824    }
825}
826