WallpaperCropActivity.java revision e39c9a953ca11319b747b3aa79f4ccd082b775b7
1/*
2 * Copyright (C) 2013 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/* Copied from Launcher3 */
17package com.android.wallpapercropper;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.WallpaperManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.SharedPreferences;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.Bitmap;
28import android.graphics.Bitmap.CompressFormat;
29import android.graphics.BitmapFactory;
30import android.graphics.BitmapRegionDecoder;
31import android.graphics.Canvas;
32import android.graphics.Matrix;
33import android.graphics.Paint;
34import android.graphics.Point;
35import android.graphics.Rect;
36import android.graphics.RectF;
37import android.net.Uri;
38import android.os.AsyncTask;
39import android.os.Bundle;
40import android.util.Log;
41import android.view.Display;
42import android.view.View;
43import android.view.WindowManager;
44import android.widget.Toast;
45
46import com.android.gallery3d.common.Utils;
47import com.android.gallery3d.exif.ExifInterface;
48import com.android.photos.BitmapRegionTileSource;
49import com.android.photos.BitmapRegionTileSource.BitmapSource;
50
51import java.io.BufferedInputStream;
52import java.io.ByteArrayInputStream;
53import java.io.ByteArrayOutputStream;
54import java.io.FileNotFoundException;
55import java.io.IOException;
56import java.io.InputStream;
57
58public class WallpaperCropActivity extends Activity {
59    private static final String LOGTAG = "Launcher3.CropActivity";
60
61    protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
62    protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
63    private static final int DEFAULT_COMPRESS_QUALITY = 90;
64    /**
65     * The maximum bitmap size we allow to be returned through the intent.
66     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
67     * have some overhead to hit so that we go way below the limit here to make
68     * sure the intent stays below 1MB.We should consider just returning a byte
69     * array instead of a Bitmap instance to avoid overhead.
70     */
71    public static final int MAX_BMAP_IN_INTENT = 750000;
72    private static final float WALLPAPER_SCREENS_SPAN = 2f;
73
74    protected CropView mCropView;
75    protected Uri mUri;
76
77    @Override
78    protected void onCreate(Bundle savedInstanceState) {
79        super.onCreate(savedInstanceState);
80        init();
81        if (!enableRotation()) {
82            setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
83        }
84    }
85
86    protected void init() {
87        setContentView(R.layout.wallpaper_cropper);
88
89        mCropView = (CropView) findViewById(R.id.cropView);
90
91        Intent cropIntent = getIntent();
92        final Uri imageUri = cropIntent.getData();
93
94        if (imageUri == null) {
95            Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
96            finish();
97            return;
98        }
99
100        // Action bar
101        // Show the custom action bar view
102        final ActionBar actionBar = getActionBar();
103        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
104        actionBar.getCustomView().setOnClickListener(
105                new View.OnClickListener() {
106                    @Override
107                    public void onClick(View v) {
108                        boolean finishActivityWhenDone = true;
109                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
110                    }
111                });
112
113        // Load image in background
114        final BitmapRegionTileSource.UriBitmapSource bitmapSource =
115                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
116        Runnable onLoad = new Runnable() {
117            public void run() {
118                if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
119                    Toast.makeText(WallpaperCropActivity.this,
120                            getString(R.string.wallpaper_load_fail),
121                            Toast.LENGTH_LONG).show();
122                    finish();
123                }
124            }
125        };
126        setCropViewTileSource(bitmapSource, true, false, onLoad);
127    }
128
129    public void setCropViewTileSource(
130            final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
131            final boolean moveToLeft, final Runnable postExecute) {
132        final Context context = WallpaperCropActivity.this;
133        final View progressView = findViewById(R.id.loading);
134        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
135            protected Void doInBackground(Void...args) {
136                if (!isCancelled()) {
137                    bitmapSource.loadInBackground();
138                }
139                return null;
140            }
141            protected void onPostExecute(Void arg) {
142                if (!isCancelled()) {
143                    progressView.setVisibility(View.INVISIBLE);
144                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
145                        mCropView.setTileSource(
146                                new BitmapRegionTileSource(context, bitmapSource), null);
147                        mCropView.setTouchEnabled(touchEnabled);
148                        if (moveToLeft) {
149                            mCropView.moveToLeft();
150                        }
151                    }
152                }
153                if (postExecute != null) {
154                    postExecute.run();
155                }
156            }
157        };
158        // We don't want to show the spinner every time we load an image, because that would be
159        // annoying; instead, only start showing the spinner if loading the image has taken
160        // longer than 1 sec (ie 1000 ms)
161        progressView.postDelayed(new Runnable() {
162            public void run() {
163                if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
164                    progressView.setVisibility(View.VISIBLE);
165                }
166            }
167        }, 1000);
168        loadBitmapTask.execute();
169    }
170
171    public boolean enableRotation() {
172        return getResources().getBoolean(R.bool.allow_rotation);
173    }
174
175    public static String getSharedPreferencesKey() {
176        return WallpaperCropActivity.class.getName();
177    }
178
179    // As a ratio of screen height, the total distance we want the parallax effect to span
180    // horizontally
181    private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
182        float aspectRatio = width / (float) height;
183
184        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
185        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
186        // We will use these two data points to extrapolate how much the wallpaper parallax effect
187        // to span (ie travel) at any aspect ratio:
188
189        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
190        final float ASPECT_RATIO_PORTRAIT = 10/16f;
191        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
192        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
193
194        // To find out the desired width at different aspect ratios, we use the following two
195        // formulas, where the coefficient on x is the aspect ratio (width/height):
196        //   (16/10)x + y = 1.5
197        //   (10/16)x + y = 1.2
198        // We solve for x and y and end up with a final formula:
199        final float x =
200            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
201            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
202        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
203        return x * aspectRatio + y;
204    }
205
206    static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
207        Point minDims = new Point();
208        Point maxDims = new Point();
209        windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
210
211        int maxDim = Math.max(maxDims.x, maxDims.y);
212        int minDim = Math.max(minDims.x, minDims.y);
213
214        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
215            Point realSize = new Point();
216            windowManager.getDefaultDisplay().getRealSize(realSize);
217            maxDim = Math.max(realSize.x, realSize.y);
218            minDim = Math.min(realSize.x, realSize.y);
219        }
220
221        // We need to ensure that there is enough extra space in the wallpaper
222        // for the intended
223        // parallax effects
224        final int defaultWidth, defaultHeight;
225        if (isScreenLarge(res)) {
226            defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
227            defaultHeight = maxDim;
228        } else {
229            defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
230            defaultHeight = maxDim;
231        }
232        return new Point(defaultWidth, defaultHeight);
233    }
234
235    public static int getRotationFromExif(String path) {
236        return getRotationFromExifHelper(path, null, 0, null, null);
237    }
238
239    public static int getRotationFromExif(Context context, Uri uri) {
240        return getRotationFromExifHelper(null, null, 0, context, uri);
241    }
242
243    public static int getRotationFromExif(Resources res, int resId) {
244        return getRotationFromExifHelper(null, res, resId, null, null);
245    }
246
247    private static int getRotationFromExifHelper(
248            String path, Resources res, int resId, Context context, Uri uri) {
249        ExifInterface ei = new ExifInterface();
250        InputStream is = null;
251        BufferedInputStream bis = null;
252        try {
253            if (path != null) {
254                ei.readExif(path);
255            } else if (uri != null) {
256                is = context.getContentResolver().openInputStream(uri);
257                bis = new BufferedInputStream(is);
258                ei.readExif(bis);
259            } else {
260                is = res.openRawResource(resId);
261                bis = new BufferedInputStream(is);
262                ei.readExif(bis);
263            }
264            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
265            if (ori != null) {
266                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
267            }
268        } catch (IOException e) {
269            Log.w(LOGTAG, "Getting exif data failed", e);
270        } finally {
271            Utils.closeSilently(bis);
272            Utils.closeSilently(is);
273        }
274        return 0;
275    }
276
277    protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
278        int rotation = getRotationFromExif(filePath);
279        BitmapCropTask cropTask = new BitmapCropTask(
280                this, filePath, null, rotation, 0, 0, true, false, null);
281        final Point bounds = cropTask.getImageBounds();
282        Runnable onEndCrop = new Runnable() {
283            public void run() {
284                updateWallpaperDimensions(bounds.x, bounds.y);
285                if (finishActivityWhenDone) {
286                    setResult(Activity.RESULT_OK);
287                    finish();
288                }
289            }
290        };
291        cropTask.setOnEndRunnable(onEndCrop);
292        cropTask.setNoCrop(true);
293        cropTask.execute();
294    }
295
296    protected void cropImageAndSetWallpaper(
297            Resources res, int resId, final boolean finishActivityWhenDone) {
298        // crop this image and scale it down to the default wallpaper size for
299        // this device
300        int rotation = getRotationFromExif(res, resId);
301        Point inSize = mCropView.getSourceDimensions();
302        Point outSize = getDefaultWallpaperSize(getResources(),
303                getWindowManager());
304        RectF crop = getMaxCropRect(
305                inSize.x, inSize.y, outSize.x, outSize.y, false);
306        Runnable onEndCrop = new Runnable() {
307            public void run() {
308                // Passing 0, 0 will cause launcher to revert to using the
309                // default wallpaper size
310                updateWallpaperDimensions(0, 0);
311                if (finishActivityWhenDone) {
312                    setResult(Activity.RESULT_OK);
313                    finish();
314                }
315            }
316        };
317        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
318                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
319        cropTask.execute();
320    }
321
322    private static boolean isScreenLarge(Resources res) {
323        Configuration config = res.getConfiguration();
324        return config.smallestScreenWidthDp >= 720;
325    }
326
327    protected void cropImageAndSetWallpaper(Uri uri,
328            OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
329        // Get the crop
330        boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
331
332
333        Display d = getWindowManager().getDefaultDisplay();
334
335        Point displaySize = new Point();
336        d.getSize(displaySize);
337        boolean isPortrait = displaySize.x < displaySize.y;
338
339        Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(),
340                getWindowManager());
341        // Get the crop
342        RectF cropRect = mCropView.getCrop();
343        int cropRotation = mCropView.getImageRotation();
344        float cropScale = mCropView.getWidth() / (float) cropRect.width();
345
346        Point inSize = mCropView.getSourceDimensions();
347        Matrix rotateMatrix = new Matrix();
348        rotateMatrix.setRotate(cropRotation);
349        float[] rotatedInSize = new float[] { inSize.x, inSize.y };
350        rotateMatrix.mapPoints(rotatedInSize);
351        rotatedInSize[0] = Math.abs(rotatedInSize[0]);
352        rotatedInSize[1] = Math.abs(rotatedInSize[1]);
353
354        // ADJUST CROP WIDTH
355        // Extend the crop all the way to the right, for parallax
356        // (or all the way to the left, in RTL)
357        float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
358        // Cap the amount of extra width
359        float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
360        extraSpace = Math.min(extraSpace, maxExtraSpace);
361
362        if (ltr) {
363            cropRect.right += extraSpace;
364        } else {
365            cropRect.left -= extraSpace;
366        }
367
368        // ADJUST CROP HEIGHT
369        if (isPortrait) {
370            cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
371        } else { // LANDSCAPE
372            float extraPortraitHeight =
373                    defaultWallpaperSize.y / cropScale - cropRect.height();
374            float expandHeight =
375                    Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
376                            extraPortraitHeight / 2);
377            cropRect.top -= expandHeight;
378            cropRect.bottom += expandHeight;
379        }
380        final int outWidth = (int) Math.round(cropRect.width() * cropScale);
381        final int outHeight = (int) Math.round(cropRect.height() * cropScale);
382
383        Runnable onEndCrop = new Runnable() {
384            public void run() {
385                updateWallpaperDimensions(outWidth, outHeight);
386                if (finishActivityWhenDone) {
387                    setResult(Activity.RESULT_OK);
388                    finish();
389                }
390            }
391        };
392        BitmapCropTask cropTask = new BitmapCropTask(this, uri,
393                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
394        if (onBitmapCroppedHandler != null) {
395            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
396        }
397        cropTask.execute();
398    }
399
400    public interface OnBitmapCroppedHandler {
401        public void onBitmapCropped(byte[] imageBytes);
402    }
403
404    protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
405        Uri mInUri = null;
406        Context mContext;
407        String mInFilePath;
408        byte[] mInImageBytes;
409        int mInResId = 0;
410        RectF mCropBounds = null;
411        int mOutWidth, mOutHeight;
412        int mRotation;
413        String mOutputFormat = "jpg"; // for now
414        boolean mSetWallpaper;
415        boolean mSaveCroppedBitmap;
416        Bitmap mCroppedBitmap;
417        Runnable mOnEndRunnable;
418        Resources mResources;
419        OnBitmapCroppedHandler mOnBitmapCroppedHandler;
420        boolean mNoCrop;
421
422        public BitmapCropTask(Context c, String filePath,
423                RectF cropBounds, int rotation, int outWidth, int outHeight,
424                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
425            mContext = c;
426            mInFilePath = filePath;
427            init(cropBounds, rotation,
428                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
429        }
430
431        public BitmapCropTask(byte[] imageBytes,
432                RectF cropBounds, int rotation, int outWidth, int outHeight,
433                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
434            mInImageBytes = imageBytes;
435            init(cropBounds, rotation,
436                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
437        }
438
439        public BitmapCropTask(Context c, Uri inUri,
440                RectF cropBounds, int rotation, int outWidth, int outHeight,
441                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
442            mContext = c;
443            mInUri = inUri;
444            init(cropBounds, rotation,
445                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
446        }
447
448        public BitmapCropTask(Context c, Resources res, int inResId,
449                RectF cropBounds, int rotation, int outWidth, int outHeight,
450                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
451            mContext = c;
452            mInResId = inResId;
453            mResources = res;
454            init(cropBounds, rotation,
455                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
456        }
457
458        private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
459                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
460            mCropBounds = cropBounds;
461            mRotation = rotation;
462            mOutWidth = outWidth;
463            mOutHeight = outHeight;
464            mSetWallpaper = setWallpaper;
465            mSaveCroppedBitmap = saveCroppedBitmap;
466            mOnEndRunnable = onEndRunnable;
467        }
468
469        public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
470            mOnBitmapCroppedHandler = handler;
471        }
472
473        public void setNoCrop(boolean value) {
474            mNoCrop = value;
475        }
476
477        public void setOnEndRunnable(Runnable onEndRunnable) {
478            mOnEndRunnable = onEndRunnable;
479        }
480
481        // Helper to setup input stream
482        private InputStream regenerateInputStream() {
483            if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
484                Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
485                        "image byte array given");
486            } else {
487                try {
488                    if (mInUri != null) {
489                        return new BufferedInputStream(
490                                mContext.getContentResolver().openInputStream(mInUri));
491                    } else if (mInFilePath != null) {
492                        return mContext.openFileInput(mInFilePath);
493                    } else if (mInImageBytes != null) {
494                        return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
495                    } else {
496                        return new BufferedInputStream(mResources.openRawResource(mInResId));
497                    }
498                } catch (FileNotFoundException e) {
499                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
500                }
501            }
502            return null;
503        }
504
505        public Point getImageBounds() {
506            InputStream is = regenerateInputStream();
507            if (is != null) {
508                BitmapFactory.Options options = new BitmapFactory.Options();
509                options.inJustDecodeBounds = true;
510                BitmapFactory.decodeStream(is, null, options);
511                Utils.closeSilently(is);
512                if (options.outWidth != 0 && options.outHeight != 0) {
513                    return new Point(options.outWidth, options.outHeight);
514                }
515            }
516            return null;
517        }
518
519        public void setCropBounds(RectF cropBounds) {
520            mCropBounds = cropBounds;
521        }
522
523        public Bitmap getCroppedBitmap() {
524            return mCroppedBitmap;
525        }
526        public boolean cropBitmap() {
527            boolean failure = false;
528
529
530            WallpaperManager wallpaperManager = null;
531            if (mSetWallpaper) {
532                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
533            }
534
535
536            if (mSetWallpaper && mNoCrop) {
537                try {
538                    InputStream is = regenerateInputStream();
539                    if (is != null) {
540                        wallpaperManager.setStream(is);
541                        Utils.closeSilently(is);
542                    }
543                } catch (IOException e) {
544                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
545                    failure = true;
546                }
547                return !failure;
548            } else {
549                // Find crop bounds (scaled to original image size)
550                Rect roundedTrueCrop = new Rect();
551                Matrix rotateMatrix = new Matrix();
552                Matrix inverseRotateMatrix = new Matrix();
553
554                Point bounds = getImageBounds();
555                if (mRotation > 0) {
556                    rotateMatrix.setRotate(mRotation);
557                    inverseRotateMatrix.setRotate(-mRotation);
558
559                    mCropBounds.roundOut(roundedTrueCrop);
560                    mCropBounds = new RectF(roundedTrueCrop);
561
562                    if (bounds == null) {
563                        Log.w(LOGTAG, "cannot get bounds for image");
564                        failure = true;
565                        return false;
566                    }
567
568                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
569                    rotateMatrix.mapPoints(rotatedBounds);
570                    rotatedBounds[0] = Math.abs(rotatedBounds[0]);
571                    rotatedBounds[1] = Math.abs(rotatedBounds[1]);
572
573                    mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
574                    inverseRotateMatrix.mapRect(mCropBounds);
575                    mCropBounds.offset(bounds.x/2, bounds.y/2);
576
577                }
578
579                mCropBounds.roundOut(roundedTrueCrop);
580
581                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
582                    Log.w(LOGTAG, "crop has bad values for full size image");
583                    failure = true;
584                    return false;
585                }
586
587                // See how much we're reducing the size of the image
588                int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
589                        roundedTrueCrop.height() / mOutHeight));
590                // Attempt to open a region decoder
591                BitmapRegionDecoder decoder = null;
592                InputStream is = null;
593                try {
594                    is = regenerateInputStream();
595                    if (is == null) {
596                        Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
597                        failure = true;
598                        return false;
599                    }
600                    decoder = BitmapRegionDecoder.newInstance(is, false);
601                    Utils.closeSilently(is);
602                } catch (IOException e) {
603                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
604                } finally {
605                   Utils.closeSilently(is);
606                   is = null;
607                }
608
609                Bitmap crop = null;
610                if (decoder != null) {
611                    // Do region decoding to get crop bitmap
612                    BitmapFactory.Options options = new BitmapFactory.Options();
613                    if (scaleDownSampleSize > 1) {
614                        options.inSampleSize = scaleDownSampleSize;
615                    }
616                    crop = decoder.decodeRegion(roundedTrueCrop, options);
617                    decoder.recycle();
618                }
619
620                if (crop == null) {
621                    // BitmapRegionDecoder has failed, try to crop in-memory
622                    is = regenerateInputStream();
623                    Bitmap fullSize = null;
624                    if (is != null) {
625                        BitmapFactory.Options options = new BitmapFactory.Options();
626                        if (scaleDownSampleSize > 1) {
627                            options.inSampleSize = scaleDownSampleSize;
628                        }
629                        fullSize = BitmapFactory.decodeStream(is, null, options);
630                        Utils.closeSilently(is);
631                    }
632                    if (fullSize != null) {
633                        // Find out the true sample size that was used by the decoder
634                        scaleDownSampleSize = bounds.x / fullSize.getWidth();
635                        mCropBounds.left /= scaleDownSampleSize;
636                        mCropBounds.top /= scaleDownSampleSize;
637                        mCropBounds.bottom /= scaleDownSampleSize;
638                        mCropBounds.right /= scaleDownSampleSize;
639                        mCropBounds.roundOut(roundedTrueCrop);
640
641                        // Adjust values to account for issues related to rounding
642                        if (roundedTrueCrop.width() > fullSize.getWidth()) {
643                            // Adjust the width
644                            roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
645                        }
646                        if (roundedTrueCrop.right > fullSize.getWidth()) {
647                            // Adjust the left value
648                            int adjustment = roundedTrueCrop.left -
649                                    Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
650                            roundedTrueCrop.left -= adjustment;
651                            roundedTrueCrop.right -= adjustment;
652                        }
653                        if (roundedTrueCrop.height() > fullSize.getHeight()) {
654                            // Adjust the height
655                            roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
656                        }
657                        if (roundedTrueCrop.bottom > fullSize.getHeight()) {
658                            // Adjust the top value
659                            int adjustment = roundedTrueCrop.top -
660                                    Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
661                            roundedTrueCrop.top -= adjustment;
662                            roundedTrueCrop.bottom -= adjustment;
663                        }
664
665                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
666                                roundedTrueCrop.top, roundedTrueCrop.width(),
667                                roundedTrueCrop.height());
668                    }
669                }
670
671                if (crop == null) {
672                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
673                    failure = true;
674                    return false;
675                }
676                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
677                    float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
678                    rotateMatrix.mapPoints(dimsAfter);
679                    dimsAfter[0] = Math.abs(dimsAfter[0]);
680                    dimsAfter[1] = Math.abs(dimsAfter[1]);
681
682                    if (!(mOutWidth > 0 && mOutHeight > 0)) {
683                        mOutWidth = Math.round(dimsAfter[0]);
684                        mOutHeight = Math.round(dimsAfter[1]);
685                    }
686
687                    RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
688                    RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
689
690                    Matrix m = new Matrix();
691                    if (mRotation == 0) {
692                        m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
693                    } else {
694                        Matrix m1 = new Matrix();
695                        m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
696                        Matrix m2 = new Matrix();
697                        m2.setRotate(mRotation);
698                        Matrix m3 = new Matrix();
699                        m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
700                        Matrix m4 = new Matrix();
701                        m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
702
703                        Matrix c1 = new Matrix();
704                        c1.setConcat(m2, m1);
705                        Matrix c2 = new Matrix();
706                        c2.setConcat(m4, m3);
707                        m.setConcat(c2, c1);
708                    }
709
710                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
711                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
712                    if (tmp != null) {
713                        Canvas c = new Canvas(tmp);
714                        Paint p = new Paint();
715                        p.setFilterBitmap(true);
716                        c.drawBitmap(crop, m, p);
717                        crop = tmp;
718                    }
719                }
720
721                if (mSaveCroppedBitmap) {
722                    mCroppedBitmap = crop;
723                }
724
725                // Get output compression format
726                CompressFormat cf =
727                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
728
729                // Compress to byte array
730                ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
731                if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
732                    // If we need to set to the wallpaper, set it
733                    if (mSetWallpaper && wallpaperManager != null) {
734                        try {
735                            byte[] outByteArray = tmpOut.toByteArray();
736                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
737                            if (mOnBitmapCroppedHandler != null) {
738                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
739                            }
740                        } catch (IOException e) {
741                            Log.w(LOGTAG, "cannot write stream to wallpaper", e);
742                            failure = true;
743                        }
744                    }
745                } else {
746                    Log.w(LOGTAG, "cannot compress bitmap");
747                    failure = true;
748                }
749            }
750            return !failure; // True if any of the operations failed
751        }
752
753        @Override
754        protected Boolean doInBackground(Void... params) {
755            return cropBitmap();
756        }
757
758        @Override
759        protected void onPostExecute(Boolean result) {
760            if (mOnEndRunnable != null) {
761                mOnEndRunnable.run();
762            }
763        }
764    }
765
766    protected void updateWallpaperDimensions(int width, int height) {
767        String spKey = getSharedPreferencesKey();
768        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
769        SharedPreferences.Editor editor = sp.edit();
770        if (width != 0 && height != 0) {
771            editor.putInt(WALLPAPER_WIDTH_KEY, width);
772            editor.putInt(WALLPAPER_HEIGHT_KEY, height);
773        } else {
774            editor.remove(WALLPAPER_WIDTH_KEY);
775            editor.remove(WALLPAPER_HEIGHT_KEY);
776        }
777        editor.commit();
778
779        suggestWallpaperDimension(getResources(),
780                sp, getWindowManager(), WallpaperManager.getInstance(this));
781    }
782
783    static public void suggestWallpaperDimension(Resources res,
784            final SharedPreferences sharedPrefs,
785            WindowManager windowManager,
786            final WallpaperManager wallpaperManager) {
787        final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
788
789        new AsyncTask<Void, Void, Void>() {
790            public Void doInBackground(Void ... args) {
791                // If we have saved a wallpaper width/height, use that instead
792                int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
793                int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
794                wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
795                return null;
796            }
797        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
798    }
799
800    protected static RectF getMaxCropRect(
801            int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
802        RectF cropRect = new RectF();
803        // Get a crop rect that will fit this
804        if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
805             cropRect.top = 0;
806             cropRect.bottom = inHeight;
807             cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
808             cropRect.right = inWidth - cropRect.left;
809             if (leftAligned) {
810                 cropRect.right -= cropRect.left;
811                 cropRect.left = 0;
812             }
813        } else {
814            cropRect.left = 0;
815            cropRect.right = inWidth;
816            cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
817            cropRect.bottom = inHeight - cropRect.top;
818        }
819        return cropRect;
820    }
821
822    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
823        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
824    }
825
826    protected static String getFileExtension(String requestFormat) {
827        String outputFormat = (requestFormat == null)
828                ? "jpg"
829                : requestFormat;
830        outputFormat = outputFormat.toLowerCase();
831        return (outputFormat.equals("png") || outputFormat.equals("gif"))
832                ? "png" // We don't support gif compression.
833                : "jpg";
834    }
835}
836