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