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 
FastBitmapDrawable(Bitmap bitmap)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
draw(Canvas canvas)169         public void draw(Canvas canvas) {
170             canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, mPaint);
171         }
172 
173         @Override
getOpacity()174         public int getOpacity() {
175             return PixelFormat.OPAQUE;
176         }
177 
178         @Override
setBounds(int left, int top, int right, int bottom)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
setAlpha(int alpha)185         public void setAlpha(int alpha) {
186             throw new UnsupportedOperationException("Not supported with this drawable");
187         }
188 
189         @Override
setColorFilter(ColorFilter cf)190         public void setColorFilter(ColorFilter cf) {
191             throw new UnsupportedOperationException("Not supported with this drawable");
192         }
193 
194         @Override
setDither(boolean dither)195         public void setDither(boolean dither) {
196             throw new UnsupportedOperationException("Not supported with this drawable");
197         }
198 
199         @Override
setFilterBitmap(boolean filter)200         public void setFilterBitmap(boolean filter) {
201             throw new UnsupportedOperationException("Not supported with this drawable");
202         }
203 
204         @Override
getIntrinsicWidth()205         public int getIntrinsicWidth() {
206             return mWidth;
207         }
208 
209         @Override
getIntrinsicHeight()210         public int getIntrinsicHeight() {
211             return mHeight;
212         }
213 
214         @Override
getMinimumWidth()215         public int getMinimumWidth() {
216             return mWidth;
217         }
218 
219         @Override
getMinimumHeight()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 
Globals(Looper looper)232         Globals(Looper looper) {
233             IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
234             mService = IWallpaperManager.Stub.asInterface(b);
235         }
236 
onWallpaperChanged()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 
peekWallpaperBitmap(Context context, boolean returnDefault)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 
forgetLoadedWallpaper()275         public void forgetLoadedWallpaper() {
276             synchronized (this) {
277                 mWallpaper = null;
278                 mDefaultWallpaper = null;
279             }
280         }
281 
getCurrentWallpaperLocked(Context context)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 
getDefaultWallpaperLocked(Context context)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 
initGlobals(Looper looper)335     static void initGlobals(Looper looper) {
336         synchronized (sSync) {
337             if (sGlobals == null) {
338                 sGlobals = new Globals(looper);
339             }
340         }
341     }
342 
WallpaperManager(Context context, Handler handler)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      */
getInstance(Context context)351     public static WallpaperManager getInstance(Context context) {
352         return (WallpaperManager)context.getSystemService(
353                 Context.WALLPAPER_SERVICE);
354     }
355 
356     /** @hide */
getIWallpaperManager()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      */
getDrawable()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      */
getBuiltInDrawable()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      */
getBuiltInDrawable(int outWidth, int outHeight, boolean scaleToFit, float horizontalAlignment, float verticalAlignment)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 
getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight, float horizontalAlignment, float verticalAlignment)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      */
peekDrawable()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      */
getFastDrawable()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      */
peekFastDrawable()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      */
getBitmap()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      */
forgetLoadedWallpaper()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      */
getWallpaperInfo()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      */
getCropAndSetWallpaperIntent(Uri imageUri)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      */
setResource(int resid)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      */
setBitmap(Bitmap bitmap)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      */
setStream(InputStream data)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 
setWallpaper(InputStream data, FileOutputStream fos)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      */
hasResourceWallpaper(int resid)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      */
getDesiredMinimumWidth()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      */
getDesiredMinimumHeight()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      */
suggestDesiredDimensions(int minimumWidth, int minimumHeight)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
setDisplayPadding(Rect padding)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
setDisplayOffset(IBinder windowToken, int x, int y)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      */
setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset)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      */
setWallpaperOffsetSteps(float xStep, float yStep)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      */
sendWallpaperCommand(IBinder windowToken, String action, int x, int y, int z, Bundle extras)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      */
clearWallpaperOffsets(IBinder windowToken)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      */
clear()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      */
openDefaultWallpaper(Context context)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      */
getDefaultWallpaperComponent(Context context)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