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