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