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