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