1104c45677660586026a7e74ef8c47d396403d50eMichael Jurka/*
2104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * Copyright (C) 2013 The Android Open Source Project
3104c45677660586026a7e74ef8c47d396403d50eMichael Jurka *
4104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * Licensed under the Apache License, Version 2.0 (the "License");
5104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * you may not use this file except in compliance with the License.
6104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * You may obtain a copy of the License at
7104c45677660586026a7e74ef8c47d396403d50eMichael Jurka *
8104c45677660586026a7e74ef8c47d396403d50eMichael Jurka *      http://www.apache.org/licenses/LICENSE-2.0
9104c45677660586026a7e74ef8c47d396403d50eMichael Jurka *
10104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * Unless required by applicable law or agreed to in writing, software
11104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * distributed under the License is distributed on an "AS IS" BASIS,
12104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * See the License for the specific language governing permissions and
14104c45677660586026a7e74ef8c47d396403d50eMichael Jurka * limitations under the License.
15104c45677660586026a7e74ef8c47d396403d50eMichael Jurka */
16104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
17104c45677660586026a7e74ef8c47d396403d50eMichael Jurkapackage com.android.launcher3;
18104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
192d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport android.app.ActionBar;
20104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.app.Activity;
21104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.app.WallpaperManager;
22104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.content.Context;
232d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport android.content.Intent;
24104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.content.SharedPreferences;
25adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurkaimport android.content.res.Configuration;
26eadbfc564d84aaf1d800da3d0d6edf6312f89648Michael Jurkaimport android.content.res.Resources;
27104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Bitmap;
28104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Bitmap.CompressFormat;
29104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.BitmapFactory;
30104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.BitmapRegionDecoder;
31104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Canvas;
32104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Matrix;
33104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Paint;
34104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Point;
35104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.Rect;
36104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.graphics.RectF;
37104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.net.Uri;
38104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.os.AsyncTask;
392d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport android.os.Bundle;
408713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurkaimport android.util.FloatMath;
41104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport android.util.Log;
422d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport android.view.Display;
432d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport android.view.View;
442d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport android.view.WindowManager;
45104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
46104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport com.android.gallery3d.common.Utils;
478713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurkaimport com.android.gallery3d.exif.ExifInterface;
482d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurkaimport com.android.photos.BitmapRegionTileSource;
49104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
50104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport java.io.BufferedInputStream;
51104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport java.io.ByteArrayInputStream;
52104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport java.io.ByteArrayOutputStream;
53104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport java.io.FileNotFoundException;
54104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport java.io.IOException;
55104c45677660586026a7e74ef8c47d396403d50eMichael Jurkaimport java.io.InputStream;
56104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
57104c45677660586026a7e74ef8c47d396403d50eMichael Jurkapublic class WallpaperCropActivity extends Activity {
58104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    private static final String LOGTAG = "Launcher3.CropActivity";
59104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
60104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
61104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
62104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    private static final int DEFAULT_COMPRESS_QUALITY = 90;
63104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    /**
64104c45677660586026a7e74ef8c47d396403d50eMichael Jurka     * The maximum bitmap size we allow to be returned through the intent.
65104c45677660586026a7e74ef8c47d396403d50eMichael Jurka     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
66104c45677660586026a7e74ef8c47d396403d50eMichael Jurka     * have some overhead to hit so that we go way below the limit here to make
67104c45677660586026a7e74ef8c47d396403d50eMichael Jurka     * sure the intent stays below 1MB.We should consider just returning a byte
68104c45677660586026a7e74ef8c47d396403d50eMichael Jurka     * array instead of a Bitmap instance to avoid overhead.
69104c45677660586026a7e74ef8c47d396403d50eMichael Jurka     */
70104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    public static final int MAX_BMAP_IN_INTENT = 750000;
712d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    private static final float WALLPAPER_SCREENS_SPAN = 2f;
72104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
732d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    protected CropView mCropView;
742d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    protected Uri mUri;
752d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
762d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    @Override
772d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    protected void onCreate(Bundle savedInstanceState) {
782d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        super.onCreate(savedInstanceState);
792d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        init();
807267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka        if (!enableRotation()) {
817267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka            setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
827267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka        }
832d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
842d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
852d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    protected void init() {
862d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        setContentView(R.layout.wallpaper_cropper);
872d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
882d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        mCropView = (CropView) findViewById(R.id.cropView);
892d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
90f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka        Intent cropIntent = getIntent();
912d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final Uri imageUri = cropIntent.getData();
922d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
93f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka        if (imageUri == null) {
94f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka            Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
95f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka            finish();
96f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka            return;
97f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka        }
98f6502c316c602e44fd5a1706013d7123a9eb1022Michael Jurka
998713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        int rotation = getRotationFromExif(this, imageUri);
1008713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
1012d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        mCropView.setTouchEnabled(true);
1022d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // Action bar
1032d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // Show the custom action bar view
1042d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final ActionBar actionBar = getActionBar();
1052d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
1062d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        actionBar.getCustomView().setOnClickListener(
1072d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                new View.OnClickListener() {
1082d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    @Override
1092d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    public void onClick(View v) {
1102d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                        boolean finishActivityWhenDone = true;
111be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
1122d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    }
1132d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                });
1147267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka    }
1157267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka
1167267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka    public boolean enableRotation() {
1177267fa58693549894999db17359ce26f8bc7b4a7Michael Jurka        return getResources().getBoolean(R.bool.allow_rotation);
1182d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
1192d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
120adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka    public static String getSharedPreferencesKey() {
121adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka        return WallpaperCropActivity.class.getName();
122adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka    }
123adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka
1242d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    // As a ratio of screen height, the total distance we want the parallax effect to span
1252d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    // horizontally
1262d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
1272d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        float aspectRatio = width / (float) height;
1282d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1292d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
1302d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
1312d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // We will use these two data points to extrapolate how much the wallpaper parallax effect
1322d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // to span (ie travel) at any aspect ratio:
1332d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1342d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
1352d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final float ASPECT_RATIO_PORTRAIT = 10/16f;
1362d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
1372d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
1382d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1392d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // To find out the desired width at different aspect ratios, we use the following two
1402d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // formulas, where the coefficient on x is the aspect ratio (width/height):
1412d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        //   (16/10)x + y = 1.5
1422d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        //   (10/16)x + y = 1.2
1432d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // We solve for x and y and end up with a final formula:
1442d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final float x =
1452d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
1462d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
1472d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
1482d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        return x * aspectRatio + y;
1492d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
1502d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1512d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
1522d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point minDims = new Point();
1532d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point maxDims = new Point();
1542d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
1552d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1562d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        int maxDim = Math.max(maxDims.x, maxDims.y);
15788f33511b8f84521aeb285114b70ccb938d1bf81Michael Jurka        int minDim = Math.max(minDims.x, minDims.y);
1582d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1592d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
1602d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            Point realSize = new Point();
1612d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            windowManager.getDefaultDisplay().getRealSize(realSize);
1622d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            maxDim = Math.max(realSize.x, realSize.y);
16388f33511b8f84521aeb285114b70ccb938d1bf81Michael Jurka            minDim = Math.min(realSize.x, realSize.y);
1642d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
1652d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1662d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // We need to ensure that there is enough extra space in the wallpaper
1672d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // for the intended
1682d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // parallax effects
1692d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final int defaultWidth, defaultHeight;
170adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka        if (isScreenLarge(res)) {
1712d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
1722d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            defaultHeight = maxDim;
1732d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        } else {
1742d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
1752d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            defaultHeight = maxDim;
1762d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
1772d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        return new Point(defaultWidth, defaultHeight);
1782d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
1792d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
1808713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    public static int getRotationFromExif(String path) {
1818713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        return getRotationFromExifHelper(path, null, 0, null, null);
1828713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    }
1838713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
1848713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    public static int getRotationFromExif(Context context, Uri uri) {
1858713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        return getRotationFromExifHelper(null, null, 0, context, uri);
1868713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    }
1878713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
1888713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    public static int getRotationFromExif(Resources res, int resId) {
1898713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        return getRotationFromExifHelper(null, res, resId, null, null);
1908713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    }
1918713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
1928713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    private static int getRotationFromExifHelper(
1938713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            String path, Resources res, int resId, Context context, Uri uri) {
1948713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        ExifInterface ei = new ExifInterface();
1958713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        try {
1968713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            if (path != null) {
1978713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                ei.readExif(path);
1988713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            } else if (uri != null) {
1998713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                InputStream is = context.getContentResolver().openInputStream(uri);
2008713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                BufferedInputStream bis = new BufferedInputStream(is);
2018713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                ei.readExif(bis);
2028713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            } else {
2038713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                InputStream is = res.openRawResource(resId);
2048713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                BufferedInputStream bis = new BufferedInputStream(is);
2058713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                ei.readExif(bis);
2068713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            }
2078713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
2088713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            if (ori != null) {
2098713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
2108713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            }
2118713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        } catch (IOException e) {
2128713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            Log.w(LOGTAG, "Getting exif data failed", e);
2138713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        }
2148713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        return 0;
2158713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka    }
2168713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
217be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
2188713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        int rotation = getRotationFromExif(filePath);
2198713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        BitmapCropTask cropTask = new BitmapCropTask(
2208713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                this, filePath, null, rotation, 0, 0, true, false, null);
221be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        final Point bounds = cropTask.getImageBounds();
222be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        Runnable onEndCrop = new Runnable() {
223be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            public void run() {
224be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                updateWallpaperDimensions(bounds.x, bounds.y);
225be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                if (finishActivityWhenDone) {
226be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                    setResult(Activity.RESULT_OK);
227be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                    finish();
228be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                }
229be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            }
230be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        };
231be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        cropTask.setOnEndRunnable(onEndCrop);
232be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        cropTask.setNoCrop(true);
233be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        cropTask.execute();
234be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    }
235be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
236be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    protected void cropImageAndSetWallpaper(
237be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            Resources res, int resId, final boolean finishActivityWhenDone) {
2382d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // crop this image and scale it down to the default wallpaper size for
2392d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // this device
2408713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        int rotation = getRotationFromExif(res, resId);
2412d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point inSize = mCropView.getSourceDimensions();
2422d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point outSize = getDefaultWallpaperSize(getResources(),
2432d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                getWindowManager());
2442d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        RectF crop = getMaxCropRect(
245be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                inSize.x, inSize.y, outSize.x, outSize.y, false);
2462d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Runnable onEndCrop = new Runnable() {
2472d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            public void run() {
2482d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                // Passing 0, 0 will cause launcher to revert to using the
2492d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                // default wallpaper size
2502d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                updateWallpaperDimensions(0, 0);
2512d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                if (finishActivityWhenDone) {
2522d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    setResult(Activity.RESULT_OK);
2532d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    finish();
2542d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                }
2552d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            }
2562d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        };
2571e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
2588713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
2592d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        cropTask.execute();
2602d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
2612d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
262adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka    private static boolean isScreenLarge(Resources res) {
263adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka        Configuration config = res.getConfiguration();
264adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka        return config.smallestScreenWidthDp >= 720;
265adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka    }
266adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka
267be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    protected void cropImageAndSetWallpaper(Uri uri,
268be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
269fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        // Get the crop
270fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
271fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka
2722d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point minDims = new Point();
2732d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point maxDims = new Point();
2742d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Display d = getWindowManager().getDefaultDisplay();
2752d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        d.getCurrentSizeRange(minDims, maxDims);
2762d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
2772d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Point displaySize = new Point();
2782d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        d.getSize(displaySize);
2792d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
2802d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        int maxDim = Math.max(maxDims.x, maxDims.y);
2812d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final int minDim = Math.min(minDims.x, minDims.y);
282fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        int defaultWallpaperWidth;
283adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka        if (isScreenLarge(getResources())) {
284fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka            defaultWallpaperWidth = (int) (maxDim *
2852d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    wallpaperTravelToScreenWidthRatio(maxDim, minDim));
2862d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        } else {
287fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka            defaultWallpaperWidth = Math.max((int)
2882d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
2892d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
2902d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
2912d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        boolean isPortrait = displaySize.x < displaySize.y;
2922d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        int portraitHeight;
2932d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        if (isPortrait) {
2942d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            portraitHeight = mCropView.getHeight();
2952d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        } else {
2962d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            // TODO: how to actually get the proper portrait height?
2972d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            // This is not quite right:
2982d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            portraitHeight = Math.max(maxDims.x, maxDims.y);
2992d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
3002d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        if (android.os.Build.VERSION.SDK_INT >=
3012d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
3022d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            Point realSize = new Point();
3032d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            d.getRealSize(realSize);
3042d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            portraitHeight = Math.max(realSize.x, realSize.y);
3052d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
3062d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // Get the crop
3072d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        RectF cropRect = mCropView.getCrop();
3088713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        int cropRotation = mCropView.getImageRotation();
3092d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        float cropScale = mCropView.getWidth() / (float) cropRect.width();
3102d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
3118713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        Point inSize = mCropView.getSourceDimensions();
3128713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        Matrix rotateMatrix = new Matrix();
3138713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        rotateMatrix.setRotate(cropRotation);
3148713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        float[] rotatedInSize = new float[] { inSize.x, inSize.y };
3158713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        rotateMatrix.mapPoints(rotatedInSize);
3168713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        rotatedInSize[0] = Math.abs(rotatedInSize[0]);
3178713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        rotatedInSize[1] = Math.abs(rotatedInSize[1]);
3188713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
3192d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // ADJUST CROP WIDTH
3202d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // Extend the crop all the way to the right, for parallax
321fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        // (or all the way to the left, in RTL)
3228713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
3232d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // Cap the amount of extra width
324fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
325fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        extraSpace = Math.min(extraSpace, maxExtraSpace);
3262d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
327fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        if (ltr) {
328fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka            cropRect.right += extraSpace;
329fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        } else {
330fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka            cropRect.left -= extraSpace;
331fe0ace3583bc57adbaaec730b1b1757032e90c41Michael Jurka        }
3322d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
3332d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // ADJUST CROP HEIGHT
3342d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        if (isPortrait) {
3352d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.bottom = cropRect.top + portraitHeight / cropScale;
3362d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        } else { // LANDSCAPE
3372d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            float extraPortraitHeight =
3382d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    portraitHeight / cropScale - cropRect.height();
3392d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            float expandHeight =
3408713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
3412d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                            extraPortraitHeight / 2);
3422d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.top -= expandHeight;
3432d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.bottom += expandHeight;
3442d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
3452d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final int outWidth = (int) Math.round(cropRect.width() * cropScale);
3462d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        final int outHeight = (int) Math.round(cropRect.height() * cropScale);
3472d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
3482d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        Runnable onEndCrop = new Runnable() {
3492d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            public void run() {
3502d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                updateWallpaperDimensions(outWidth, outHeight);
3512d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                if (finishActivityWhenDone) {
3522d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    setResult(Activity.RESULT_OK);
3532d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                    finish();
3542d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                }
3552d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            }
3562d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        };
3571e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka        BitmapCropTask cropTask = new BitmapCropTask(this, uri,
3588713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
359be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        if (onBitmapCroppedHandler != null) {
360be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
361be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
3622d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        cropTask.execute();
3632d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
364104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
365be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    public interface OnBitmapCroppedHandler {
366be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        public void onBitmapCropped(byte[] imageBytes);
367be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    }
368be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
3691e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka    protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
370104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        Uri mInUri = null;
371be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        Context mContext;
372be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        String mInFilePath;
373be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        byte[] mInImageBytes;
374fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka        int mInResId = 0;
375104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        InputStream mInStream;
376104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        RectF mCropBounds = null;
377104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        int mOutWidth, mOutHeight;
3788713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        int mRotation;
379104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        String mOutputFormat = "jpg"; // for now
380104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        boolean mSetWallpaper;
381104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        boolean mSaveCroppedBitmap;
382104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        Bitmap mCroppedBitmap;
383fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka        Runnable mOnEndRunnable;
384eadbfc564d84aaf1d800da3d0d6edf6312f89648Michael Jurka        Resources mResources;
385be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        OnBitmapCroppedHandler mOnBitmapCroppedHandler;
386be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        boolean mNoCrop;
387be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
388be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        public BitmapCropTask(Context c, String filePath,
3898713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
390be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
391be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mContext = c;
392be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mInFilePath = filePath;
3938713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            init(cropBounds, rotation,
3948713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
395be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
396be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
397be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        public BitmapCropTask(byte[] imageBytes,
3988713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
399be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
400be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mInImageBytes = imageBytes;
4018713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            init(cropBounds, rotation,
4028713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
403be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
404104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
4051e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka        public BitmapCropTask(Context c, Uri inUri,
4068713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
407fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
4081e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            mContext = c;
409104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            mInUri = inUri;
4108713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            init(cropBounds, rotation,
4118713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
412fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka        }
413fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka
4141e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka        public BitmapCropTask(Context c, Resources res, int inResId,
4158713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
416fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
4171e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            mContext = c;
418fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mInResId = inResId;
419be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mResources = res;
4208713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            init(cropBounds, rotation,
4218713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
422be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
423be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
4248713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
425be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
426fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mCropBounds = cropBounds;
4278713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka            mRotation = rotation;
428fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mOutWidth = outWidth;
429fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mOutHeight = outHeight;
430fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mSetWallpaper = setWallpaper;
431fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mSaveCroppedBitmap = saveCroppedBitmap;
432fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka            mOnEndRunnable = onEndRunnable;
433be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
434be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
435be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
436be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mOnBitmapCroppedHandler = handler;
437be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
438be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
439be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        public void setNoCrop(boolean value) {
440be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mNoCrop = value;
441be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        }
442be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka
443be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka        public void setOnEndRunnable(Runnable onEndRunnable) {
444be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            mOnEndRunnable = onEndRunnable;
445104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
446104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
447104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        // Helper to setup input stream
448104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        private void regenerateInputStream() {
449be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
450be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
451be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                        "image byte array given");
452104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            } else {
453104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                Utils.closeSilently(mInStream);
454104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                try {
455fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                    if (mInUri != null) {
456fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                        mInStream = new BufferedInputStream(
4571e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                                mContext.getContentResolver().openInputStream(mInUri));
458be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                    } else if (mInFilePath != null) {
459be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                        mInStream = mContext.openFileInput(mInFilePath);
460be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                    } else if (mInImageBytes != null) {
461be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                        mInStream = new BufferedInputStream(
462be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                                new ByteArrayInputStream(mInImageBytes));
463fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                    } else {
464fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                        mInStream = new BufferedInputStream(
465eadbfc564d84aaf1d800da3d0d6edf6312f89648Michael Jurka                                mResources.openRawResource(mInResId));
466fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka                    }
467104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                } catch (FileNotFoundException e) {
468104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
469104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
470104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            }
471104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
472104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
473104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        public Point getImageBounds() {
474104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            regenerateInputStream();
475104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            if (mInStream != null) {
476104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                BitmapFactory.Options options = new BitmapFactory.Options();
477104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                options.inJustDecodeBounds = true;
478104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                BitmapFactory.decodeStream(mInStream, null, options);
479104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (options.outWidth != 0 && options.outHeight != 0) {
480104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    return new Point(options.outWidth, options.outHeight);
481104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
482104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            }
483104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            return null;
484104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
485104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
486104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        public void setCropBounds(RectF cropBounds) {
487104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            mCropBounds = cropBounds;
488104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
489104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
490104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        public Bitmap getCroppedBitmap() {
491104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            return mCroppedBitmap;
492104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
493104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        public boolean cropBitmap() {
494104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            boolean failure = false;
495104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
496104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            regenerateInputStream();
497104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
4981e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            WallpaperManager wallpaperManager = null;
4991e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            if (mSetWallpaper) {
5001e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
5011e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            }
5021e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            if (mSetWallpaper && mNoCrop && mInStream != null) {
503be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                try {
5041e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                    wallpaperManager.setStream(mInStream);
505be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                } catch (IOException e) {
506be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
507be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                    failure = true;
508be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                }
509be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                return !failure;
510be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            }
511104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            if (mInStream != null) {
512104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                // Find crop bounds (scaled to original image size)
513104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                Rect roundedTrueCrop = new Rect();
5148713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                Matrix rotateMatrix = new Matrix();
5158713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                Matrix inverseRotateMatrix = new Matrix();
5168713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                if (mRotation > 0) {
5178713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    rotateMatrix.setRotate(mRotation);
5188713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    inverseRotateMatrix.setRotate(-mRotation);
5198713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
5208713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    mCropBounds.roundOut(roundedTrueCrop);
5218713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    mCropBounds = new RectF(roundedTrueCrop);
5228713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
5238713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    Point bounds = getImageBounds();
5248713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
5258713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
5268713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    rotateMatrix.mapPoints(rotatedBounds);
5278713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    rotatedBounds[0] = Math.abs(rotatedBounds[0]);
5288713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    rotatedBounds[1] = Math.abs(rotatedBounds[1]);
5298713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
5308713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
5318713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    inverseRotateMatrix.mapRect(mCropBounds);
5328713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    mCropBounds.offset(bounds.x/2, bounds.y/2);
5338713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
5348713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    regenerateInputStream();
5358713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                }
5368713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
537104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                mCropBounds.roundOut(roundedTrueCrop);
538104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
539104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
540104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Log.w(LOGTAG, "crop has bad values for full size image");
541104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    failure = true;
542104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    return false;
543104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
544104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
545104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                // See how much we're reducing the size of the image
546104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
547104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        roundedTrueCrop.height() / mOutHeight);
548104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
549104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                // Attempt to open a region decoder
550104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                BitmapRegionDecoder decoder = null;
551104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                try {
552104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
553104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                } catch (IOException e) {
554104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
555104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
556104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
557104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                Bitmap crop = null;
558104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (decoder != null) {
559104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    // Do region decoding to get crop bitmap
560104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    BitmapFactory.Options options = new BitmapFactory.Options();
561104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    if (scaleDownSampleSize > 1) {
562104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        options.inSampleSize = scaleDownSampleSize;
563104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    }
564104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    crop = decoder.decodeRegion(roundedTrueCrop, options);
565104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    decoder.recycle();
566104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
567104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
568104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (crop == null) {
569104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    // BitmapRegionDecoder has failed, try to crop in-memory
570104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    regenerateInputStream();
571104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Bitmap fullSize = null;
572104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    if (mInStream != null) {
573104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        BitmapFactory.Options options = new BitmapFactory.Options();
574104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        if (scaleDownSampleSize > 1) {
575104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                            options.inSampleSize = scaleDownSampleSize;
576104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        }
577104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        fullSize = BitmapFactory.decodeStream(mInStream, null, options);
578104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    }
579104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    if (fullSize != null) {
5808713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mCropBounds.left /= scaleDownSampleSize;
5818713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mCropBounds.top /= scaleDownSampleSize;
5828713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mCropBounds.bottom /= scaleDownSampleSize;
5838713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mCropBounds.right /= scaleDownSampleSize;
5848713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mCropBounds.roundOut(roundedTrueCrop);
5858713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
586104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
587104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                                roundedTrueCrop.top, roundedTrueCrop.width(),
588104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                                roundedTrueCrop.height());
589104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    }
590104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
591104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
592104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (crop == null) {
593104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
594104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    failure = true;
595104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    return false;
596104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
5978713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
5988713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
5998713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    rotateMatrix.mapPoints(dimsAfter);
6008713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    dimsAfter[0] = Math.abs(dimsAfter[0]);
6018713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    dimsAfter[1] = Math.abs(dimsAfter[1]);
6028713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
6038713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    if (!(mOutWidth > 0 && mOutHeight > 0)) {
6048713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mOutWidth = Math.round(dimsAfter[0]);
6058713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        mOutHeight = Math.round(dimsAfter[1]);
606104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    }
6078713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
6088713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
609fd471c0b4f028948d5aaefe37d4b9582b4b4372fMichael Jurka                    RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
6108713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
6118713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    Matrix m = new Matrix();
6128713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    if (mRotation == 0) {
6138713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
6148713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    } else {
6158713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        Matrix m1 = new Matrix();
6168713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
6178713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        Matrix m2 = new Matrix();
6188713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        m2.setRotate(mRotation);
6198713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        Matrix m3 = new Matrix();
6208713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
6218713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        Matrix m4 = new Matrix();
6228713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
6238713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
6248713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        Matrix c1 = new Matrix();
6258713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        c1.setConcat(m2, m1);
6268713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        Matrix c2 = new Matrix();
6278713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        c2.setConcat(m4, m3);
6288713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                        m.setConcat(c2, c1);
6298713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka                    }
6308713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka
631104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
632104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
633104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    if (tmp != null) {
634104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        Canvas c = new Canvas(tmp);
63516205da30e223368b31c68b7d76ac3d52f940c88Michael Jurka                        Paint p = new Paint();
63616205da30e223368b31c68b7d76ac3d52f940c88Michael Jurka                        p.setFilterBitmap(true);
63716205da30e223368b31c68b7d76ac3d52f940c88Michael Jurka                        c.drawBitmap(crop, m, p);
638104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        crop = tmp;
639104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    }
640104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
641104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
642104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (mSaveCroppedBitmap) {
643104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    mCroppedBitmap = crop;
644104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
645104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
646104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                // Get output compression format
647104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                CompressFormat cf =
648104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
649104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
650104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                // Compress to byte array
651104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
652104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
653104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    // If we need to set to the wallpaper, set it
6541e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                    if (mSetWallpaper && wallpaperManager != null) {
6551e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                        try {
6561e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                            byte[] outByteArray = tmpOut.toByteArray();
6571e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
6581e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                            if (mOnBitmapCroppedHandler != null) {
6591e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
660104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                            }
6611e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                        } catch (IOException e) {
6621e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                            Log.w(LOGTAG, "cannot write stream to wallpaper", e);
6631e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                            failure = true;
664104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                        }
665104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    }
666104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                } else {
667104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    Log.w(LOGTAG, "cannot compress bitmap");
668104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                    failure = true;
669104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                }
670104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            }
671104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            return !failure; // True if any of the operations failed
672104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
673104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
674104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        @Override
675104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        protected Boolean doInBackground(Void... params) {
676104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            return cropBitmap();
677104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
678104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
679104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        @Override
680104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        protected void onPostExecute(Boolean result) {
6811e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            if (mOnEndRunnable != null) {
6821e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka                mOnEndRunnable.run();
6831e4e6ddb38d0b867d0564be834f3425ed53d46e1Michael Jurka            }
684104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
685104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    }
686104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
687104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    protected void updateWallpaperDimensions(int width, int height) {
688adc574ccb1986856b38198ad5a934b7543a1debbMichael Jurka        String spKey = getSharedPreferencesKey();
689104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
690104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        SharedPreferences.Editor editor = sp.edit();
691104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        if (width != 0 && height != 0) {
692104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            editor.putInt(WALLPAPER_WIDTH_KEY, width);
693104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            editor.putInt(WALLPAPER_HEIGHT_KEY, height);
694104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        } else {
695104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            editor.remove(WALLPAPER_WIDTH_KEY);
696104c45677660586026a7e74ef8c47d396403d50eMichael Jurka            editor.remove(WALLPAPER_HEIGHT_KEY);
697104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        }
698104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        editor.commit();
699fe09cb7fe3ec1ab3e7aabd2d0415de61df31c4dbMichael Jurka
7002d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        suggestWallpaperDimension(getResources(),
701104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                sp, getWindowManager(), WallpaperManager.getInstance(this));
702104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    }
703104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
7042d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    static public void suggestWallpaperDimension(Resources res,
7052d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            final SharedPreferences sharedPrefs,
7062d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            WindowManager windowManager,
7072d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            final WallpaperManager wallpaperManager) {
7088713a94562c535bd2cc7d72e767caf9468d7bbaaMichael Jurka        final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
7092d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
7102d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        new Thread("suggestWallpaperDimension") {
7112d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            public void run() {
7122d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                // If we have saved a wallpaper width/height, use that instead
7132d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
7142d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
7152d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka                wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
7162d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            }
7172d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }.start();
7182d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
7192d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
720be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka    protected static RectF getMaxCropRect(
721be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka            int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
7222d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        RectF cropRect = new RectF();
7232d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        // Get a crop rect that will fit this
7242d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
7252d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka             cropRect.top = 0;
7262d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka             cropRect.bottom = inHeight;
7272d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka             cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
7282d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka             cropRect.right = inWidth - cropRect.left;
729be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka             if (leftAligned) {
730be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                 cropRect.right -= cropRect.left;
731be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka                 cropRect.left = 0;
732be2f8ddd8067d0da71e2d03e42bcad17f9430439Michael Jurka             }
7332d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        } else {
7342d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.left = 0;
7352d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.right = inWidth;
7362d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
7372d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka            cropRect.bottom = inHeight - cropRect.top;
7382d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        }
7392d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka        return cropRect;
7402d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka    }
7412d8de5845be5f49d3767b8354f0d5479a0715abeMichael Jurka
742104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
743104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
744104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    }
745104c45677660586026a7e74ef8c47d396403d50eMichael Jurka
746104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    protected static String getFileExtension(String requestFormat) {
747104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        String outputFormat = (requestFormat == null)
748104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                ? "jpg"
749104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                : requestFormat;
750104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        outputFormat = outputFormat.toLowerCase();
751104c45677660586026a7e74ef8c47d396403d50eMichael Jurka        return (outputFormat.equals("png") || outputFormat.equals("gif"))
752104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                ? "png" // We don't support gif compression.
753104c45677660586026a7e74ef8c47d396403d50eMichael Jurka                : "jpg";
754104c45677660586026a7e74ef8c47d396403d50eMichael Jurka    }
755104c45677660586026a7e74ef8c47d396403d50eMichael Jurka}
756