Home | History | Annotate | Download | only in app
      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.ContentResolver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.PackageManager;
     23 import android.content.pm.ResolveInfo;
     24 import android.content.res.Resources;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.graphics.BitmapRegionDecoder;
     28 import android.graphics.Canvas;
     29 import android.graphics.ColorFilter;
     30 import android.graphics.Matrix;
     31 import android.graphics.Paint;
     32 import android.graphics.PixelFormat;
     33 import android.graphics.PorterDuff;
     34 import android.graphics.PorterDuffXfermode;
     35 import android.graphics.Rect;
     36 import android.graphics.RectF;
     37 import android.graphics.drawable.BitmapDrawable;
     38 import android.graphics.drawable.Drawable;
     39 import android.net.Uri;
     40 import android.os.Bundle;
     41 import android.os.Handler;
     42 import android.os.IBinder;
     43 import android.os.Looper;
     44 import android.os.Message;
     45 import android.os.ParcelFileDescriptor;
     46 import android.os.RemoteException;
     47 import android.os.ServiceManager;
     48 import android.util.DisplayMetrics;
     49 import android.util.Log;
     50 import android.view.WindowManager;
     51 import android.view.WindowManagerGlobal;
     52 
     53 import java.io.BufferedInputStream;
     54 import java.io.FileOutputStream;
     55 import java.io.IOException;
     56 import java.io.InputStream;
     57 import java.util.List;
     58 
     59 /**
     60  * Provides access to the system wallpaper. With WallpaperManager, you can
     61  * get the current wallpaper, get the desired dimensions for the wallpaper, set
     62  * the wallpaper, and more. Get an instance of WallpaperManager with
     63  * {@link #getInstance(android.content.Context) getInstance()}.
     64  */
     65 public class WallpaperManager {
     66     private static String TAG = "WallpaperManager";
     67     private static boolean DEBUG = false;
     68     private float mWallpaperXStep = -1;
     69     private float mWallpaperYStep = -1;
     70 
     71     /**
     72      * Activity Action: Show settings for choosing wallpaper. Do not use directly to construct
     73      * an intent; instead, use {@link #getCropAndSetWallpaperIntent}.
     74      * <p>Input:  {@link Intent#getData} is the URI of the image to crop and set as wallpaper.
     75      * <p>Output: RESULT_OK if user decided to crop/set the wallpaper, RESULT_CANCEL otherwise
     76      * Activities that support this intent should specify a MIME filter of "image/*"
     77      */
     78     public static final String ACTION_CROP_AND_SET_WALLPAPER =
     79             "android.service.wallpaper.CROP_AND_SET_WALLPAPER";
     80 
     81     /**
     82      * Launch an activity for the user to pick the current global live
     83      * wallpaper.
     84      */
     85     public static final String ACTION_LIVE_WALLPAPER_CHOOSER
     86             = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER";
     87 
     88     /**
     89      * Directly launch live wallpaper preview, allowing the user to immediately
     90      * confirm to switch to a specific live wallpaper.  You must specify
     91      * {@link #EXTRA_LIVE_WALLPAPER_COMPONENT} with the ComponentName of
     92      * a live wallpaper component that is to be shown.
     93      */
     94     public static final String ACTION_CHANGE_LIVE_WALLPAPER
     95             = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER";
     96 
     97     /**
     98      * Extra in {@link #ACTION_CHANGE_LIVE_WALLPAPER} that specifies the
     99      * ComponentName of a live wallpaper that should be shown as a preview,
    100      * for the user to confirm.
    101      */
    102     public static final String EXTRA_LIVE_WALLPAPER_COMPONENT
    103             = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT";
    104 
    105     /**
    106      * Manifest entry for activities that respond to {@link Intent#ACTION_SET_WALLPAPER}
    107      * which allows them to provide a custom large icon associated with this action.
    108      */
    109     public static final String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
    110 
    111     /**
    112      * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
    113      * host when the user taps on an empty area (not performing an action
    114      * in the host).  The x and y arguments are the location of the tap in
    115      * screen coordinates.
    116      */
    117     public static final String COMMAND_TAP = "android.wallpaper.tap";
    118 
    119     /**
    120      * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
    121      * host when the user releases a secondary pointer on an empty area
    122      * (not performing an action in the host).  The x and y arguments are
    123      * the location of the secondary tap in screen coordinates.
    124      */
    125     public static final String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap";
    126 
    127     /**
    128      * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
    129      * host when the user drops an object into an area of the host.  The x
    130      * and y arguments are the location of the drop.
    131      */
    132     public static final String COMMAND_DROP = "android.home.drop";
    133 
    134     private final Context mContext;
    135 
    136     /**
    137      * Special drawable that draws a wallpaper as fast as possible.  Assumes
    138      * no scaling or placement off (0,0) of the wallpaper (this should be done
    139      * at the time the bitmap is loaded).
    140      */
    141     static class FastBitmapDrawable extends Drawable {
    142         private final Bitmap mBitmap;
    143         private final int mWidth;
    144         private final int mHeight;
    145         private int mDrawLeft;
    146         private int mDrawTop;
    147         private final Paint mPaint;
    148 
    149         private FastBitmapDrawable(Bitmap bitmap) {
    150             mBitmap = bitmap;
    151             mWidth = bitmap.getWidth();
    152             mHeight = bitmap.getHeight();
    153 
    154             setBounds(0, 0, mWidth, mHeight);
    155 
    156             mPaint = new Paint();
    157             mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    158         }
    159 
    160         @Override
    161         public void draw(Canvas canvas) {
    162             canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, mPaint);
    163         }
    164 
    165         @Override
    166         public int getOpacity() {
    167             return PixelFormat.OPAQUE;
    168         }
    169 
    170         @Override
    171         public void setBounds(int left, int top, int right, int bottom) {
    172             mDrawLeft = left + (right-left - mWidth) / 2;
    173             mDrawTop = top + (bottom-top - mHeight) / 2;
    174         }
    175 
    176         @Override
    177         public void setAlpha(int alpha) {
    178             throw new UnsupportedOperationException("Not supported with this drawable");
    179         }
    180 
    181         @Override
    182         public void setColorFilter(ColorFilter cf) {
    183             throw new UnsupportedOperationException("Not supported with this drawable");
    184         }
    185 
    186         @Override
    187         public void setDither(boolean dither) {
    188             throw new UnsupportedOperationException("Not supported with this drawable");
    189         }
    190 
    191         @Override
    192         public void setFilterBitmap(boolean filter) {
    193             throw new UnsupportedOperationException("Not supported with this drawable");
    194         }
    195 
    196         @Override
    197         public int getIntrinsicWidth() {
    198             return mWidth;
    199         }
    200 
    201         @Override
    202         public int getIntrinsicHeight() {
    203             return mHeight;
    204         }
    205 
    206         @Override
    207         public int getMinimumWidth() {
    208             return mWidth;
    209         }
    210 
    211         @Override
    212         public int getMinimumHeight() {
    213             return mHeight;
    214         }
    215     }
    216 
    217     static class Globals extends IWallpaperManagerCallback.Stub {
    218         private IWallpaperManager mService;
    219         private Bitmap mWallpaper;
    220         private Bitmap mDefaultWallpaper;
    221 
    222         private static final int MSG_CLEAR_WALLPAPER = 1;
    223 
    224         private final Handler mHandler;
    225 
    226         Globals(Looper looper) {
    227             IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
    228             mService = IWallpaperManager.Stub.asInterface(b);
    229             mHandler = new Handler(looper) {
    230                 @Override
    231                 public void handleMessage(Message msg) {
    232                     switch (msg.what) {
    233                         case MSG_CLEAR_WALLPAPER:
    234                             synchronized (this) {
    235                                 mWallpaper = null;
    236                                 mDefaultWallpaper = null;
    237                             }
    238                             break;
    239                     }
    240                 }
    241             };
    242         }
    243 
    244         public void onWallpaperChanged() {
    245             /* The wallpaper has changed but we shouldn't eagerly load the
    246              * wallpaper as that would be inefficient. Reset the cached wallpaper
    247              * to null so if the user requests the wallpaper again then we'll
    248              * fetch it.
    249              */
    250             mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
    251         }
    252 
    253         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
    254             synchronized (this) {
    255                 if (mWallpaper != null) {
    256                     return mWallpaper;
    257                 }
    258                 if (mDefaultWallpaper != null) {
    259                     return mDefaultWallpaper;
    260                 }
    261                 mWallpaper = null;
    262                 try {
    263                     mWallpaper = getCurrentWallpaperLocked(context);
    264                 } catch (OutOfMemoryError e) {
    265                     Log.w(TAG, "No memory load current wallpaper", e);
    266                 }
    267                 if (returnDefault) {
    268                     if (mWallpaper == null) {
    269                         mDefaultWallpaper = getDefaultWallpaperLocked(context);
    270                         return mDefaultWallpaper;
    271                     } else {
    272                         mDefaultWallpaper = null;
    273                     }
    274                 }
    275                 return mWallpaper;
    276             }
    277         }
    278 
    279         public void forgetLoadedWallpaper() {
    280             synchronized (this) {
    281                 mWallpaper = null;
    282                 mDefaultWallpaper = null;
    283                 mHandler.removeMessages(MSG_CLEAR_WALLPAPER);
    284             }
    285         }
    286 
    287         private Bitmap getCurrentWallpaperLocked(Context context) {
    288             try {
    289                 Bundle params = new Bundle();
    290                 ParcelFileDescriptor fd = mService.getWallpaper(this, params);
    291                 if (fd != null) {
    292                     int width = params.getInt("width", 0);
    293                     int height = params.getInt("height", 0);
    294 
    295                     try {
    296                         BitmapFactory.Options options = new BitmapFactory.Options();
    297                         Bitmap bm = BitmapFactory.decodeFileDescriptor(
    298                                 fd.getFileDescriptor(), null, options);
    299                         return generateBitmap(context, bm, width, height);
    300                     } catch (OutOfMemoryError e) {
    301                         Log.w(TAG, "Can't decode file", e);
    302                     } finally {
    303                         try {
    304                             fd.close();
    305                         } catch (IOException e) {
    306                             // Ignore
    307                         }
    308                     }
    309                 }
    310             } catch (RemoteException e) {
    311                 // Ignore
    312             }
    313             return null;
    314         }
    315 
    316         private Bitmap getDefaultWallpaperLocked(Context context) {
    317             try {
    318                 InputStream is = context.getResources().openRawResource(
    319                         com.android.internal.R.drawable.default_wallpaper);
    320                 if (is != null) {
    321                     int width = mService.getWidthHint();
    322                     int height = mService.getHeightHint();
    323 
    324                     try {
    325                         BitmapFactory.Options options = new BitmapFactory.Options();
    326                         Bitmap bm = BitmapFactory.decodeStream(is, null, options);
    327                         return generateBitmap(context, bm, width, height);
    328                     } catch (OutOfMemoryError e) {
    329                         Log.w(TAG, "Can't decode stream", e);
    330                     } finally {
    331                         try {
    332                             is.close();
    333                         } catch (IOException e) {
    334                             // Ignore
    335                         }
    336                     }
    337                 }
    338             } catch (RemoteException e) {
    339                 // Ignore
    340             }
    341             return null;
    342         }
    343     }
    344 
    345     private static final Object sSync = new Object[0];
    346     private static Globals sGlobals;
    347 
    348     static void initGlobals(Looper looper) {
    349         synchronized (sSync) {
    350             if (sGlobals == null) {
    351                 sGlobals = new Globals(looper);
    352             }
    353         }
    354     }
    355 
    356     /*package*/ WallpaperManager(Context context, Handler handler) {
    357         mContext = context;
    358         initGlobals(context.getMainLooper());
    359     }
    360 
    361     /**
    362      * Retrieve a WallpaperManager associated with the given Context.
    363      */
    364     public static WallpaperManager getInstance(Context context) {
    365         return (WallpaperManager)context.getSystemService(
    366                 Context.WALLPAPER_SERVICE);
    367     }
    368 
    369     /** @hide */
    370     public IWallpaperManager getIWallpaperManager() {
    371         return sGlobals.mService;
    372     }
    373 
    374     /**
    375      * Retrieve the current system wallpaper; if
    376      * no wallpaper is set, the system built-in static wallpaper is returned.
    377      * This is returned as an
    378      * abstract Drawable that you can install in a View to display whatever
    379      * wallpaper the user has currently set.
    380      *
    381      * @return Returns a Drawable object that will draw the wallpaper.
    382      */
    383     public Drawable getDrawable() {
    384         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
    385         if (bm != null) {
    386             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
    387             dr.setDither(false);
    388             return dr;
    389         }
    390         return null;
    391     }
    392 
    393     /**
    394      * Returns a drawable for the system built-in static wallpaper .
    395      *
    396      */
    397     public Drawable getBuiltInDrawable() {
    398         return getBuiltInDrawable(0, 0, false, 0, 0);
    399     }
    400 
    401     /**
    402      * Returns a drawable for the system built-in static wallpaper. Based on the parameters, the
    403      * drawable can be cropped and scaled
    404      *
    405      * @param outWidth The width of the returned drawable
    406      * @param outWidth The height of the returned drawable
    407      * @param scaleToFit If true, scale the wallpaper down rather than just cropping it
    408      * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image;
    409      *        0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned
    410      * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image;
    411      *        0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned
    412      *
    413      */
    414     public Drawable getBuiltInDrawable(int outWidth, int outHeight,
    415             boolean scaleToFit, float horizontalAlignment, float verticalAlignment) {
    416         if (sGlobals.mService == null) {
    417             Log.w(TAG, "WallpaperService not running");
    418             return null;
    419         }
    420         Resources resources = mContext.getResources();
    421         horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
    422         verticalAlignment = Math.max(0, Math.min(1, verticalAlignment));
    423 
    424         InputStream is = new BufferedInputStream(
    425                 resources.openRawResource(com.android.internal.R.drawable.default_wallpaper));
    426 
    427         if (is == null) {
    428             Log.e(TAG, "default wallpaper input stream is null");
    429             return null;
    430         } else {
    431             if (outWidth <= 0 || outHeight <= 0) {
    432                 Bitmap fullSize = BitmapFactory.decodeStream(is, null, null);
    433                 return new BitmapDrawable(resources, fullSize);
    434             } else {
    435                 int inWidth;
    436                 int inHeight;
    437                 {
    438                     BitmapFactory.Options options = new BitmapFactory.Options();
    439                     options.inJustDecodeBounds = true;
    440                     BitmapFactory.decodeStream(is, null, options);
    441                     if (options.outWidth != 0 && options.outHeight != 0) {
    442                         inWidth = options.outWidth;
    443                         inHeight = options.outHeight;
    444                     } else {
    445                         Log.e(TAG, "default wallpaper dimensions are 0");
    446                         return null;
    447                     }
    448                 }
    449 
    450                 is = new BufferedInputStream(resources.openRawResource(
    451                         com.android.internal.R.drawable.default_wallpaper));
    452 
    453                 RectF cropRectF;
    454 
    455                 outWidth = Math.min(inWidth, outWidth);
    456                 outHeight = Math.min(inHeight, outHeight);
    457                 if (scaleToFit) {
    458                     cropRectF = getMaxCropRect(inWidth, inHeight, outWidth, outHeight,
    459                         horizontalAlignment, verticalAlignment);
    460                 } else {
    461                     float left = (inWidth - outWidth) * horizontalAlignment;
    462                     float right = left + outWidth;
    463                     float top = (inHeight - outHeight) * verticalAlignment;
    464                     float bottom = top + outHeight;
    465                     cropRectF = new RectF(left, top, right, bottom);
    466                 }
    467                 Rect roundedTrueCrop = new Rect();
    468                 cropRectF.roundOut(roundedTrueCrop);
    469 
    470                 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
    471                     Log.w(TAG, "crop has bad values for full size image");
    472                     return null;
    473                 }
    474 
    475                 // See how much we're reducing the size of the image
    476                 int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / outWidth,
    477                         roundedTrueCrop.height() / outHeight);
    478 
    479                 // Attempt to open a region decoder
    480                 BitmapRegionDecoder decoder = null;
    481                 try {
    482                     decoder = BitmapRegionDecoder.newInstance(is, true);
    483                 } catch (IOException e) {
    484                     Log.w(TAG, "cannot open region decoder for default wallpaper");
    485                 }
    486 
    487                 Bitmap crop = null;
    488                 if (decoder != null) {
    489                     // Do region decoding to get crop bitmap
    490                     BitmapFactory.Options options = new BitmapFactory.Options();
    491                     if (scaleDownSampleSize > 1) {
    492                         options.inSampleSize = scaleDownSampleSize;
    493                     }
    494                     crop = decoder.decodeRegion(roundedTrueCrop, options);
    495                     decoder.recycle();
    496                 }
    497 
    498                 if (crop == null) {
    499                     // BitmapRegionDecoder has failed, try to crop in-memory
    500                     is = new BufferedInputStream(resources.openRawResource(
    501                             com.android.internal.R.drawable.default_wallpaper));
    502                     Bitmap fullSize = null;
    503                     if (is != null) {
    504                         BitmapFactory.Options options = new BitmapFactory.Options();
    505                         if (scaleDownSampleSize > 1) {
    506                             options.inSampleSize = scaleDownSampleSize;
    507                         }
    508                         fullSize = BitmapFactory.decodeStream(is, null, options);
    509                     }
    510                     if (fullSize != null) {
    511                         crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
    512                                 roundedTrueCrop.top, roundedTrueCrop.width(),
    513                                 roundedTrueCrop.height());
    514                     }
    515                 }
    516 
    517                 if (crop == null) {
    518                     Log.w(TAG, "cannot decode default wallpaper");
    519                     return null;
    520                 }
    521 
    522                 // Scale down if necessary
    523                 if (outWidth > 0 && outHeight > 0 &&
    524                         (crop.getWidth() != outWidth || crop.getHeight() != outHeight)) {
    525                     Matrix m = new Matrix();
    526                     RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
    527                     RectF returnRect = new RectF(0, 0, outWidth, outHeight);
    528                     m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    529                     Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
    530                             (int) returnRect.height(), Bitmap.Config.ARGB_8888);
    531                     if (tmp != null) {
    532                         Canvas c = new Canvas(tmp);
    533                         Paint p = new Paint();
    534                         p.setFilterBitmap(true);
    535                         c.drawBitmap(crop, m, p);
    536                         crop = tmp;
    537                     }
    538                 }
    539 
    540                 return new BitmapDrawable(resources, crop);
    541             }
    542         }
    543     }
    544 
    545     private static RectF getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight,
    546                 float horizontalAlignment, float verticalAlignment) {
    547         RectF cropRect = new RectF();
    548         // Get a crop rect that will fit this
    549         if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
    550              cropRect.top = 0;
    551              cropRect.bottom = inHeight;
    552              float cropWidth = outWidth * (inHeight / (float) outHeight);
    553              cropRect.left = (inWidth - cropWidth) * horizontalAlignment;
    554              cropRect.right = cropRect.left + cropWidth;
    555         } else {
    556             cropRect.left = 0;
    557             cropRect.right = inWidth;
    558             float cropHeight = outHeight * (inWidth / (float) outWidth);
    559             cropRect.top = (inHeight - cropHeight) * verticalAlignment;
    560             cropRect.bottom = cropRect.top + cropHeight;
    561         }
    562         return cropRect;
    563     }
    564 
    565     /**
    566      * Retrieve the current system wallpaper; if there is no wallpaper set,
    567      * a null pointer is returned. This is returned as an
    568      * abstract Drawable that you can install in a View to display whatever
    569      * wallpaper the user has currently set.
    570      *
    571      * @return Returns a Drawable object that will draw the wallpaper or a
    572      * null pointer if these is none.
    573      */
    574     public Drawable peekDrawable() {
    575         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
    576         if (bm != null) {
    577             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
    578             dr.setDither(false);
    579             return dr;
    580         }
    581         return null;
    582     }
    583 
    584     /**
    585      * Like {@link #getDrawable()}, but the returned Drawable has a number
    586      * of limitations to reduce its overhead as much as possible. It will
    587      * never scale the wallpaper (only centering it if the requested bounds
    588      * do match the bitmap bounds, which should not be typical), doesn't
    589      * allow setting an alpha, color filter, or other attributes, etc.  The
    590      * bounds of the returned drawable will be initialized to the same bounds
    591      * as the wallpaper, so normally you will not need to touch it.  The
    592      * drawable also assumes that it will be used in a context running in
    593      * the same density as the screen (not in density compatibility mode).
    594      *
    595      * @return Returns a Drawable object that will draw the wallpaper.
    596      */
    597     public Drawable getFastDrawable() {
    598         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);
    599         if (bm != null) {
    600             return new FastBitmapDrawable(bm);
    601         }
    602         return null;
    603     }
    604 
    605     /**
    606      * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
    607      * a null pointer is returned.
    608      *
    609      * @return Returns an optimized Drawable object that will draw the
    610      * wallpaper or a null pointer if these is none.
    611      */
    612     public Drawable peekFastDrawable() {
    613         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
    614         if (bm != null) {
    615             return new FastBitmapDrawable(bm);
    616         }
    617         return null;
    618     }
    619 
    620     /**
    621      * Like {@link #getDrawable()} but returns a Bitmap.
    622      *
    623      * @hide
    624      */
    625     public Bitmap getBitmap() {
    626         return sGlobals.peekWallpaperBitmap(mContext, true);
    627     }
    628 
    629     /**
    630      * Remove all internal references to the last loaded wallpaper.  Useful
    631      * for apps that want to reduce memory usage when they only temporarily
    632      * need to have the wallpaper.  After calling, the next request for the
    633      * wallpaper will require reloading it again from disk.
    634      */
    635     public void forgetLoadedWallpaper() {
    636         sGlobals.forgetLoadedWallpaper();
    637     }
    638 
    639     /**
    640      * If the current wallpaper is a live wallpaper component, return the
    641      * information about that wallpaper.  Otherwise, if it is a static image,
    642      * simply return null.
    643      */
    644     public WallpaperInfo getWallpaperInfo() {
    645         try {
    646             if (sGlobals.mService == null) {
    647                 Log.w(TAG, "WallpaperService not running");
    648                 return null;
    649             } else {
    650                 return sGlobals.mService.getWallpaperInfo();
    651             }
    652         } catch (RemoteException e) {
    653             return null;
    654         }
    655     }
    656 
    657     /**
    658      * Gets an Intent that will launch an activity that crops the given
    659      * image and sets the device's wallpaper. If there is a default HOME activity
    660      * that supports cropping wallpapers, it will be preferred as the default.
    661      * Use this method instead of directly creating a {@link #ACTION_CROP_AND_SET_WALLPAPER}
    662      * intent.
    663      *
    664      * @param imageUri The image URI that will be set in the intent. The must be a content
    665      *                 URI and its provider must resolve its type to "image/*"
    666      *
    667      * @throws IllegalArgumentException if the URI is not a content URI or its MIME type is
    668      *         not "image/*"
    669      */
    670     public Intent getCropAndSetWallpaperIntent(Uri imageUri) {
    671         if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())) {
    672             throw new IllegalArgumentException("Image URI must be of the "
    673                     + ContentResolver.SCHEME_CONTENT + " scheme type");
    674         }
    675 
    676         final PackageManager packageManager = mContext.getPackageManager();
    677         Intent cropAndSetWallpaperIntent =
    678                 new Intent(ACTION_CROP_AND_SET_WALLPAPER, imageUri);
    679         cropAndSetWallpaperIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    680 
    681         // Find out if the default HOME activity supports CROP_AND_SET_WALLPAPER
    682         Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
    683         ResolveInfo resolvedHome = packageManager.resolveActivity(homeIntent,
    684                 PackageManager.MATCH_DEFAULT_ONLY);
    685         if (resolvedHome != null) {
    686             cropAndSetWallpaperIntent.setPackage(resolvedHome.activityInfo.packageName);
    687 
    688             List<ResolveInfo> cropAppList = packageManager.queryIntentActivities(
    689                     cropAndSetWallpaperIntent, 0);
    690             if (cropAppList.size() > 0) {
    691                 return cropAndSetWallpaperIntent;
    692             }
    693         }
    694 
    695         // fallback crop activity
    696         cropAndSetWallpaperIntent.setPackage("com.android.wallpapercropper");
    697         List<ResolveInfo> cropAppList = packageManager.queryIntentActivities(
    698                 cropAndSetWallpaperIntent, 0);
    699         if (cropAppList.size() > 0) {
    700             return cropAndSetWallpaperIntent;
    701         }
    702         // If the URI is not of the right type, or for some reason the system wallpaper
    703         // cropper doesn't exist, return null
    704         throw new IllegalArgumentException("Cannot use passed URI to set wallpaper; " +
    705             "check that the type returned by ContentProvider matches image/*");
    706     }
    707 
    708     /**
    709      * Change the current system wallpaper to the bitmap in the given resource.
    710      * The resource is opened as a raw data stream and copied into the
    711      * wallpaper; it must be a valid PNG or JPEG image.  On success, the intent
    712      * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
    713      *
    714      * <p>This method requires the caller to hold the permission
    715      * {@link android.Manifest.permission#SET_WALLPAPER}.
    716      *
    717      * @param resid The bitmap to save.
    718      *
    719      * @throws IOException If an error occurs reverting to the built-in
    720      * wallpaper.
    721      */
    722     public void setResource(int resid) throws IOException {
    723         if (sGlobals.mService == null) {
    724             Log.w(TAG, "WallpaperService not running");
    725             return;
    726         }
    727         try {
    728             Resources resources = mContext.getResources();
    729             /* Set the wallpaper to the default values */
    730             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
    731                     "res:" + resources.getResourceName(resid));
    732             if (fd != null) {
    733                 FileOutputStream fos = null;
    734                 try {
    735                     fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
    736                     setWallpaper(resources.openRawResource(resid), fos);
    737                 } finally {
    738                     if (fos != null) {
    739                         fos.close();
    740                     }
    741                 }
    742             }
    743         } catch (RemoteException e) {
    744             // Ignore
    745         }
    746     }
    747 
    748     /**
    749      * Change the current system wallpaper to a bitmap.  The given bitmap is
    750      * converted to a PNG and stored as the wallpaper.  On success, the intent
    751      * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
    752      *
    753      * <p>This method requires the caller to hold the permission
    754      * {@link android.Manifest.permission#SET_WALLPAPER}.
    755      *
    756      * @param bitmap The bitmap to save.
    757      *
    758      * @throws IOException If an error occurs reverting to the built-in
    759      * wallpaper.
    760      */
    761     public void setBitmap(Bitmap bitmap) throws IOException {
    762         if (sGlobals.mService == null) {
    763             Log.w(TAG, "WallpaperService not running");
    764             return;
    765         }
    766         try {
    767             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
    768             if (fd == null) {
    769                 return;
    770             }
    771             FileOutputStream fos = null;
    772             try {
    773                 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
    774                 bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
    775             } finally {
    776                 if (fos != null) {
    777                     fos.close();
    778                 }
    779             }
    780         } catch (RemoteException e) {
    781             // Ignore
    782         }
    783     }
    784 
    785     /**
    786      * Change the current system wallpaper to a specific byte stream.  The
    787      * give InputStream is copied into persistent storage and will now be
    788      * used as the wallpaper.  Currently it must be either a JPEG or PNG
    789      * image.  On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
    790      * is broadcast.
    791      *
    792      * <p>This method requires the caller to hold the permission
    793      * {@link android.Manifest.permission#SET_WALLPAPER}.
    794      *
    795      * @param data A stream containing the raw data to install as a wallpaper.
    796      *
    797      * @throws IOException If an error occurs reverting to the built-in
    798      * wallpaper.
    799      */
    800     public void setStream(InputStream data) throws IOException {
    801         if (sGlobals.mService == null) {
    802             Log.w(TAG, "WallpaperService not running");
    803             return;
    804         }
    805         try {
    806             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
    807             if (fd == null) {
    808                 return;
    809             }
    810             FileOutputStream fos = null;
    811             try {
    812                 fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
    813                 setWallpaper(data, fos);
    814             } finally {
    815                 if (fos != null) {
    816                     fos.close();
    817                 }
    818             }
    819         } catch (RemoteException e) {
    820             // Ignore
    821         }
    822     }
    823 
    824     private void setWallpaper(InputStream data, FileOutputStream fos)
    825             throws IOException {
    826         byte[] buffer = new byte[32768];
    827         int amt;
    828         while ((amt=data.read(buffer)) > 0) {
    829             fos.write(buffer, 0, amt);
    830         }
    831     }
    832 
    833     /**
    834      * Return whether any users are currently set to use the wallpaper
    835      * with the given resource ID.  That is, their wallpaper has been
    836      * set through {@link #setResource(int)} with the same resource id.
    837      */
    838     public boolean hasResourceWallpaper(int resid) {
    839         if (sGlobals.mService == null) {
    840             Log.w(TAG, "WallpaperService not running");
    841             return false;
    842         }
    843         try {
    844             Resources resources = mContext.getResources();
    845             String name = "res:" + resources.getResourceName(resid);
    846             return sGlobals.mService.hasNamedWallpaper(name);
    847         } catch (RemoteException e) {
    848             return false;
    849         }
    850     }
    851 
    852     /**
    853      * Returns the desired minimum width for the wallpaper. Callers of
    854      * {@link #setBitmap(android.graphics.Bitmap)} or
    855      * {@link #setStream(java.io.InputStream)} should check this value
    856      * beforehand to make sure the supplied wallpaper respects the desired
    857      * minimum width.
    858      *
    859      * If the returned value is <= 0, the caller should use the width of
    860      * the default display instead.
    861      *
    862      * @return The desired minimum width for the wallpaper. This value should
    863      * be honored by applications that set the wallpaper but it is not
    864      * mandatory.
    865      */
    866     public int getDesiredMinimumWidth() {
    867         if (sGlobals.mService == null) {
    868             Log.w(TAG, "WallpaperService not running");
    869             return 0;
    870         }
    871         try {
    872             return sGlobals.mService.getWidthHint();
    873         } catch (RemoteException e) {
    874             // Shouldn't happen!
    875             return 0;
    876         }
    877     }
    878 
    879     /**
    880      * Returns the desired minimum height for the wallpaper. Callers of
    881      * {@link #setBitmap(android.graphics.Bitmap)} or
    882      * {@link #setStream(java.io.InputStream)} should check this value
    883      * beforehand to make sure the supplied wallpaper respects the desired
    884      * minimum height.
    885      *
    886      * If the returned value is <= 0, the caller should use the height of
    887      * the default display instead.
    888      *
    889      * @return The desired minimum height for the wallpaper. This value should
    890      * be honored by applications that set the wallpaper but it is not
    891      * mandatory.
    892      */
    893     public int getDesiredMinimumHeight() {
    894         if (sGlobals.mService == null) {
    895             Log.w(TAG, "WallpaperService not running");
    896             return 0;
    897         }
    898         try {
    899             return sGlobals.mService.getHeightHint();
    900         } catch (RemoteException e) {
    901             // Shouldn't happen!
    902             return 0;
    903         }
    904     }
    905 
    906     /**
    907      * For use only by the current home application, to specify the size of
    908      * wallpaper it would like to use.  This allows such applications to have
    909      * a virtual wallpaper that is larger than the physical screen, matching
    910      * the size of their workspace.
    911      *
    912      * <p>Note developers, who don't seem to be reading this.  This is
    913      * for <em>home screens</em> to tell what size wallpaper they would like.
    914      * Nobody else should be calling this!  Certainly not other non-home-screen
    915      * apps that change the wallpaper.  Those apps are supposed to
    916      * <b>retrieve</b> the suggested size so they can construct a wallpaper
    917      * that matches it.
    918      *
    919      * <p>This method requires the caller to hold the permission
    920      * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}.
    921      *
    922      * @param minimumWidth Desired minimum width
    923      * @param minimumHeight Desired minimum height
    924      */
    925     public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
    926         try {
    927             if (sGlobals.mService == null) {
    928                 Log.w(TAG, "WallpaperService not running");
    929             } else {
    930                 sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
    931             }
    932         } catch (RemoteException e) {
    933             // Ignore
    934         }
    935     }
    936 
    937     /**
    938      * Set the position of the current wallpaper within any larger space, when
    939      * that wallpaper is visible behind the given window.  The X and Y offsets
    940      * are floating point numbers ranging from 0 to 1, representing where the
    941      * wallpaper should be positioned within the screen space.  These only
    942      * make sense when the wallpaper is larger than the screen.
    943      *
    944      * @param windowToken The window who these offsets should be associated
    945      * with, as returned by {@link android.view.View#getWindowToken()
    946      * View.getWindowToken()}.
    947      * @param xOffset The offset along the X dimension, from 0 to 1.
    948      * @param yOffset The offset along the Y dimension, from 0 to 1.
    949      */
    950     public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
    951         try {
    952             //Log.v(TAG, "Sending new wallpaper offsets from app...");
    953             WindowManagerGlobal.getWindowSession().setWallpaperPosition(
    954                     windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
    955             //Log.v(TAG, "...app returning after sending offsets!");
    956         } catch (RemoteException e) {
    957             // Ignore.
    958         }
    959     }
    960 
    961     /**
    962      * For applications that use multiple virtual screens showing a wallpaper,
    963      * specify the step size between virtual screens. For example, if the
    964      * launcher has 3 virtual screens, it would specify an xStep of 0.5,
    965      * since the X offset for those screens are 0.0, 0.5 and 1.0
    966      * @param xStep The X offset delta from one screen to the next one
    967      * @param yStep The Y offset delta from one screen to the next one
    968      */
    969     public void setWallpaperOffsetSteps(float xStep, float yStep) {
    970         mWallpaperXStep = xStep;
    971         mWallpaperYStep = yStep;
    972     }
    973 
    974     /**
    975      * Send an arbitrary command to the current active wallpaper.
    976      *
    977      * @param windowToken The window who these offsets should be associated
    978      * with, as returned by {@link android.view.View#getWindowToken()
    979      * View.getWindowToken()}.
    980      * @param action Name of the command to perform.  This must be a scoped
    981      * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT".
    982      * @param x Arbitrary integer argument based on command.
    983      * @param y Arbitrary integer argument based on command.
    984      * @param z Arbitrary integer argument based on command.
    985      * @param extras Optional additional information for the command, or null.
    986      */
    987     public void sendWallpaperCommand(IBinder windowToken, String action,
    988             int x, int y, int z, Bundle extras) {
    989         try {
    990             //Log.v(TAG, "Sending new wallpaper offsets from app...");
    991             WindowManagerGlobal.getWindowSession().sendWallpaperCommand(
    992                     windowToken, action, x, y, z, extras, false);
    993             //Log.v(TAG, "...app returning after sending offsets!");
    994         } catch (RemoteException e) {
    995             // Ignore.
    996         }
    997     }
    998 
    999     /**
   1000      * Clear the offsets previously associated with this window through
   1001      * {@link #setWallpaperOffsets(IBinder, float, float)}.  This reverts
   1002      * the window to its default state, where it does not cause the wallpaper
   1003      * to scroll from whatever its last offsets were.
   1004      *
   1005      * @param windowToken The window who these offsets should be associated
   1006      * with, as returned by {@link android.view.View#getWindowToken()
   1007      * View.getWindowToken()}.
   1008      */
   1009     public void clearWallpaperOffsets(IBinder windowToken) {
   1010         try {
   1011             WindowManagerGlobal.getWindowSession().setWallpaperPosition(
   1012                     windowToken, -1, -1, -1, -1);
   1013         } catch (RemoteException e) {
   1014             // Ignore.
   1015         }
   1016     }
   1017 
   1018     /**
   1019      * Remove any currently set wallpaper, reverting to the system's built-in
   1020      * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
   1021      * is broadcast.
   1022      *
   1023      * <p>This method requires the caller to hold the permission
   1024      * {@link android.Manifest.permission#SET_WALLPAPER}.
   1025      *
   1026      * @throws IOException If an error occurs reverting to the built-in
   1027      * wallpaper.
   1028      */
   1029     public void clear() throws IOException {
   1030         setResource(com.android.internal.R.drawable.default_wallpaper);
   1031     }
   1032 
   1033     static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) {
   1034         if (bm == null) {
   1035             return null;
   1036         }
   1037 
   1038         WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
   1039         DisplayMetrics metrics = new DisplayMetrics();
   1040         wm.getDefaultDisplay().getMetrics(metrics);
   1041         bm.setDensity(metrics.noncompatDensityDpi);
   1042 
   1043         if (width <= 0 || height <= 0
   1044                 || (bm.getWidth() == width && bm.getHeight() == height)) {
   1045             return bm;
   1046         }
   1047 
   1048         // This is the final bitmap we want to return.
   1049         try {
   1050             Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
   1051             newbm.setDensity(metrics.noncompatDensityDpi);
   1052 
   1053             Canvas c = new Canvas(newbm);
   1054             Rect targetRect = new Rect();
   1055             targetRect.right = bm.getWidth();
   1056             targetRect.bottom = bm.getHeight();
   1057 
   1058             int deltaw = width - targetRect.right;
   1059             int deltah = height - targetRect.bottom;
   1060 
   1061             if (deltaw > 0 || deltah > 0) {
   1062                 // We need to scale up so it covers the entire area.
   1063                 float scale;
   1064                 if (deltaw > deltah) {
   1065                     scale = width / (float)targetRect.right;
   1066                 } else {
   1067                     scale = height / (float)targetRect.bottom;
   1068                 }
   1069                 targetRect.right = (int)(targetRect.right*scale);
   1070                 targetRect.bottom = (int)(targetRect.bottom*scale);
   1071                 deltaw = width - targetRect.right;
   1072                 deltah = height - targetRect.bottom;
   1073             }
   1074 
   1075             targetRect.offset(deltaw/2, deltah/2);
   1076 
   1077             Paint paint = new Paint();
   1078             paint.setFilterBitmap(true);
   1079             paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
   1080             c.drawBitmap(bm, null, targetRect, paint);
   1081 
   1082             bm.recycle();
   1083             c.setBitmap(null);
   1084             return newbm;
   1085         } catch (OutOfMemoryError e) {
   1086             Log.w(TAG, "Can't generate default bitmap", e);
   1087             return bm;
   1088         }
   1089     }
   1090 }
   1091