Home | History | Annotate | Download | only in launcher3
      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 
     17 package com.android.launcher3;
     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     protected 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.setEnabled(false);
    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.setEnabled(true);
    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         } catch (NullPointerException e) {
    280             // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
    281             Log.w(LOGTAG, "Getting exif data failed", e);
    282         } finally {
    283             Utils.closeSilently(bis);
    284             Utils.closeSilently(is);
    285         }
    286         return 0;
    287     }
    288 
    289     protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
    290         int rotation = getRotationFromExif(filePath);
    291         BitmapCropTask cropTask = new BitmapCropTask(
    292                 this, filePath, null, rotation, 0, 0, true, false, null);
    293         final Point bounds = cropTask.getImageBounds();
    294         Runnable onEndCrop = new Runnable() {
    295             public void run() {
    296                 updateWallpaperDimensions(bounds.x, bounds.y);
    297                 if (finishActivityWhenDone) {
    298                     setResult(Activity.RESULT_OK);
    299                     finish();
    300                 }
    301             }
    302         };
    303         cropTask.setOnEndRunnable(onEndCrop);
    304         cropTask.setNoCrop(true);
    305         cropTask.execute();
    306     }
    307 
    308     protected void cropImageAndSetWallpaper(
    309             Resources res, int resId, final boolean finishActivityWhenDone) {
    310         // crop this image and scale it down to the default wallpaper size for
    311         // this device
    312         int rotation = getRotationFromExif(res, resId);
    313         Point inSize = mCropView.getSourceDimensions();
    314         Point outSize = getDefaultWallpaperSize(getResources(),
    315                 getWindowManager());
    316         RectF crop = getMaxCropRect(
    317                 inSize.x, inSize.y, outSize.x, outSize.y, false);
    318         Runnable onEndCrop = new Runnable() {
    319             public void run() {
    320                 // Passing 0, 0 will cause launcher to revert to using the
    321                 // default wallpaper size
    322                 updateWallpaperDimensions(0, 0);
    323                 if (finishActivityWhenDone) {
    324                     setResult(Activity.RESULT_OK);
    325                     finish();
    326                 }
    327             }
    328         };
    329         BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
    330                 crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
    331         cropTask.execute();
    332     }
    333 
    334     private static boolean isScreenLarge(Resources res) {
    335         Configuration config = res.getConfiguration();
    336         return config.smallestScreenWidthDp >= 720;
    337     }
    338 
    339     protected void cropImageAndSetWallpaper(Uri uri,
    340             OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
    341         boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
    342         // Get the crop
    343         boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
    344 
    345         Display d = getWindowManager().getDefaultDisplay();
    346 
    347         Point displaySize = new Point();
    348         d.getSize(displaySize);
    349         boolean isPortrait = displaySize.x < displaySize.y;
    350 
    351         Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(),
    352                 getWindowManager());
    353         // Get the crop
    354         RectF cropRect = mCropView.getCrop();
    355         int cropRotation = mCropView.getImageRotation();
    356         float cropScale = mCropView.getWidth() / (float) cropRect.width();
    357 
    358         Point inSize = mCropView.getSourceDimensions();
    359         Matrix rotateMatrix = new Matrix();
    360         rotateMatrix.setRotate(cropRotation);
    361         float[] rotatedInSize = new float[] { inSize.x, inSize.y };
    362         rotateMatrix.mapPoints(rotatedInSize);
    363         rotatedInSize[0] = Math.abs(rotatedInSize[0]);
    364         rotatedInSize[1] = Math.abs(rotatedInSize[1]);
    365 
    366         // ADJUST CROP WIDTH
    367         // Extend the crop all the way to the right, for parallax
    368         // (or all the way to the left, in RTL)
    369         float extraSpace;
    370         if (centerCrop) {
    371             extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left);
    372         } else {
    373             extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
    374         }
    375         // Cap the amount of extra width
    376         float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
    377         extraSpace = Math.min(extraSpace, maxExtraSpace);
    378 
    379         if (centerCrop) {
    380             cropRect.left -= extraSpace / 2f;
    381             cropRect.right += extraSpace / 2f;
    382         } else {
    383             if (ltr) {
    384                 cropRect.right += extraSpace;
    385             } else {
    386                 cropRect.left -= extraSpace;
    387             }
    388         }
    389 
    390         // ADJUST CROP HEIGHT
    391         if (isPortrait) {
    392             cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
    393         } else { // LANDSCAPE
    394             float extraPortraitHeight =
    395                     defaultWallpaperSize.y / cropScale - cropRect.height();
    396             float expandHeight =
    397                     Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
    398                             extraPortraitHeight / 2);
    399             cropRect.top -= expandHeight;
    400             cropRect.bottom += expandHeight;
    401         }
    402         final int outWidth = (int) Math.round(cropRect.width() * cropScale);
    403         final int outHeight = (int) Math.round(cropRect.height() * cropScale);
    404 
    405         Runnable onEndCrop = new Runnable() {
    406             public void run() {
    407                 updateWallpaperDimensions(outWidth, outHeight);
    408                 if (finishActivityWhenDone) {
    409                     setResult(Activity.RESULT_OK);
    410                     finish();
    411                 }
    412             }
    413         };
    414         BitmapCropTask cropTask = new BitmapCropTask(this, uri,
    415                 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
    416         if (onBitmapCroppedHandler != null) {
    417             cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
    418         }
    419         cropTask.execute();
    420     }
    421 
    422     public interface OnBitmapCroppedHandler {
    423         public void onBitmapCropped(byte[] imageBytes);
    424     }
    425 
    426     protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
    427         Uri mInUri = null;
    428         Context mContext;
    429         String mInFilePath;
    430         byte[] mInImageBytes;
    431         int mInResId = 0;
    432         RectF mCropBounds = null;
    433         int mOutWidth, mOutHeight;
    434         int mRotation;
    435         String mOutputFormat = "jpg"; // for now
    436         boolean mSetWallpaper;
    437         boolean mSaveCroppedBitmap;
    438         Bitmap mCroppedBitmap;
    439         Runnable mOnEndRunnable;
    440         Resources mResources;
    441         OnBitmapCroppedHandler mOnBitmapCroppedHandler;
    442         boolean mNoCrop;
    443 
    444         public BitmapCropTask(Context c, String filePath,
    445                 RectF cropBounds, int rotation, int outWidth, int outHeight,
    446                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
    447             mContext = c;
    448             mInFilePath = filePath;
    449             init(cropBounds, rotation,
    450                     outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
    451         }
    452 
    453         public BitmapCropTask(byte[] imageBytes,
    454                 RectF cropBounds, int rotation, int outWidth, int outHeight,
    455                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
    456             mInImageBytes = imageBytes;
    457             init(cropBounds, rotation,
    458                     outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
    459         }
    460 
    461         public BitmapCropTask(Context c, Uri inUri,
    462                 RectF cropBounds, int rotation, int outWidth, int outHeight,
    463                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
    464             mContext = c;
    465             mInUri = inUri;
    466             init(cropBounds, rotation,
    467                     outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
    468         }
    469 
    470         public BitmapCropTask(Context c, Resources res, int inResId,
    471                 RectF cropBounds, int rotation, int outWidth, int outHeight,
    472                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
    473             mContext = c;
    474             mInResId = inResId;
    475             mResources = res;
    476             init(cropBounds, rotation,
    477                     outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
    478         }
    479 
    480         private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
    481                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
    482             mCropBounds = cropBounds;
    483             mRotation = rotation;
    484             mOutWidth = outWidth;
    485             mOutHeight = outHeight;
    486             mSetWallpaper = setWallpaper;
    487             mSaveCroppedBitmap = saveCroppedBitmap;
    488             mOnEndRunnable = onEndRunnable;
    489         }
    490 
    491         public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
    492             mOnBitmapCroppedHandler = handler;
    493         }
    494 
    495         public void setNoCrop(boolean value) {
    496             mNoCrop = value;
    497         }
    498 
    499         public void setOnEndRunnable(Runnable onEndRunnable) {
    500             mOnEndRunnable = onEndRunnable;
    501         }
    502 
    503         // Helper to setup input stream
    504         private InputStream regenerateInputStream() {
    505             if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
    506                 Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
    507                         "image byte array given");
    508             } else {
    509                 try {
    510                     if (mInUri != null) {
    511                         return new BufferedInputStream(
    512                                 mContext.getContentResolver().openInputStream(mInUri));
    513                     } else if (mInFilePath != null) {
    514                         return mContext.openFileInput(mInFilePath);
    515                     } else if (mInImageBytes != null) {
    516                         return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
    517                     } else {
    518                         return new BufferedInputStream(mResources.openRawResource(mInResId));
    519                     }
    520                 } catch (FileNotFoundException e) {
    521                     Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
    522                 }
    523             }
    524             return null;
    525         }
    526 
    527         public Point getImageBounds() {
    528             InputStream is = regenerateInputStream();
    529             if (is != null) {
    530                 BitmapFactory.Options options = new BitmapFactory.Options();
    531                 options.inJustDecodeBounds = true;
    532                 BitmapFactory.decodeStream(is, null, options);
    533                 Utils.closeSilently(is);
    534                 if (options.outWidth != 0 && options.outHeight != 0) {
    535                     return new Point(options.outWidth, options.outHeight);
    536                 }
    537             }
    538             return null;
    539         }
    540 
    541         public void setCropBounds(RectF cropBounds) {
    542             mCropBounds = cropBounds;
    543         }
    544 
    545         public Bitmap getCroppedBitmap() {
    546             return mCroppedBitmap;
    547         }
    548         public boolean cropBitmap() {
    549             boolean failure = false;
    550 
    551 
    552             WallpaperManager wallpaperManager = null;
    553             if (mSetWallpaper) {
    554                 wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
    555             }
    556 
    557 
    558             if (mSetWallpaper && mNoCrop) {
    559                 try {
    560                     InputStream is = regenerateInputStream();
    561                     if (is != null) {
    562                         wallpaperManager.setStream(is);
    563                         Utils.closeSilently(is);
    564                     }
    565                 } catch (IOException e) {
    566                     Log.w(LOGTAG, "cannot write stream to wallpaper", e);
    567                     failure = true;
    568                 }
    569                 return !failure;
    570             } else {
    571                 // Find crop bounds (scaled to original image size)
    572                 Rect roundedTrueCrop = new Rect();
    573                 Matrix rotateMatrix = new Matrix();
    574                 Matrix inverseRotateMatrix = new Matrix();
    575 
    576                 Point bounds = getImageBounds();
    577                 if (mRotation > 0) {
    578                     rotateMatrix.setRotate(mRotation);
    579                     inverseRotateMatrix.setRotate(-mRotation);
    580 
    581                     mCropBounds.roundOut(roundedTrueCrop);
    582                     mCropBounds = new RectF(roundedTrueCrop);
    583 
    584                     if (bounds == null) {
    585                         Log.w(LOGTAG, "cannot get bounds for image");
    586                         failure = true;
    587                         return false;
    588                     }
    589 
    590                     float[] rotatedBounds = new float[] { bounds.x, bounds.y };
    591                     rotateMatrix.mapPoints(rotatedBounds);
    592                     rotatedBounds[0] = Math.abs(rotatedBounds[0]);
    593                     rotatedBounds[1] = Math.abs(rotatedBounds[1]);
    594 
    595                     mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
    596                     inverseRotateMatrix.mapRect(mCropBounds);
    597                     mCropBounds.offset(bounds.x/2, bounds.y/2);
    598 
    599                 }
    600 
    601                 mCropBounds.roundOut(roundedTrueCrop);
    602 
    603                 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
    604                     Log.w(LOGTAG, "crop has bad values for full size image");
    605                     failure = true;
    606                     return false;
    607                 }
    608 
    609                 // See how much we're reducing the size of the image
    610                 int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
    611                         roundedTrueCrop.height() / mOutHeight));
    612                 // Attempt to open a region decoder
    613                 BitmapRegionDecoder decoder = null;
    614                 InputStream is = null;
    615                 try {
    616                     is = regenerateInputStream();
    617                     if (is == null) {
    618                         Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
    619                         failure = true;
    620                         return false;
    621                     }
    622                     decoder = BitmapRegionDecoder.newInstance(is, false);
    623                     Utils.closeSilently(is);
    624                 } catch (IOException e) {
    625                     Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
    626                 } finally {
    627                    Utils.closeSilently(is);
    628                    is = null;
    629                 }
    630 
    631                 Bitmap crop = null;
    632                 if (decoder != null) {
    633                     // Do region decoding to get crop bitmap
    634                     BitmapFactory.Options options = new BitmapFactory.Options();
    635                     if (scaleDownSampleSize > 1) {
    636                         options.inSampleSize = scaleDownSampleSize;
    637                     }
    638                     crop = decoder.decodeRegion(roundedTrueCrop, options);
    639                     decoder.recycle();
    640                 }
    641 
    642                 if (crop == null) {
    643                     // BitmapRegionDecoder has failed, try to crop in-memory
    644                     is = regenerateInputStream();
    645                     Bitmap fullSize = null;
    646                     if (is != null) {
    647                         BitmapFactory.Options options = new BitmapFactory.Options();
    648                         if (scaleDownSampleSize > 1) {
    649                             options.inSampleSize = scaleDownSampleSize;
    650                         }
    651                         fullSize = BitmapFactory.decodeStream(is, null, options);
    652                         Utils.closeSilently(is);
    653                     }
    654                     if (fullSize != null) {
    655                         // Find out the true sample size that was used by the decoder
    656                         scaleDownSampleSize = bounds.x / fullSize.getWidth();
    657                         mCropBounds.left /= scaleDownSampleSize;
    658                         mCropBounds.top /= scaleDownSampleSize;
    659                         mCropBounds.bottom /= scaleDownSampleSize;
    660                         mCropBounds.right /= scaleDownSampleSize;
    661                         mCropBounds.roundOut(roundedTrueCrop);
    662 
    663                         // Adjust values to account for issues related to rounding
    664                         if (roundedTrueCrop.width() > fullSize.getWidth()) {
    665                             // Adjust the width
    666                             roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
    667                         }
    668                         if (roundedTrueCrop.right > fullSize.getWidth()) {
    669                             // Adjust the left value
    670                             int adjustment = roundedTrueCrop.left -
    671                                     Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
    672                             roundedTrueCrop.left -= adjustment;
    673                             roundedTrueCrop.right -= adjustment;
    674                         }
    675                         if (roundedTrueCrop.height() > fullSize.getHeight()) {
    676                             // Adjust the height
    677                             roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
    678                         }
    679                         if (roundedTrueCrop.bottom > fullSize.getHeight()) {
    680                             // Adjust the top value
    681                             int adjustment = roundedTrueCrop.top -
    682                                     Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
    683                             roundedTrueCrop.top -= adjustment;
    684                             roundedTrueCrop.bottom -= adjustment;
    685                         }
    686 
    687                         crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
    688                                 roundedTrueCrop.top, roundedTrueCrop.width(),
    689                                 roundedTrueCrop.height());
    690                     }
    691                 }
    692 
    693                 if (crop == null) {
    694                     Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
    695                     failure = true;
    696                     return false;
    697                 }
    698                 if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
    699                     float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
    700                     rotateMatrix.mapPoints(dimsAfter);
    701                     dimsAfter[0] = Math.abs(dimsAfter[0]);
    702                     dimsAfter[1] = Math.abs(dimsAfter[1]);
    703 
    704                     if (!(mOutWidth > 0 && mOutHeight > 0)) {
    705                         mOutWidth = Math.round(dimsAfter[0]);
    706                         mOutHeight = Math.round(dimsAfter[1]);
    707                     }
    708 
    709                     RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
    710                     RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
    711 
    712                     Matrix m = new Matrix();
    713                     if (mRotation == 0) {
    714                         m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    715                     } else {
    716                         Matrix m1 = new Matrix();
    717                         m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
    718                         Matrix m2 = new Matrix();
    719                         m2.setRotate(mRotation);
    720                         Matrix m3 = new Matrix();
    721                         m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
    722                         Matrix m4 = new Matrix();
    723                         m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    724 
    725                         Matrix c1 = new Matrix();
    726                         c1.setConcat(m2, m1);
    727                         Matrix c2 = new Matrix();
    728                         c2.setConcat(m4, m3);
    729                         m.setConcat(c2, c1);
    730                     }
    731 
    732                     Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
    733                             (int) returnRect.height(), Bitmap.Config.ARGB_8888);
    734                     if (tmp != null) {
    735                         Canvas c = new Canvas(tmp);
    736                         Paint p = new Paint();
    737                         p.setFilterBitmap(true);
    738                         c.drawBitmap(crop, m, p);
    739                         crop = tmp;
    740                     }
    741                 }
    742 
    743                 if (mSaveCroppedBitmap) {
    744                     mCroppedBitmap = crop;
    745                 }
    746 
    747                 // Get output compression format
    748                 CompressFormat cf =
    749                         convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
    750 
    751                 // Compress to byte array
    752                 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
    753                 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
    754                     // If we need to set to the wallpaper, set it
    755                     if (mSetWallpaper && wallpaperManager != null) {
    756                         try {
    757                             byte[] outByteArray = tmpOut.toByteArray();
    758                             wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
    759                             if (mOnBitmapCroppedHandler != null) {
    760                                 mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
    761                             }
    762                         } catch (IOException e) {
    763                             Log.w(LOGTAG, "cannot write stream to wallpaper", e);
    764                             failure = true;
    765                         }
    766                     }
    767                 } else {
    768                     Log.w(LOGTAG, "cannot compress bitmap");
    769                     failure = true;
    770                 }
    771             }
    772             return !failure; // True if any of the operations failed
    773         }
    774 
    775         @Override
    776         protected Boolean doInBackground(Void... params) {
    777             return cropBitmap();
    778         }
    779 
    780         @Override
    781         protected void onPostExecute(Boolean result) {
    782             if (mOnEndRunnable != null) {
    783                 mOnEndRunnable.run();
    784             }
    785         }
    786     }
    787 
    788     protected void updateWallpaperDimensions(int width, int height) {
    789         String spKey = getSharedPreferencesKey();
    790         SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
    791         SharedPreferences.Editor editor = sp.edit();
    792         if (width != 0 && height != 0) {
    793             editor.putInt(WALLPAPER_WIDTH_KEY, width);
    794             editor.putInt(WALLPAPER_HEIGHT_KEY, height);
    795         } else {
    796             editor.remove(WALLPAPER_WIDTH_KEY);
    797             editor.remove(WALLPAPER_HEIGHT_KEY);
    798         }
    799         editor.commit();
    800 
    801         suggestWallpaperDimension(getResources(),
    802                 sp, getWindowManager(), WallpaperManager.getInstance(this));
    803     }
    804 
    805     static public void suggestWallpaperDimension(Resources res,
    806             final SharedPreferences sharedPrefs,
    807             WindowManager windowManager,
    808             final WallpaperManager wallpaperManager) {
    809         final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
    810         // If we have saved a wallpaper width/height, use that instead
    811         int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
    812         int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
    813         if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
    814                 savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
    815             wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
    816         }
    817     }
    818 
    819     protected static RectF getMaxCropRect(
    820             int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
    821         RectF cropRect = new RectF();
    822         // Get a crop rect that will fit this
    823         if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
    824              cropRect.top = 0;
    825              cropRect.bottom = inHeight;
    826              cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
    827              cropRect.right = inWidth - cropRect.left;
    828              if (leftAligned) {
    829                  cropRect.right -= cropRect.left;
    830                  cropRect.left = 0;
    831              }
    832         } else {
    833             cropRect.left = 0;
    834             cropRect.right = inWidth;
    835             cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
    836             cropRect.bottom = inHeight - cropRect.top;
    837         }
    838         return cropRect;
    839     }
    840 
    841     protected static CompressFormat convertExtensionToCompressFormat(String extension) {
    842         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
    843     }
    844 
    845     protected static String getFileExtension(String requestFormat) {
    846         String outputFormat = (requestFormat == null)
    847                 ? "jpg"
    848                 : requestFormat;
    849         outputFormat = outputFormat.toLowerCase();
    850         return (outputFormat.equals("png") || outputFormat.equals("gif"))
    851                 ? "png" // We don't support gif compression.
    852                 : "jpg";
    853     }
    854 }
    855