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