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