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