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