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 */
17package com.android.wallpapercropper;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.WallpaperManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.SharedPreferences;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.Bitmap;
28import android.graphics.Bitmap.CompressFormat;
29import android.graphics.BitmapFactory;
30import android.graphics.BitmapRegionDecoder;
31import android.graphics.Canvas;
32import android.graphics.Matrix;
33import android.graphics.Paint;
34import android.graphics.Point;
35import android.graphics.Rect;
36import android.graphics.RectF;
37import android.net.Uri;
38import android.os.AsyncTask;
39import android.os.Bundle;
40import android.util.FloatMath;
41import android.util.Log;
42import android.view.Display;
43import android.view.View;
44import android.view.WindowManager;
45
46import com.android.gallery3d.common.Utils;
47import com.android.gallery3d.exif.ExifInterface;
48import com.android.photos.BitmapRegionTileSource;
49
50import java.io.BufferedInputStream;
51import java.io.ByteArrayInputStream;
52import java.io.ByteArrayOutputStream;
53import java.io.FileNotFoundException;
54import java.io.IOException;
55import java.io.InputStream;
56
57public 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