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