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