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