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