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