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