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