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.IntDef;
20import android.annotation.RawRes;
21import android.annotation.SystemApi;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.content.res.Resources;
29import android.content.res.Resources.NotFoundException;
30import android.graphics.Bitmap;
31import android.graphics.BitmapFactory;
32import android.graphics.BitmapRegionDecoder;
33import android.graphics.Canvas;
34import android.graphics.ColorFilter;
35import android.graphics.Matrix;
36import android.graphics.Paint;
37import android.graphics.PixelFormat;
38import android.graphics.PorterDuff;
39import android.graphics.PorterDuffXfermode;
40import android.graphics.Rect;
41import android.graphics.RectF;
42import android.graphics.drawable.BitmapDrawable;
43import android.graphics.drawable.Drawable;
44import android.net.Uri;
45import android.os.Bundle;
46import android.os.DeadSystemException;
47import android.os.Handler;
48import android.os.IBinder;
49import android.os.Looper;
50import android.os.ParcelFileDescriptor;
51import android.os.Process;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.SystemProperties;
55import android.os.UserHandle;
56import android.text.TextUtils;
57import android.util.Log;
58import android.view.WindowManagerGlobal;
59
60import libcore.io.IoUtils;
61
62import java.io.BufferedInputStream;
63import java.io.File;
64import java.io.FileInputStream;
65import java.io.FileOutputStream;
66import java.io.IOException;
67import java.io.InputStream;
68import java.lang.annotation.Retention;
69import java.lang.annotation.RetentionPolicy;
70import java.util.List;
71import java.util.concurrent.CountDownLatch;
72import java.util.concurrent.TimeUnit;
73
74/**
75 * Provides access to the system wallpaper. With WallpaperManager, you can
76 * get the current wallpaper, get the desired dimensions for the wallpaper, set
77 * the wallpaper, and more. Get an instance of WallpaperManager with
78 * {@link #getInstance(android.content.Context) getInstance()}.
79 *
80 * <p> An app can check whether wallpapers are supported for the current user, by calling
81 * {@link #isWallpaperSupported()}, and whether setting of wallpapers is allowed, by calling
82 * {@link #isSetWallpaperAllowed()}.
83 */
84public class WallpaperManager {
85    private static String TAG = "WallpaperManager";
86    private static boolean DEBUG = false;
87    private float mWallpaperXStep = -1;
88    private float mWallpaperYStep = -1;
89
90    /** {@hide} */
91    private static final String PROP_WALLPAPER = "ro.config.wallpaper";
92    /** {@hide} */
93    private static final String PROP_LOCK_WALLPAPER = "ro.config.lock_wallpaper";
94    /** {@hide} */
95    private static final String PROP_WALLPAPER_COMPONENT = "ro.config.wallpaper_component";
96
97    /**
98     * Activity Action: Show settings for choosing wallpaper. Do not use directly to construct
99     * an intent; instead, use {@link #getCropAndSetWallpaperIntent}.
100     * <p>Input:  {@link Intent#getData} is the URI of the image to crop and set as wallpaper.
101     * <p>Output: RESULT_OK if user decided to crop/set the wallpaper, RESULT_CANCEL otherwise
102     * Activities that support this intent should specify a MIME filter of "image/*"
103     */
104    public static final String ACTION_CROP_AND_SET_WALLPAPER =
105            "android.service.wallpaper.CROP_AND_SET_WALLPAPER";
106
107    /**
108     * Launch an activity for the user to pick the current global live
109     * wallpaper.
110     */
111    public static final String ACTION_LIVE_WALLPAPER_CHOOSER
112            = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER";
113
114    /**
115     * Directly launch live wallpaper preview, allowing the user to immediately
116     * confirm to switch to a specific live wallpaper.  You must specify
117     * {@link #EXTRA_LIVE_WALLPAPER_COMPONENT} with the ComponentName of
118     * a live wallpaper component that is to be shown.
119     */
120    public static final String ACTION_CHANGE_LIVE_WALLPAPER
121            = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER";
122
123    /**
124     * Extra in {@link #ACTION_CHANGE_LIVE_WALLPAPER} that specifies the
125     * ComponentName of a live wallpaper that should be shown as a preview,
126     * for the user to confirm.
127     */
128    public static final String EXTRA_LIVE_WALLPAPER_COMPONENT
129            = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT";
130
131    /**
132     * Manifest entry for activities that respond to {@link Intent#ACTION_SET_WALLPAPER}
133     * which allows them to provide a custom large icon associated with this action.
134     */
135    public static final String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
136
137    /**
138     * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
139     * host when the user taps on an empty area (not performing an action
140     * in the host).  The x and y arguments are the location of the tap in
141     * screen coordinates.
142     */
143    public static final String COMMAND_TAP = "android.wallpaper.tap";
144
145    /**
146     * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
147     * host when the user releases a secondary pointer on an empty area
148     * (not performing an action in the host).  The x and y arguments are
149     * the location of the secondary tap in screen coordinates.
150     */
151    public static final String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap";
152
153    /**
154     * Command for {@link #sendWallpaperCommand}: reported by the wallpaper
155     * host when the user drops an object into an area of the host.  The x
156     * and y arguments are the location of the drop.
157     */
158    public static final String COMMAND_DROP = "android.home.drop";
159
160    /**
161     * Extra passed back from setWallpaper() giving the new wallpaper's assigned ID.
162     * @hide
163     */
164    public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID";
165
166    // flags for which kind of wallpaper to act on
167
168    /** @hide */
169    @IntDef(flag = true, value = {
170            FLAG_SYSTEM,
171            FLAG_LOCK
172    })
173    @Retention(RetentionPolicy.SOURCE)
174    public @interface SetWallpaperFlags {}
175
176    /**
177     * Flag: set or retrieve the general system wallpaper.
178     */
179    public static final int FLAG_SYSTEM = 1 << 0;
180
181    /**
182     * Flag: set or retrieve the lock-screen-specific wallpaper.
183     */
184    public static final int FLAG_LOCK = 1 << 1;
185
186    private final Context mContext;
187
188    /**
189     * Special drawable that draws a wallpaper as fast as possible.  Assumes
190     * no scaling or placement off (0,0) of the wallpaper (this should be done
191     * at the time the bitmap is loaded).
192     */
193    static class FastBitmapDrawable extends Drawable {
194        private final Bitmap mBitmap;
195        private final int mWidth;
196        private final int mHeight;
197        private int mDrawLeft;
198        private int mDrawTop;
199        private final Paint mPaint;
200
201        private FastBitmapDrawable(Bitmap bitmap) {
202            mBitmap = bitmap;
203            mWidth = bitmap.getWidth();
204            mHeight = bitmap.getHeight();
205
206            setBounds(0, 0, mWidth, mHeight);
207
208            mPaint = new Paint();
209            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
210        }
211
212        @Override
213        public void draw(Canvas canvas) {
214            canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, mPaint);
215        }
216
217        @Override
218        public int getOpacity() {
219            return PixelFormat.OPAQUE;
220        }
221
222        @Override
223        public void setBounds(int left, int top, int right, int bottom) {
224            mDrawLeft = left + (right-left - mWidth) / 2;
225            mDrawTop = top + (bottom-top - mHeight) / 2;
226        }
227
228        @Override
229        public void setAlpha(int alpha) {
230            throw new UnsupportedOperationException("Not supported with this drawable");
231        }
232
233        @Override
234        public void setColorFilter(ColorFilter colorFilter) {
235            throw new UnsupportedOperationException("Not supported with this drawable");
236        }
237
238        @Override
239        public void setDither(boolean dither) {
240            throw new UnsupportedOperationException("Not supported with this drawable");
241        }
242
243        @Override
244        public void setFilterBitmap(boolean filter) {
245            throw new UnsupportedOperationException("Not supported with this drawable");
246        }
247
248        @Override
249        public int getIntrinsicWidth() {
250            return mWidth;
251        }
252
253        @Override
254        public int getIntrinsicHeight() {
255            return mHeight;
256        }
257
258        @Override
259        public int getMinimumWidth() {
260            return mWidth;
261        }
262
263        @Override
264        public int getMinimumHeight() {
265            return mHeight;
266        }
267    }
268
269    static class Globals extends IWallpaperManagerCallback.Stub {
270        private final IWallpaperManager mService;
271        private Bitmap mCachedWallpaper;
272        private int mCachedWallpaperUserId;
273        private Bitmap mDefaultWallpaper;
274
275        Globals(Looper looper) {
276            IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
277            mService = IWallpaperManager.Stub.asInterface(b);
278            forgetLoadedWallpaper();
279        }
280
281        public void onWallpaperChanged() {
282            /* The wallpaper has changed but we shouldn't eagerly load the
283             * wallpaper as that would be inefficient. Reset the cached wallpaper
284             * to null so if the user requests the wallpaper again then we'll
285             * fetch it.
286             */
287            forgetLoadedWallpaper();
288        }
289
290        public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
291                @SetWallpaperFlags int which) {
292            return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
293        }
294
295        public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
296                @SetWallpaperFlags int which, int userId) {
297            if (mService != null) {
298                try {
299                    if (!mService.isWallpaperSupported(context.getOpPackageName())) {
300                        return null;
301                    }
302                } catch (RemoteException e) {
303                    throw e.rethrowFromSystemServer();
304                }
305            }
306            synchronized (this) {
307                if (mCachedWallpaper != null && mCachedWallpaperUserId == userId) {
308                    return mCachedWallpaper;
309                }
310                mCachedWallpaper = null;
311                mCachedWallpaperUserId = 0;
312                try {
313                    mCachedWallpaper = getCurrentWallpaperLocked(userId);
314                    mCachedWallpaperUserId = userId;
315                } catch (OutOfMemoryError e) {
316                    Log.w(TAG, "No memory load current wallpaper", e);
317                }
318                if (mCachedWallpaper != null) {
319                    return mCachedWallpaper;
320                }
321            }
322            if (returnDefault) {
323                Bitmap defaultWallpaper = mDefaultWallpaper;
324                if (defaultWallpaper == null) {
325                    defaultWallpaper = getDefaultWallpaper(context, which);
326                    synchronized (this) {
327                        mDefaultWallpaper = defaultWallpaper;
328                    }
329                }
330                return defaultWallpaper;
331            }
332            return null;
333        }
334
335        void forgetLoadedWallpaper() {
336            synchronized (this) {
337                mCachedWallpaper = null;
338                mCachedWallpaperUserId = 0;
339                mDefaultWallpaper = null;
340            }
341        }
342
343        private Bitmap getCurrentWallpaperLocked(int userId) {
344            if (mService == null) {
345                Log.w(TAG, "WallpaperService not running");
346                return null;
347            }
348
349            try {
350                Bundle params = new Bundle();
351                ParcelFileDescriptor fd = mService.getWallpaper(this, FLAG_SYSTEM,
352                        params, userId);
353                if (fd != null) {
354                    try {
355                        BitmapFactory.Options options = new BitmapFactory.Options();
356                        return BitmapFactory.decodeFileDescriptor(
357                                fd.getFileDescriptor(), null, options);
358                    } catch (OutOfMemoryError e) {
359                        Log.w(TAG, "Can't decode file", e);
360                    } finally {
361                        IoUtils.closeQuietly(fd);
362                    }
363                }
364            } catch (RemoteException e) {
365                throw e.rethrowFromSystemServer();
366            }
367            return null;
368        }
369
370        private Bitmap getDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
371            InputStream is = openDefaultWallpaper(context, which);
372            if (is != null) {
373                try {
374                    BitmapFactory.Options options = new BitmapFactory.Options();
375                    return BitmapFactory.decodeStream(is, null, options);
376                } catch (OutOfMemoryError e) {
377                    Log.w(TAG, "Can't decode stream", e);
378                } finally {
379                    IoUtils.closeQuietly(is);
380                }
381            }
382            return null;
383        }
384    }
385
386    private static final Object sSync = new Object[0];
387    private static Globals sGlobals;
388
389    static void initGlobals(Looper looper) {
390        synchronized (sSync) {
391            if (sGlobals == null) {
392                sGlobals = new Globals(looper);
393            }
394        }
395    }
396
397    /*package*/ WallpaperManager(Context context, Handler handler) {
398        mContext = context;
399        initGlobals(context.getMainLooper());
400    }
401
402    /**
403     * Retrieve a WallpaperManager associated with the given Context.
404     */
405    public static WallpaperManager getInstance(Context context) {
406        return (WallpaperManager)context.getSystemService(
407                Context.WALLPAPER_SERVICE);
408    }
409
410    /** @hide */
411    public IWallpaperManager getIWallpaperManager() {
412        return sGlobals.mService;
413    }
414
415    /**
416     * Retrieve the current system wallpaper; if
417     * no wallpaper is set, the system built-in static wallpaper is returned.
418     * This is returned as an
419     * abstract Drawable that you can install in a View to display whatever
420     * wallpaper the user has currently set.
421     * <p>
422     * This method can return null if there is no system wallpaper available, if
423     * wallpapers are not supported in the current user, or if the calling app is not
424     * permitted to access the system wallpaper.
425     *
426     * @return Returns a Drawable object that will draw the system wallpaper,
427     *     or {@code null} if no system wallpaper exists or if the calling application
428     *     is not able to access the wallpaper.
429     */
430    public Drawable getDrawable() {
431        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
432        if (bm != null) {
433            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
434            dr.setDither(false);
435            return dr;
436        }
437        return null;
438    }
439
440    /**
441     * Obtain a drawable for the built-in static system wallpaper.
442     */
443    public Drawable getBuiltInDrawable() {
444        return getBuiltInDrawable(0, 0, false, 0, 0, FLAG_SYSTEM);
445    }
446
447    /**
448     * Obtain a drawable for the specified built-in static system wallpaper.
449     *
450     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
451     *     IllegalArgumentException if an invalid wallpaper is requested.
452     * @return A Drawable presenting the specified wallpaper image, or {@code null}
453     *     if no built-in default image for that wallpaper type exists.
454     */
455    public Drawable getBuiltInDrawable(@SetWallpaperFlags int which) {
456        return getBuiltInDrawable(0, 0, false, 0, 0, which);
457    }
458
459    /**
460     * Returns a drawable for the system built-in static wallpaper. Based on the parameters, the
461     * drawable can be cropped and scaled
462     *
463     * @param outWidth The width of the returned drawable
464     * @param outWidth The height of the returned drawable
465     * @param scaleToFit If true, scale the wallpaper down rather than just cropping it
466     * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image;
467     *        0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned
468     * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image;
469     *        0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned
470     * @return A Drawable presenting the built-in default system wallpaper image,
471     *        or {@code null} if no such default image is defined on this device.
472     */
473    public Drawable getBuiltInDrawable(int outWidth, int outHeight,
474            boolean scaleToFit, float horizontalAlignment, float verticalAlignment) {
475        return getBuiltInDrawable(outWidth, outHeight, scaleToFit,
476                horizontalAlignment, verticalAlignment, FLAG_SYSTEM);
477    }
478
479    /**
480     * Returns a drawable for the built-in static wallpaper of the specified type.  Based on the
481     * parameters, the drawable can be cropped and scaled.
482     *
483     * @param outWidth The width of the returned drawable
484     * @param outWidth The height of the returned drawable
485     * @param scaleToFit If true, scale the wallpaper down rather than just cropping it
486     * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image;
487     *        0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned
488     * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image;
489     *        0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned
490     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
491     *     IllegalArgumentException if an invalid wallpaper is requested.
492     * @return A Drawable presenting the built-in default wallpaper image of the given type,
493     *        or {@code null} if no default image of that type is defined on this device.
494     */
495    public Drawable getBuiltInDrawable(int outWidth, int outHeight, boolean scaleToFit,
496            float horizontalAlignment, float verticalAlignment, @SetWallpaperFlags int which) {
497        if (sGlobals.mService == null) {
498            Log.w(TAG, "WallpaperService not running");
499            throw new RuntimeException(new DeadSystemException());
500        }
501
502        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
503            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
504        }
505
506        Resources resources = mContext.getResources();
507        horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
508        verticalAlignment = Math.max(0, Math.min(1, verticalAlignment));
509
510        InputStream wpStream = openDefaultWallpaper(mContext, which);
511        if (wpStream == null) {
512            if (DEBUG) {
513                Log.w(TAG, "default wallpaper stream " + which + " is null");
514            }
515            return null;
516        } else {
517            InputStream is = new BufferedInputStream(wpStream);
518            if (outWidth <= 0 || outHeight <= 0) {
519                Bitmap fullSize = BitmapFactory.decodeStream(is, null, null);
520                return new BitmapDrawable(resources, fullSize);
521            } else {
522                int inWidth;
523                int inHeight;
524                // Just measure this time through...
525                {
526                    BitmapFactory.Options options = new BitmapFactory.Options();
527                    options.inJustDecodeBounds = true;
528                    BitmapFactory.decodeStream(is, null, options);
529                    if (options.outWidth != 0 && options.outHeight != 0) {
530                        inWidth = options.outWidth;
531                        inHeight = options.outHeight;
532                    } else {
533                        Log.e(TAG, "default wallpaper dimensions are 0");
534                        return null;
535                    }
536                }
537
538                // Reopen the stream to do the full decode.  We know at this point
539                // that openDefaultWallpaper() will return non-null.
540                is = new BufferedInputStream(openDefaultWallpaper(mContext, which));
541
542                RectF cropRectF;
543
544                outWidth = Math.min(inWidth, outWidth);
545                outHeight = Math.min(inHeight, outHeight);
546                if (scaleToFit) {
547                    cropRectF = getMaxCropRect(inWidth, inHeight, outWidth, outHeight,
548                        horizontalAlignment, verticalAlignment);
549                } else {
550                    float left = (inWidth - outWidth) * horizontalAlignment;
551                    float right = left + outWidth;
552                    float top = (inHeight - outHeight) * verticalAlignment;
553                    float bottom = top + outHeight;
554                    cropRectF = new RectF(left, top, right, bottom);
555                }
556                Rect roundedTrueCrop = new Rect();
557                cropRectF.roundOut(roundedTrueCrop);
558
559                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
560                    Log.w(TAG, "crop has bad values for full size image");
561                    return null;
562                }
563
564                // See how much we're reducing the size of the image
565                int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / outWidth,
566                        roundedTrueCrop.height() / outHeight);
567
568                // Attempt to open a region decoder
569                BitmapRegionDecoder decoder = null;
570                try {
571                    decoder = BitmapRegionDecoder.newInstance(is, true);
572                } catch (IOException e) {
573                    Log.w(TAG, "cannot open region decoder for default wallpaper");
574                }
575
576                Bitmap crop = null;
577                if (decoder != null) {
578                    // Do region decoding to get crop bitmap
579                    BitmapFactory.Options options = new BitmapFactory.Options();
580                    if (scaleDownSampleSize > 1) {
581                        options.inSampleSize = scaleDownSampleSize;
582                    }
583                    crop = decoder.decodeRegion(roundedTrueCrop, options);
584                    decoder.recycle();
585                }
586
587                if (crop == null) {
588                    // BitmapRegionDecoder has failed, try to crop in-memory. We know at
589                    // this point that openDefaultWallpaper() will return non-null.
590                    is = new BufferedInputStream(openDefaultWallpaper(mContext, which));
591                    Bitmap fullSize = null;
592                    BitmapFactory.Options options = new BitmapFactory.Options();
593                    if (scaleDownSampleSize > 1) {
594                        options.inSampleSize = scaleDownSampleSize;
595                    }
596                    fullSize = BitmapFactory.decodeStream(is, null, options);
597                    if (fullSize != null) {
598                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
599                                roundedTrueCrop.top, roundedTrueCrop.width(),
600                                roundedTrueCrop.height());
601                    }
602                }
603
604                if (crop == null) {
605                    Log.w(TAG, "cannot decode default wallpaper");
606                    return null;
607                }
608
609                // Scale down if necessary
610                if (outWidth > 0 && outHeight > 0 &&
611                        (crop.getWidth() != outWidth || crop.getHeight() != outHeight)) {
612                    Matrix m = new Matrix();
613                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
614                    RectF returnRect = new RectF(0, 0, outWidth, outHeight);
615                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
616                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
617                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
618                    if (tmp != null) {
619                        Canvas c = new Canvas(tmp);
620                        Paint p = new Paint();
621                        p.setFilterBitmap(true);
622                        c.drawBitmap(crop, m, p);
623                        crop = tmp;
624                    }
625                }
626
627                return new BitmapDrawable(resources, crop);
628            }
629        }
630    }
631
632    private static RectF getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight,
633                float horizontalAlignment, float verticalAlignment) {
634        RectF cropRect = new RectF();
635        // Get a crop rect that will fit this
636        if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
637             cropRect.top = 0;
638             cropRect.bottom = inHeight;
639             float cropWidth = outWidth * (inHeight / (float) outHeight);
640             cropRect.left = (inWidth - cropWidth) * horizontalAlignment;
641             cropRect.right = cropRect.left + cropWidth;
642        } else {
643            cropRect.left = 0;
644            cropRect.right = inWidth;
645            float cropHeight = outHeight * (inWidth / (float) outWidth);
646            cropRect.top = (inHeight - cropHeight) * verticalAlignment;
647            cropRect.bottom = cropRect.top + cropHeight;
648        }
649        return cropRect;
650    }
651
652    /**
653     * Retrieve the current system wallpaper; if there is no wallpaper set,
654     * a null pointer is returned. This is returned as an
655     * abstract Drawable that you can install in a View to display whatever
656     * wallpaper the user has currently set.
657     *
658     * @return Returns a Drawable object that will draw the wallpaper or a
659     * null pointer if these is none.
660     */
661    public Drawable peekDrawable() {
662        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
663        if (bm != null) {
664            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
665            dr.setDither(false);
666            return dr;
667        }
668        return null;
669    }
670
671    /**
672     * Like {@link #getDrawable()}, but the returned Drawable has a number
673     * of limitations to reduce its overhead as much as possible. It will
674     * never scale the wallpaper (only centering it if the requested bounds
675     * do match the bitmap bounds, which should not be typical), doesn't
676     * allow setting an alpha, color filter, or other attributes, etc.  The
677     * bounds of the returned drawable will be initialized to the same bounds
678     * as the wallpaper, so normally you will not need to touch it.  The
679     * drawable also assumes that it will be used in a context running in
680     * the same density as the screen (not in density compatibility mode).
681     *
682     * @return Returns a Drawable object that will draw the wallpaper.
683     */
684    public Drawable getFastDrawable() {
685        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
686        if (bm != null) {
687            return new FastBitmapDrawable(bm);
688        }
689        return null;
690    }
691
692    /**
693     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
694     * a null pointer is returned.
695     *
696     * @return Returns an optimized Drawable object that will draw the
697     * wallpaper or a null pointer if these is none.
698     */
699    public Drawable peekFastDrawable() {
700       Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
701        if (bm != null) {
702            return new FastBitmapDrawable(bm);
703        }
704        return null;
705    }
706
707    /**
708     * Like {@link #getDrawable()} but returns a Bitmap.
709     *
710     * @hide
711     */
712    public Bitmap getBitmap() {
713        return getBitmapAsUser(mContext.getUserId());
714    }
715
716    /**
717     * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
718     *
719     * @hide
720     */
721    public Bitmap getBitmapAsUser(int userId) {
722        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
723    }
724
725    /**
726     * Get an open, readable file descriptor to the given wallpaper image file.
727     * The caller is responsible for closing the file descriptor when done ingesting the file.
728     *
729     * <p>If no lock-specific wallpaper has been configured for the given user, then
730     * this method will return {@code null} when requesting {@link #FLAG_LOCK} rather than
731     * returning the system wallpaper's image file.
732     *
733     * @param which The wallpaper whose image file is to be retrieved.  Must be a single
734     *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or
735     *     {@link #FLAG_LOCK}.
736     *
737     * @see #FLAG_LOCK
738     * @see #FLAG_SYSTEM
739     */
740    public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which) {
741        return getWallpaperFile(which, mContext.getUserId());
742    }
743
744    /**
745     * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
746     * for a given user.  The caller must hold the INTERACT_ACROSS_USERS_FULL
747     * permission to access another user's wallpaper data.
748     *
749     * @param which The wallpaper whose image file is to be retrieved.  Must be a single
750     *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or
751     *     {@link #FLAG_LOCK}.
752     * @param userId The user or profile whose imagery is to be retrieved
753     *
754     * @see #FLAG_LOCK
755     * @see #FLAG_SYSTEM
756     *
757     * @hide
758     */
759    public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId) {
760        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
761            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
762        }
763
764        if (sGlobals.mService == null) {
765            Log.w(TAG, "WallpaperService not running");
766            throw new RuntimeException(new DeadSystemException());
767        } else {
768            try {
769                Bundle outParams = new Bundle();
770                return sGlobals.mService.getWallpaper(null, which, outParams, userId);
771            } catch (RemoteException e) {
772                throw e.rethrowFromSystemServer();
773            }
774        }
775    }
776
777    /**
778     * Remove all internal references to the last loaded wallpaper.  Useful
779     * for apps that want to reduce memory usage when they only temporarily
780     * need to have the wallpaper.  After calling, the next request for the
781     * wallpaper will require reloading it again from disk.
782     */
783    public void forgetLoadedWallpaper() {
784        sGlobals.forgetLoadedWallpaper();
785    }
786
787    /**
788     * If the current wallpaper is a live wallpaper component, return the
789     * information about that wallpaper.  Otherwise, if it is a static image,
790     * simply return null.
791     */
792    public WallpaperInfo getWallpaperInfo() {
793        try {
794            if (sGlobals.mService == null) {
795                Log.w(TAG, "WallpaperService not running");
796                throw new RuntimeException(new DeadSystemException());
797            } else {
798                return sGlobals.mService.getWallpaperInfo(UserHandle.myUserId());
799            }
800        } catch (RemoteException e) {
801            throw e.rethrowFromSystemServer();
802        }
803    }
804
805    /**
806     * Get the ID of the current wallpaper of the given kind.  If there is no
807     * such wallpaper configured, returns a negative number.
808     *
809     * <p>Every time the wallpaper image is set, a new ID is assigned to it.
810     * This method allows the caller to determine whether the wallpaper imagery
811     * has changed, regardless of how that change happened.
812     *
813     * @param which The wallpaper whose ID is to be returned.  Must be a single
814     *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or
815     *     {@link #FLAG_LOCK}.
816     * @return The positive numeric ID of the current wallpaper of the given kind,
817     *     or a negative value if no such wallpaper is configured.
818     */
819    public int getWallpaperId(@SetWallpaperFlags int which) {
820        return getWallpaperIdForUser(which, mContext.getUserId());
821    }
822
823    /**
824     * Get the ID of the given user's current wallpaper of the given kind.  If there
825     * is no such wallpaper configured, returns a negative number.
826     * @hide
827     */
828    public int getWallpaperIdForUser(@SetWallpaperFlags int which, int userId) {
829        try {
830            if (sGlobals.mService == null) {
831                Log.w(TAG, "WallpaperService not running");
832                throw new RuntimeException(new DeadSystemException());
833            } else {
834                return sGlobals.mService.getWallpaperIdForUser(which, userId);
835            }
836        } catch (RemoteException e) {
837            throw e.rethrowFromSystemServer();
838        }
839    }
840
841    /**
842     * Gets an Intent that will launch an activity that crops the given
843     * image and sets the device's wallpaper. If there is a default HOME activity
844     * that supports cropping wallpapers, it will be preferred as the default.
845     * Use this method instead of directly creating a {@link #ACTION_CROP_AND_SET_WALLPAPER}
846     * intent.
847     *
848     * @param imageUri The image URI that will be set in the intent. The must be a content
849     *                 URI and its provider must resolve its type to "image/*"
850     *
851     * @throws IllegalArgumentException if the URI is not a content URI or its MIME type is
852     *         not "image/*"
853     */
854    public Intent getCropAndSetWallpaperIntent(Uri imageUri) {
855        if (imageUri == null) {
856            throw new IllegalArgumentException("Image URI must not be null");
857        }
858
859        if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())) {
860            throw new IllegalArgumentException("Image URI must be of the "
861                    + ContentResolver.SCHEME_CONTENT + " scheme type");
862        }
863
864        final PackageManager packageManager = mContext.getPackageManager();
865        Intent cropAndSetWallpaperIntent =
866                new Intent(ACTION_CROP_AND_SET_WALLPAPER, imageUri);
867        cropAndSetWallpaperIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
868
869        // Find out if the default HOME activity supports CROP_AND_SET_WALLPAPER
870        Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
871        ResolveInfo resolvedHome = packageManager.resolveActivity(homeIntent,
872                PackageManager.MATCH_DEFAULT_ONLY);
873        if (resolvedHome != null) {
874            cropAndSetWallpaperIntent.setPackage(resolvedHome.activityInfo.packageName);
875
876            List<ResolveInfo> cropAppList = packageManager.queryIntentActivities(
877                    cropAndSetWallpaperIntent, 0);
878            if (cropAppList.size() > 0) {
879                return cropAndSetWallpaperIntent;
880            }
881        }
882
883        // fallback crop activity
884        final String cropperPackage = mContext.getString(
885                com.android.internal.R.string.config_wallpaperCropperPackage);
886        cropAndSetWallpaperIntent.setPackage(cropperPackage);
887        List<ResolveInfo> cropAppList = packageManager.queryIntentActivities(
888                cropAndSetWallpaperIntent, 0);
889        if (cropAppList.size() > 0) {
890            return cropAndSetWallpaperIntent;
891        }
892        // If the URI is not of the right type, or for some reason the system wallpaper
893        // cropper doesn't exist, return null
894        throw new IllegalArgumentException("Cannot use passed URI to set wallpaper; " +
895            "check that the type returned by ContentProvider matches image/*");
896    }
897
898    /**
899     * Change the current system wallpaper to the bitmap in the given resource.
900     * The resource is opened as a raw data stream and copied into the
901     * wallpaper; it must be a valid PNG or JPEG image.  On success, the intent
902     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
903     *
904     * <p>This method requires the caller to hold the permission
905     * {@link android.Manifest.permission#SET_WALLPAPER}.
906     *
907     * @param resid The resource ID of the bitmap to be used as the wallpaper image
908     *
909     * @throws IOException If an error occurs reverting to the built-in
910     * wallpaper.
911     */
912    public void setResource(@RawRes int resid) throws IOException {
913        setResource(resid, FLAG_SYSTEM | FLAG_LOCK);
914    }
915
916    /**
917     * Version of {@link #setResource(int)} that allows the caller to specify which
918     * of the supported wallpaper categories to set.
919     *
920     * @param resid The resource ID of the bitmap to be used as the wallpaper image
921     * @param which Flags indicating which wallpaper(s) to configure with the new imagery
922     *
923     * @see #FLAG_LOCK
924     * @see #FLAG_SYSTEM
925     *
926     * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
927     *
928     * @throws IOException
929     */
930    public int setResource(@RawRes int resid, @SetWallpaperFlags int which)
931            throws IOException {
932        if (sGlobals.mService == null) {
933            Log.w(TAG, "WallpaperService not running");
934            throw new RuntimeException(new DeadSystemException());
935        }
936        final Bundle result = new Bundle();
937        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
938        try {
939            Resources resources = mContext.getResources();
940            /* Set the wallpaper to the default values */
941            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
942                    "res:" + resources.getResourceName(resid),
943                    mContext.getOpPackageName(), null, false, result, which, completion,
944                    UserHandle.myUserId());
945            if (fd != null) {
946                FileOutputStream fos = null;
947                boolean ok = false;
948                try {
949                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
950                    copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
951                    // The 'close()' is the trigger for any server-side image manipulation,
952                    // so we must do that before waiting for completion.
953                    fos.close();
954                    completion.waitForCompletion();
955                } finally {
956                    // Might be redundant but completion shouldn't wait unless the write
957                    // succeeded; this is a fallback if it threw past the close+wait.
958                    IoUtils.closeQuietly(fos);
959                }
960            }
961        } catch (RemoteException e) {
962            throw e.rethrowFromSystemServer();
963        }
964        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
965    }
966
967    /**
968     * Change the current system wallpaper to a bitmap.  The given bitmap is
969     * converted to a PNG and stored as the wallpaper.  On success, the intent
970     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
971     *
972     * <p>This method is equivalent to calling
973     * {@link #setBitmap(Bitmap, Rect, boolean)} and passing {@code null} for the
974     * {@code visibleCrop} rectangle and {@code true} for the {@code allowBackup}
975     * parameter.
976     *
977     * <p>This method requires the caller to hold the permission
978     * {@link android.Manifest.permission#SET_WALLPAPER}.
979     *
980     * @param bitmap The bitmap to be used as the new system wallpaper.
981     *
982     * @throws IOException If an error occurs when attempting to set the wallpaper
983     *     to the provided image.
984     */
985    public void setBitmap(Bitmap bitmap) throws IOException {
986        setBitmap(bitmap, null, true);
987    }
988
989    /**
990     * Change the current system wallpaper to a bitmap, specifying a hint about
991     * which subrectangle of the full image is to be visible.  The OS will then
992     * try to best present the given portion of the full image as the static system
993     * wallpaper image.  On success, the intent
994     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
995     *
996     * <p>Passing {@code null} as the {@code visibleHint} parameter is equivalent to
997     * passing (0, 0, {@code fullImage.getWidth()}, {@code fullImage.getHeight()}).
998     *
999     * <p>This method requires the caller to hold the permission
1000     * {@link android.Manifest.permission#SET_WALLPAPER}.
1001     *
1002     * @param fullImage A bitmap that will supply the wallpaper imagery.
1003     * @param visibleCropHint The rectangular subregion of {@code fullImage} that should be
1004     *     displayed as wallpaper.  Passing {@code null} for this parameter means that
1005     *     the full image should be displayed if possible given the image's and device's
1006     *     aspect ratios, etc.
1007     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
1008     *     image for restore to a future device; {@code false} otherwise.
1009     *
1010     * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
1011     *
1012     * @throws IOException If an error occurs when attempting to set the wallpaper
1013     *     to the provided image.
1014     * @throws IllegalArgumentException If the {@code visibleCropHint} rectangle is
1015     *     empty or invalid.
1016     */
1017    public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup)
1018            throws IOException {
1019        return setBitmap(fullImage, visibleCropHint, allowBackup, FLAG_SYSTEM | FLAG_LOCK);
1020    }
1021
1022    /**
1023     * Version of {@link #setBitmap(Bitmap, Rect, boolean)} that allows the caller
1024     * to specify which of the supported wallpaper categories to set.
1025     *
1026     * @param fullImage A bitmap that will supply the wallpaper imagery.
1027     * @param visibleCropHint The rectangular subregion of {@code fullImage} that should be
1028     *     displayed as wallpaper.  Passing {@code null} for this parameter means that
1029     *     the full image should be displayed if possible given the image's and device's
1030     *     aspect ratios, etc.
1031     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
1032     *     image for restore to a future device; {@code false} otherwise.
1033     * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
1034     *
1035     * @see #FLAG_LOCK
1036     * @see #FLAG_SYSTEM
1037     *
1038     * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
1039     *
1040     * @throws IOException
1041     */
1042    public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
1043            boolean allowBackup, @SetWallpaperFlags int which)
1044            throws IOException {
1045        return setBitmap(fullImage, visibleCropHint, allowBackup, which,
1046                UserHandle.myUserId());
1047    }
1048
1049    /**
1050     * Like {@link #setBitmap(Bitmap, Rect, boolean, int)}, but allows to pass in an explicit user
1051     * id. If the user id doesn't match the user id the process is running under, calling this
1052     * requires permission {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
1053     * @hide
1054     */
1055    public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
1056            boolean allowBackup, @SetWallpaperFlags int which, int userId)
1057            throws IOException {
1058        validateRect(visibleCropHint);
1059        if (sGlobals.mService == null) {
1060            Log.w(TAG, "WallpaperService not running");
1061            throw new RuntimeException(new DeadSystemException());
1062        }
1063        final Bundle result = new Bundle();
1064        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
1065        try {
1066            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
1067                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
1068                    result, which, completion, userId);
1069            if (fd != null) {
1070                FileOutputStream fos = null;
1071                try {
1072                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
1073                    fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
1074                    fos.close();
1075                    completion.waitForCompletion();
1076                } finally {
1077                    IoUtils.closeQuietly(fos);
1078                }
1079            }
1080        } catch (RemoteException e) {
1081            throw e.rethrowFromSystemServer();
1082        }
1083        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
1084    }
1085
1086    private final void validateRect(Rect rect) {
1087        if (rect != null && rect.isEmpty()) {
1088            throw new IllegalArgumentException("visibleCrop rectangle must be valid and non-empty");
1089        }
1090    }
1091
1092    /**
1093     * Change the current system wallpaper to a specific byte stream.  The
1094     * give InputStream is copied into persistent storage and will now be
1095     * used as the wallpaper.  Currently it must be either a JPEG or PNG
1096     * image.  On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
1097     * is broadcast.
1098     *
1099     * <p>This method is equivalent to calling
1100     * {@link #setStream(InputStream, Rect, boolean)} and passing {@code null} for the
1101     * {@code visibleCrop} rectangle and {@code true} for the {@code allowBackup}
1102     * parameter.
1103     *
1104     * <p>This method requires the caller to hold the permission
1105     * {@link android.Manifest.permission#SET_WALLPAPER}.
1106     *
1107     * @param bitmapData A stream containing the raw data to install as a wallpaper.  This
1108     *     data can be in any format handled by {@link BitmapRegionDecoder}.
1109     *
1110     * @throws IOException If an error occurs when attempting to set the wallpaper
1111     *     based on the provided image data.
1112     */
1113    public void setStream(InputStream bitmapData) throws IOException {
1114        setStream(bitmapData, null, true);
1115    }
1116
1117    private void copyStreamToWallpaperFile(InputStream data, FileOutputStream fos)
1118            throws IOException {
1119        byte[] buffer = new byte[32768];
1120        int amt;
1121        while ((amt=data.read(buffer)) > 0) {
1122            fos.write(buffer, 0, amt);
1123        }
1124    }
1125
1126    /**
1127     * Change the current system wallpaper to a specific byte stream, specifying a
1128     * hint about which subrectangle of the full image is to be visible.  The OS will
1129     * then try to best present the given portion of the full image as the static system
1130     * wallpaper image.  The data from the given InputStream is copied into persistent
1131     * storage and will then be used as the system wallpaper.  Currently the data must
1132     * be either a JPEG or PNG image.  On success, the intent
1133     * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
1134     *
1135     * <p>This method requires the caller to hold the permission
1136     * {@link android.Manifest.permission#SET_WALLPAPER}.
1137     *
1138     * @param bitmapData A stream containing the raw data to install as a wallpaper.  This
1139     *     data can be in any format handled by {@link BitmapRegionDecoder}.
1140     * @param visibleCropHint The rectangular subregion of the streamed image that should be
1141     *     displayed as wallpaper.  Passing {@code null} for this parameter means that
1142     *     the full image should be displayed if possible given the image's and device's
1143     *     aspect ratios, etc.
1144     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
1145     *     image for restore to a future device; {@code false} otherwise.
1146     * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
1147     *
1148     * @see #getWallpaperId(int)
1149     *
1150     * @throws IOException If an error occurs when attempting to set the wallpaper
1151     *     based on the provided image data.
1152     * @throws IllegalArgumentException If the {@code visibleCropHint} rectangle is
1153     *     empty or invalid.
1154     */
1155    public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup)
1156            throws IOException {
1157        return setStream(bitmapData, visibleCropHint, allowBackup, FLAG_SYSTEM | FLAG_LOCK);
1158    }
1159
1160    /**
1161     * Version of {@link #setStream(InputStream, Rect, boolean)} that allows the caller
1162     * to specify which of the supported wallpaper categories to set.
1163     *
1164     * @param bitmapData A stream containing the raw data to install as a wallpaper.  This
1165     *     data can be in any format handled by {@link BitmapRegionDecoder}.
1166     * @param visibleCropHint The rectangular subregion of the streamed image that should be
1167     *     displayed as wallpaper.  Passing {@code null} for this parameter means that
1168     *     the full image should be displayed if possible given the image's and device's
1169     *     aspect ratios, etc.
1170     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
1171     *     image for restore to a future device; {@code false} otherwise.
1172     * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
1173     * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
1174     *
1175     * @see #getWallpaperId(int)
1176     * @see #FLAG_LOCK
1177     * @see #FLAG_SYSTEM
1178     *
1179     * @throws IOException
1180     */
1181    public int setStream(InputStream bitmapData, Rect visibleCropHint,
1182            boolean allowBackup, @SetWallpaperFlags int which)
1183                    throws IOException {
1184        validateRect(visibleCropHint);
1185        if (sGlobals.mService == null) {
1186            Log.w(TAG, "WallpaperService not running");
1187            throw new RuntimeException(new DeadSystemException());
1188        }
1189        final Bundle result = new Bundle();
1190        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
1191        try {
1192            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
1193                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
1194                    result, which, completion, UserHandle.myUserId());
1195            if (fd != null) {
1196                FileOutputStream fos = null;
1197                try {
1198                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
1199                    copyStreamToWallpaperFile(bitmapData, fos);
1200                    fos.close();
1201                    completion.waitForCompletion();
1202                } finally {
1203                    IoUtils.closeQuietly(fos);
1204                }
1205            }
1206        } catch (RemoteException e) {
1207            throw e.rethrowFromSystemServer();
1208        }
1209
1210        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
1211    }
1212
1213    /**
1214     * Return whether any users are currently set to use the wallpaper
1215     * with the given resource ID.  That is, their wallpaper has been
1216     * set through {@link #setResource(int)} with the same resource id.
1217     */
1218    public boolean hasResourceWallpaper(@RawRes int resid) {
1219        if (sGlobals.mService == null) {
1220            Log.w(TAG, "WallpaperService not running");
1221            throw new RuntimeException(new DeadSystemException());
1222        }
1223        try {
1224            Resources resources = mContext.getResources();
1225            String name = "res:" + resources.getResourceName(resid);
1226            return sGlobals.mService.hasNamedWallpaper(name);
1227        } catch (RemoteException e) {
1228            throw e.rethrowFromSystemServer();
1229        }
1230    }
1231
1232    /**
1233     * Returns the desired minimum width for the wallpaper. Callers of
1234     * {@link #setBitmap(android.graphics.Bitmap)} or
1235     * {@link #setStream(java.io.InputStream)} should check this value
1236     * beforehand to make sure the supplied wallpaper respects the desired
1237     * minimum width.
1238     *
1239     * If the returned value is <= 0, the caller should use the width of
1240     * the default display instead.
1241     *
1242     * @return The desired minimum width for the wallpaper. This value should
1243     * be honored by applications that set the wallpaper but it is not
1244     * mandatory.
1245     */
1246    public int getDesiredMinimumWidth() {
1247        if (sGlobals.mService == null) {
1248            Log.w(TAG, "WallpaperService not running");
1249            throw new RuntimeException(new DeadSystemException());
1250        }
1251        try {
1252            return sGlobals.mService.getWidthHint();
1253        } catch (RemoteException e) {
1254            throw e.rethrowFromSystemServer();
1255        }
1256    }
1257
1258    /**
1259     * Returns the desired minimum height for the wallpaper. Callers of
1260     * {@link #setBitmap(android.graphics.Bitmap)} or
1261     * {@link #setStream(java.io.InputStream)} should check this value
1262     * beforehand to make sure the supplied wallpaper respects the desired
1263     * minimum height.
1264     *
1265     * If the returned value is <= 0, the caller should use the height of
1266     * the default display instead.
1267     *
1268     * @return The desired minimum height for the wallpaper. This value should
1269     * be honored by applications that set the wallpaper but it is not
1270     * mandatory.
1271     */
1272    public int getDesiredMinimumHeight() {
1273        if (sGlobals.mService == null) {
1274            Log.w(TAG, "WallpaperService not running");
1275            throw new RuntimeException(new DeadSystemException());
1276        }
1277        try {
1278            return sGlobals.mService.getHeightHint();
1279        } catch (RemoteException e) {
1280            throw e.rethrowFromSystemServer();
1281        }
1282    }
1283
1284    /**
1285     * For use only by the current home application, to specify the size of
1286     * wallpaper it would like to use.  This allows such applications to have
1287     * a virtual wallpaper that is larger than the physical screen, matching
1288     * the size of their workspace.
1289     *
1290     * <p>Note developers, who don't seem to be reading this.  This is
1291     * for <em>home apps</em> to tell what size wallpaper they would like.
1292     * Nobody else should be calling this!  Certainly not other non-home
1293     * apps that change the wallpaper.  Those apps are supposed to
1294     * <b>retrieve</b> the suggested size so they can construct a wallpaper
1295     * that matches it.
1296     *
1297     * <p>This method requires the caller to hold the permission
1298     * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}.
1299     *
1300     * @param minimumWidth Desired minimum width
1301     * @param minimumHeight Desired minimum height
1302     */
1303    public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
1304        try {
1305            /**
1306             * The framework makes no attempt to limit the window size
1307             * to the maximum texture size. Any window larger than this
1308             * cannot be composited.
1309             *
1310             * Read maximum texture size from system property and scale down
1311             * minimumWidth and minimumHeight accordingly.
1312             */
1313            int maximumTextureSize;
1314            try {
1315                maximumTextureSize = SystemProperties.getInt("sys.max_texture_size", 0);
1316            } catch (Exception e) {
1317                maximumTextureSize = 0;
1318            }
1319
1320            if (maximumTextureSize > 0) {
1321                if ((minimumWidth > maximumTextureSize) ||
1322                    (minimumHeight > maximumTextureSize)) {
1323                    float aspect = (float)minimumHeight / (float)minimumWidth;
1324                    if (minimumWidth > minimumHeight) {
1325                        minimumWidth = maximumTextureSize;
1326                        minimumHeight = (int)((minimumWidth * aspect) + 0.5);
1327                    } else {
1328                        minimumHeight = maximumTextureSize;
1329                        minimumWidth = (int)((minimumHeight / aspect) + 0.5);
1330                    }
1331                }
1332            }
1333
1334            if (sGlobals.mService == null) {
1335                Log.w(TAG, "WallpaperService not running");
1336                throw new RuntimeException(new DeadSystemException());
1337            } else {
1338                sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight,
1339                        mContext.getOpPackageName());
1340            }
1341        } catch (RemoteException e) {
1342            throw e.rethrowFromSystemServer();
1343        }
1344    }
1345
1346    /**
1347     * Specify extra padding that the wallpaper should have outside of the display.
1348     * That is, the given padding supplies additional pixels the wallpaper should extend
1349     * outside of the display itself.
1350     * @param padding The number of pixels the wallpaper should extend beyond the display,
1351     * on its left, top, right, and bottom sides.
1352     * @hide
1353     */
1354    @SystemApi
1355    public void setDisplayPadding(Rect padding) {
1356        try {
1357            if (sGlobals.mService == null) {
1358                Log.w(TAG, "WallpaperService not running");
1359                throw new RuntimeException(new DeadSystemException());
1360            } else {
1361                sGlobals.mService.setDisplayPadding(padding, mContext.getOpPackageName());
1362            }
1363        } catch (RemoteException e) {
1364            throw e.rethrowFromSystemServer();
1365        }
1366    }
1367
1368    /**
1369     * Apply a raw offset to the wallpaper window.  Should only be used in
1370     * combination with {@link #setDisplayPadding(android.graphics.Rect)} when you
1371     * have ensured that the wallpaper will extend outside of the display area so that
1372     * it can be moved without leaving part of the display uncovered.
1373     * @param x The offset, in pixels, to apply to the left edge.
1374     * @param y The offset, in pixels, to apply to the top edge.
1375     * @hide
1376     */
1377    @SystemApi
1378    public void setDisplayOffset(IBinder windowToken, int x, int y) {
1379        try {
1380            //Log.v(TAG, "Sending new wallpaper display offsets from app...");
1381            WindowManagerGlobal.getWindowSession().setWallpaperDisplayOffset(
1382                    windowToken, x, y);
1383            //Log.v(TAG, "...app returning after sending display offset!");
1384        } catch (RemoteException e) {
1385            throw e.rethrowFromSystemServer();
1386        }
1387    }
1388
1389    /**
1390     * Clear the wallpaper.
1391     *
1392     * @hide
1393     */
1394    @SystemApi
1395    public void clearWallpaper() {
1396        clearWallpaper(FLAG_LOCK, mContext.getUserId());
1397        clearWallpaper(FLAG_SYSTEM, mContext.getUserId());
1398    }
1399
1400    /**
1401     * Clear the wallpaper for a specific user.  The caller must hold the
1402     * INTERACT_ACROSS_USERS_FULL permission to clear another user's
1403     * wallpaper.
1404     * @hide
1405     */
1406    @SystemApi
1407    public void clearWallpaper(@SetWallpaperFlags int which, int userId) {
1408        if (sGlobals.mService == null) {
1409            Log.w(TAG, "WallpaperService not running");
1410            throw new RuntimeException(new DeadSystemException());
1411        }
1412        try {
1413            sGlobals.mService.clearWallpaper(mContext.getOpPackageName(), which, userId);
1414        } catch (RemoteException e) {
1415            throw e.rethrowFromSystemServer();
1416        }
1417    }
1418
1419    /**
1420     * Set the live wallpaper.
1421     *
1422     * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
1423     * permission.
1424     *
1425     * @hide
1426     */
1427    @SystemApi
1428    public boolean setWallpaperComponent(ComponentName name) {
1429        return setWallpaperComponent(name, UserHandle.myUserId());
1430    }
1431
1432    /**
1433     * Set the live wallpaper.
1434     *
1435     * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
1436     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
1437     * another user's wallpaper.
1438     *
1439     * @hide
1440     */
1441    public boolean setWallpaperComponent(ComponentName name, int userId) {
1442        if (sGlobals.mService == null) {
1443            Log.w(TAG, "WallpaperService not running");
1444            throw new RuntimeException(new DeadSystemException());
1445        }
1446        try {
1447            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
1448                    userId);
1449            return true;
1450        } catch (RemoteException e) {
1451            throw e.rethrowFromSystemServer();
1452        }
1453    }
1454
1455    /**
1456     * Set the display position of the current wallpaper within any larger space, when
1457     * that wallpaper is visible behind the given window.  The X and Y offsets
1458     * are floating point numbers ranging from 0 to 1, representing where the
1459     * wallpaper should be positioned within the screen space.  These only
1460     * make sense when the wallpaper is larger than the display.
1461     *
1462     * @param windowToken The window who these offsets should be associated
1463     * with, as returned by {@link android.view.View#getWindowToken()
1464     * View.getWindowToken()}.
1465     * @param xOffset The offset along the X dimension, from 0 to 1.
1466     * @param yOffset The offset along the Y dimension, from 0 to 1.
1467     */
1468    public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
1469        try {
1470            //Log.v(TAG, "Sending new wallpaper offsets from app...");
1471            WindowManagerGlobal.getWindowSession().setWallpaperPosition(
1472                    windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
1473            //Log.v(TAG, "...app returning after sending offsets!");
1474        } catch (RemoteException e) {
1475            throw e.rethrowFromSystemServer();
1476        }
1477    }
1478
1479    /**
1480     * For applications that use multiple virtual screens showing a wallpaper,
1481     * specify the step size between virtual screens. For example, if the
1482     * launcher has 3 virtual screens, it would specify an xStep of 0.5,
1483     * since the X offset for those screens are 0.0, 0.5 and 1.0
1484     * @param xStep The X offset delta from one screen to the next one
1485     * @param yStep The Y offset delta from one screen to the next one
1486     */
1487    public void setWallpaperOffsetSteps(float xStep, float yStep) {
1488        mWallpaperXStep = xStep;
1489        mWallpaperYStep = yStep;
1490    }
1491
1492    /**
1493     * Send an arbitrary command to the current active wallpaper.
1494     *
1495     * @param windowToken The window who these offsets should be associated
1496     * with, as returned by {@link android.view.View#getWindowToken()
1497     * View.getWindowToken()}.
1498     * @param action Name of the command to perform.  This must be a scoped
1499     * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT".
1500     * @param x Arbitrary integer argument based on command.
1501     * @param y Arbitrary integer argument based on command.
1502     * @param z Arbitrary integer argument based on command.
1503     * @param extras Optional additional information for the command, or null.
1504     */
1505    public void sendWallpaperCommand(IBinder windowToken, String action,
1506            int x, int y, int z, Bundle extras) {
1507        try {
1508            //Log.v(TAG, "Sending new wallpaper offsets from app...");
1509            WindowManagerGlobal.getWindowSession().sendWallpaperCommand(
1510                    windowToken, action, x, y, z, extras, false);
1511            //Log.v(TAG, "...app returning after sending offsets!");
1512        } catch (RemoteException e) {
1513            throw e.rethrowFromSystemServer();
1514        }
1515    }
1516
1517    /**
1518     * Returns whether wallpapers are supported for the calling user. If this function returns
1519     * {@code false}, any attempts to changing the wallpaper will have no effect,
1520     * and any attempt to obtain of the wallpaper will return {@code null}.
1521     */
1522    public boolean isWallpaperSupported() {
1523        if (sGlobals.mService == null) {
1524            Log.w(TAG, "WallpaperService not running");
1525            throw new RuntimeException(new DeadSystemException());
1526        } else {
1527            try {
1528                return sGlobals.mService.isWallpaperSupported(mContext.getOpPackageName());
1529            } catch (RemoteException e) {
1530                throw e.rethrowFromSystemServer();
1531            }
1532        }
1533    }
1534
1535    /**
1536     * Returns whether the calling package is allowed to set the wallpaper for the calling user.
1537     * If this function returns {@code false}, any attempts to change the wallpaper will have
1538     * no effect. Always returns {@code true} for device owner and profile owner.
1539     *
1540     * @see android.os.UserManager#DISALLOW_SET_WALLPAPER
1541     */
1542    public boolean isSetWallpaperAllowed() {
1543        if (sGlobals.mService == null) {
1544            Log.w(TAG, "WallpaperService not running");
1545            throw new RuntimeException(new DeadSystemException());
1546        } else {
1547            try {
1548                return sGlobals.mService.isSetWallpaperAllowed(mContext.getOpPackageName());
1549            } catch (RemoteException e) {
1550                throw e.rethrowFromSystemServer();
1551            }
1552        }
1553    }
1554
1555    /**
1556     * Clear the offsets previously associated with this window through
1557     * {@link #setWallpaperOffsets(IBinder, float, float)}.  This reverts
1558     * the window to its default state, where it does not cause the wallpaper
1559     * to scroll from whatever its last offsets were.
1560     *
1561     * @param windowToken The window who these offsets should be associated
1562     * with, as returned by {@link android.view.View#getWindowToken()
1563     * View.getWindowToken()}.
1564     */
1565    public void clearWallpaperOffsets(IBinder windowToken) {
1566        try {
1567            WindowManagerGlobal.getWindowSession().setWallpaperPosition(
1568                    windowToken, -1, -1, -1, -1);
1569        } catch (RemoteException e) {
1570            throw e.rethrowFromSystemServer();
1571        }
1572    }
1573
1574    /**
1575     * Remove any currently set system wallpaper, reverting to the system's built-in
1576     * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
1577     * is broadcast.
1578     *
1579     * <p>This method requires the caller to hold the permission
1580     * {@link android.Manifest.permission#SET_WALLPAPER}.
1581     *
1582     * @throws IOException If an error occurs reverting to the built-in
1583     * wallpaper.
1584     */
1585    public void clear() throws IOException {
1586        setStream(openDefaultWallpaper(mContext, FLAG_SYSTEM), null, false);
1587    }
1588
1589    /**
1590     * Remove one or more currently set wallpapers, reverting to the system default
1591     * display for each one.  If {@link #FLAG_SYSTEM} is set in the {@code which}
1592     * parameter, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} will be broadcast
1593     * upon success.
1594     *
1595     * @param which A bitwise combination of {@link #FLAG_SYSTEM} or
1596     *   {@link #FLAG_LOCK}
1597     * @throws IOException If an error occurs reverting to the built-in wallpaper.
1598     */
1599    public void clear(@SetWallpaperFlags int which) throws IOException {
1600        if ((which & FLAG_SYSTEM) != 0) {
1601            clear();
1602        }
1603        if ((which & FLAG_LOCK) != 0) {
1604            clearWallpaper(FLAG_LOCK, mContext.getUserId());
1605        }
1606    }
1607
1608    /**
1609     * Open stream representing the default static image wallpaper.
1610     *
1611     * If the device defines no default wallpaper of the requested kind,
1612     * {@code null} is returned.
1613     *
1614     * @hide
1615     */
1616    public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
1617        final String whichProp;
1618        final int defaultResId;
1619        if (which == FLAG_LOCK) {
1620            /* Factory-default lock wallpapers are not yet supported
1621            whichProp = PROP_LOCK_WALLPAPER;
1622            defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
1623            */
1624            return null;
1625        } else {
1626            whichProp = PROP_WALLPAPER;
1627            defaultResId = com.android.internal.R.drawable.default_wallpaper;
1628        }
1629        final String path = SystemProperties.get(whichProp);
1630        if (!TextUtils.isEmpty(path)) {
1631            final File file = new File(path);
1632            if (file.exists()) {
1633                try {
1634                    return new FileInputStream(file);
1635                } catch (IOException e) {
1636                    // Ignored, fall back to platform default below
1637                }
1638            }
1639        }
1640        try {
1641            return context.getResources().openRawResource(defaultResId);
1642        } catch (NotFoundException e) {
1643            // no default defined for this device; this is not a failure
1644        }
1645        return null;
1646    }
1647
1648    /**
1649     * Return {@link ComponentName} of the default live wallpaper, or
1650     * {@code null} if none is defined.
1651     *
1652     * @hide
1653     */
1654    public static ComponentName getDefaultWallpaperComponent(Context context) {
1655        String flat = SystemProperties.get(PROP_WALLPAPER_COMPONENT);
1656        if (!TextUtils.isEmpty(flat)) {
1657            final ComponentName cn = ComponentName.unflattenFromString(flat);
1658            if (cn != null) {
1659                return cn;
1660            }
1661        }
1662
1663        flat = context.getString(com.android.internal.R.string.default_wallpaper_component);
1664        if (!TextUtils.isEmpty(flat)) {
1665            final ComponentName cn = ComponentName.unflattenFromString(flat);
1666            if (cn != null) {
1667                return cn;
1668            }
1669        }
1670
1671        return null;
1672    }
1673
1674    /**
1675     * Register a callback for lock wallpaper observation. Only the OS may use this.
1676     *
1677     * @return true on success; false on error.
1678     * @hide
1679     */
1680    public boolean setLockWallpaperCallback(IWallpaperManagerCallback callback) {
1681        if (sGlobals.mService == null) {
1682            Log.w(TAG, "WallpaperService not running");
1683            throw new RuntimeException(new DeadSystemException());
1684        }
1685
1686        try {
1687            return sGlobals.mService.setLockWallpaperCallback(callback);
1688        } catch (RemoteException e) {
1689            throw e.rethrowFromSystemServer();
1690        }
1691    }
1692
1693    /**
1694     * Is the current system wallpaper eligible for backup?
1695     *
1696     * Only the OS itself may use this method.
1697     * @hide
1698     */
1699    public boolean isWallpaperBackupEligible(int which) {
1700        if (sGlobals.mService == null) {
1701            Log.w(TAG, "WallpaperService not running");
1702            throw new RuntimeException(new DeadSystemException());
1703        }
1704        try {
1705            return sGlobals.mService.isWallpaperBackupEligible(which, mContext.getUserId());
1706        } catch (RemoteException e) {
1707            Log.e(TAG, "Exception querying wallpaper backup eligibility: " + e.getMessage());
1708        }
1709        return false;
1710    }
1711
1712    // Private completion callback for setWallpaper() synchronization
1713    private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub {
1714        final CountDownLatch mLatch;
1715
1716        public WallpaperSetCompletion() {
1717            mLatch = new CountDownLatch(1);
1718        }
1719
1720        public void waitForCompletion() {
1721            try {
1722                mLatch.await(30, TimeUnit.SECONDS);
1723            } catch (InterruptedException e) {
1724                // This might be legit: the crop may take a very long time. Don't sweat
1725                // it in that case; we are okay with display lagging behind in order to
1726                // keep the caller from locking up indeterminately.
1727            }
1728        }
1729
1730        @Override
1731        public void onWallpaperChanged() throws RemoteException {
1732            mLatch.countDown();
1733        }
1734    }
1735}
1736