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