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