WallpaperCropActivity.java revision d6901a95d8aabc25fdd108d3605b34f848ab10e0
1e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka/*
2e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Copyright (C) 2013 The Android Open Source Project
3e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
4e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Licensed under the Apache License, Version 2.0 (the "License");
5e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * you may not use this file except in compliance with the License.
6e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * You may obtain a copy of the License at
7e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
8e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *      http://www.apache.org/licenses/LICENSE-2.0
9e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
10e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Unless required by applicable law or agreed to in writing, software
11e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * distributed under the License is distributed on an "AS IS" BASIS,
12e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * See the License for the specific language governing permissions and
14e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * limitations under the License.
15e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka */
16e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka/* Copied from Launcher3 */
17e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkapackage com.android.wallpapercropper;
18e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
19e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.app.ActionBar;
20e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.app.Activity;
21e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.app.WallpaperManager;
22e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.content.Context;
23e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.content.Intent;
24e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.content.SharedPreferences;
25e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.content.res.Configuration;
26e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.content.res.Resources;
27e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Bitmap;
28e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Bitmap.CompressFormat;
29e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.BitmapFactory;
30e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.BitmapRegionDecoder;
31e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Canvas;
32e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Matrix;
33e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Paint;
34e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Point;
35e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Rect;
36e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.RectF;
37e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.net.Uri;
38e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.os.AsyncTask;
39e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.os.Bundle;
40e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.util.Log;
41e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.Display;
42e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.View;
43e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.WindowManager;
447b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurkaimport android.widget.Toast;
45e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
46e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport com.android.gallery3d.common.Utils;
4769784065ea78550944b7eddb559c0dac952e20e8Michael Jurkaimport com.android.gallery3d.exif.ExifInterface;
48e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport com.android.photos.BitmapRegionTileSource;
497b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurkaimport com.android.photos.BitmapRegionTileSource.BitmapSource;
50e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
51e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.BufferedInputStream;
52e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.ByteArrayInputStream;
53e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.ByteArrayOutputStream;
54e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.FileNotFoundException;
55e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.IOException;
56e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.InputStream;
57e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
58e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkapublic class WallpaperCropActivity extends Activity {
59e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final String LOGTAG = "Launcher3.CropActivity";
60e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
61e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
62e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
63e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int DEFAULT_COMPRESS_QUALITY = 90;
64e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    /**
65e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * The maximum bitmap size we allow to be returned through the intent.
66e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
67e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * have some overhead to hit so that we go way below the limit here to make
68e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * sure the intent stays below 1MB.We should consider just returning a byte
69e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * array instead of a Bitmap instance to avoid overhead.
70e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     */
71e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public static final int MAX_BMAP_IN_INTENT = 750000;
72e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final float WALLPAPER_SCREENS_SPAN = 2f;
73e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
7435f922d1d038d14f94b584baae324513610f7813Michael Jurka    protected static Point sDefaultWallpaperSize;
7535f922d1d038d14f94b584baae324513610f7813Michael Jurka
76e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected CropView mCropView;
77e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected Uri mUri;
78e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
79e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
80e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void onCreate(Bundle savedInstanceState) {
81e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        super.onCreate(savedInstanceState);
82e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        init();
83e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        if (!enableRotation()) {
84e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
85e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        }
86e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
87e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
88e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void init() {
89e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        setContentView(R.layout.wallpaper_cropper);
90e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
91e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mCropView = (CropView) findViewById(R.id.cropView);
92e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
9369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        Intent cropIntent = getIntent();
94e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final Uri imageUri = cropIntent.getData();
95e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
9669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        if (imageUri == null) {
9769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
9869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            finish();
9969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            return;
10069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        }
10169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
102e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Action bar
103e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Show the custom action bar view
104e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final ActionBar actionBar = getActionBar();
105e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
106e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        actionBar.getCustomView().setOnClickListener(
107e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                new View.OnClickListener() {
108e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    @Override
109e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    public void onClick(View v) {
110e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        boolean finishActivityWhenDone = true;
111e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
112e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
113e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                });
1145271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka
1155271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        // Load image in background
1167b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka        final BitmapRegionTileSource.UriBitmapSource bitmapSource =
1177b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
1187b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka        Runnable onLoad = new Runnable() {
1197b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            public void run() {
1207b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
1217b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    Toast.makeText(WallpaperCropActivity.this,
1227b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                            getString(R.string.wallpaper_load_fail),
1237b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                            Toast.LENGTH_LONG).show();
1247b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    finish();
1257b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                }
1267b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            }
1277b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka        };
1287b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka        setCropViewTileSource(bitmapSource, true, false, onLoad);
1295271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka    }
1305271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka
1317b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka    public void setCropViewTileSource(
1327b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
1337b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            final boolean moveToLeft, final Runnable postExecute) {
1345271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        final Context context = WallpaperCropActivity.this;
1355271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        final View progressView = findViewById(R.id.loading);
1365271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
1375271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka            protected Void doInBackground(Void...args) {
1385271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                if (!isCancelled()) {
139d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                    try {
140d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                        bitmapSource.loadInBackground();
141d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                    } catch (SecurityException securityException) {
142d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                        if (isDestroyed()) {
143d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            // Temporarily granted permissions are revoked when the activity
144d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            // finishes, potentially resulting in a SecurityException here.
145d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            // Even though {@link #isDestroyed} might also return true in different
146d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            // situations where the configuration changes, we are fine with
147d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            // catching these cases here as well.
148d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            cancel(false);
149d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                        } else {
150d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            // otherwise it had a different cause and we throw it further
151d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                            throw securityException;
152d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                        }
153d6901a95d8aabc25fdd108d3605b34f848ab10e0Selim Cinek                    }
1545271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                }
1555271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                return null;
1565271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka            }
1575271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka            protected void onPostExecute(Void arg) {
1585271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                if (!isCancelled()) {
1595271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                    progressView.setVisibility(View.INVISIBLE);
1607b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
1617b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        mCropView.setTileSource(
1627b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                                new BitmapRegionTileSource(context, bitmapSource), null);
1637b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        mCropView.setTouchEnabled(touchEnabled);
1647b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        if (moveToLeft) {
1657b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                            mCropView.moveToLeft();
1667b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        }
1675271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                    }
1685271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                }
1697b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                if (postExecute != null) {
1707b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    postExecute.run();
1717b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                }
1725271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka            }
1735271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        };
1745271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        // We don't want to show the spinner every time we load an image, because that would be
1755271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        // annoying; instead, only start showing the spinner if loading the image has taken
1765271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        // longer than 1 sec (ie 1000 ms)
1775271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        progressView.postDelayed(new Runnable() {
1785271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka            public void run() {
1795271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
1805271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                    progressView.setVisibility(View.VISIBLE);
1815271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka                }
1825271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka            }
1835271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        }, 1000);
1845271ea16c1989f99c0db7ab80b2b321441f60023Michael Jurka        loadBitmapTask.execute();
185e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka    }
186e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka
187e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka    public boolean enableRotation() {
188e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        return getResources().getBoolean(R.bool.allow_rotation);
189e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
190e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
191e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public static String getSharedPreferencesKey() {
192e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return WallpaperCropActivity.class.getName();
193e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
194e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
195e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    // As a ratio of screen height, the total distance we want the parallax effect to span
196e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    // horizontally
197e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
198e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float aspectRatio = width / (float) height;
199e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
200e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
201e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
202e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // We will use these two data points to extrapolate how much the wallpaper parallax effect
203e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // to span (ie travel) at any aspect ratio:
204e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
205e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
206e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float ASPECT_RATIO_PORTRAIT = 10/16f;
207e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
208e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
209e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
210e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // To find out the desired width at different aspect ratios, we use the following two
211e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // formulas, where the coefficient on x is the aspect ratio (width/height):
212e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        //   (16/10)x + y = 1.5
213e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        //   (10/16)x + y = 1.2
214e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // We solve for x and y and end up with a final formula:
215e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float x =
216e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
217e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
218e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
219e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return x * aspectRatio + y;
220e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
221e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
222e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
22335f922d1d038d14f94b584baae324513610f7813Michael Jurka        if (sDefaultWallpaperSize == null) {
22435f922d1d038d14f94b584baae324513610f7813Michael Jurka            Point minDims = new Point();
22535f922d1d038d14f94b584baae324513610f7813Michael Jurka            Point maxDims = new Point();
22635f922d1d038d14f94b584baae324513610f7813Michael Jurka            windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
22735f922d1d038d14f94b584baae324513610f7813Michael Jurka
22835f922d1d038d14f94b584baae324513610f7813Michael Jurka            int maxDim = Math.max(maxDims.x, maxDims.y);
22935f922d1d038d14f94b584baae324513610f7813Michael Jurka            int minDim = Math.max(minDims.x, minDims.y);
23035f922d1d038d14f94b584baae324513610f7813Michael Jurka
23135f922d1d038d14f94b584baae324513610f7813Michael Jurka            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
23235f922d1d038d14f94b584baae324513610f7813Michael Jurka                Point realSize = new Point();
23335f922d1d038d14f94b584baae324513610f7813Michael Jurka                windowManager.getDefaultDisplay().getRealSize(realSize);
23435f922d1d038d14f94b584baae324513610f7813Michael Jurka                maxDim = Math.max(realSize.x, realSize.y);
23535f922d1d038d14f94b584baae324513610f7813Michael Jurka                minDim = Math.min(realSize.x, realSize.y);
23635f922d1d038d14f94b584baae324513610f7813Michael Jurka            }
237e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
23835f922d1d038d14f94b584baae324513610f7813Michael Jurka            // We need to ensure that there is enough extra space in the wallpaper
23935f922d1d038d14f94b584baae324513610f7813Michael Jurka            // for the intended parallax effects
24035f922d1d038d14f94b584baae324513610f7813Michael Jurka            final int defaultWidth, defaultHeight;
24135f922d1d038d14f94b584baae324513610f7813Michael Jurka            if (isScreenLarge(res)) {
24235f922d1d038d14f94b584baae324513610f7813Michael Jurka                defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
24335f922d1d038d14f94b584baae324513610f7813Michael Jurka                defaultHeight = maxDim;
24435f922d1d038d14f94b584baae324513610f7813Michael Jurka            } else {
24535f922d1d038d14f94b584baae324513610f7813Michael Jurka                defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
24635f922d1d038d14f94b584baae324513610f7813Michael Jurka                defaultHeight = maxDim;
24735f922d1d038d14f94b584baae324513610f7813Michael Jurka            }
24835f922d1d038d14f94b584baae324513610f7813Michael Jurka            sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight);
249e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
25035f922d1d038d14f94b584baae324513610f7813Michael Jurka        return sDefaultWallpaperSize;
251e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
252e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
25369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    public static int getRotationFromExif(String path) {
25469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        return getRotationFromExifHelper(path, null, 0, null, null);
25569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
25669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
25769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    public static int getRotationFromExif(Context context, Uri uri) {
25869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        return getRotationFromExifHelper(null, null, 0, context, uri);
25969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
26069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
26169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    public static int getRotationFromExif(Resources res, int resId) {
26269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        return getRotationFromExifHelper(null, res, resId, null, null);
26369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
26469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
26569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private static int getRotationFromExifHelper(
26669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            String path, Resources res, int resId, Context context, Uri uri) {
26769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        ExifInterface ei = new ExifInterface();
268b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka        InputStream is = null;
269b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka        BufferedInputStream bis = null;
27069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        try {
27169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            if (path != null) {
27269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                ei.readExif(path);
27369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            } else if (uri != null) {
274b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                is = context.getContentResolver().openInputStream(uri);
275b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                bis = new BufferedInputStream(is);
27669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                ei.readExif(bis);
27769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            } else {
278b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                is = res.openRawResource(resId);
279b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                bis = new BufferedInputStream(is);
28069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                ei.readExif(bis);
28169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            }
28269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
28369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            if (ori != null) {
28469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
28569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            }
28669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        } catch (IOException e) {
28769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            Log.w(LOGTAG, "Getting exif data failed", e);
288b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka        } finally {
289b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka            Utils.closeSilently(bis);
290b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka            Utils.closeSilently(is);
29169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        }
29269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        return 0;
29369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
29469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
295e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
29669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        int rotation = getRotationFromExif(filePath);
29769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        BitmapCropTask cropTask = new BitmapCropTask(
29869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                this, filePath, null, rotation, 0, 0, true, false, null);
299e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final Point bounds = cropTask.getImageBounds();
300e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Runnable onEndCrop = new Runnable() {
301e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            public void run() {
302e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (finishActivityWhenDone) {
303e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    setResult(Activity.RESULT_OK);
304e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    finish();
305e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
306e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
307e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        };
308e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        cropTask.setOnEndRunnable(onEndCrop);
309e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        cropTask.setNoCrop(true);
310e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        cropTask.execute();
311e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
312e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
313e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void cropImageAndSetWallpaper(
314e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            Resources res, int resId, final boolean finishActivityWhenDone) {
315e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // crop this image and scale it down to the default wallpaper size for
316e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // this device
31769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        int rotation = getRotationFromExif(res, resId);
318e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Point inSize = mCropView.getSourceDimensions();
319e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Point outSize = getDefaultWallpaperSize(getResources(),
320e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                getWindowManager());
321e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        RectF crop = getMaxCropRect(
322e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                inSize.x, inSize.y, outSize.x, outSize.y, false);
323e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Runnable onEndCrop = new Runnable() {
324e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            public void run() {
325e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (finishActivityWhenDone) {
326e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    setResult(Activity.RESULT_OK);
327e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    finish();
328e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
329e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
330e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        };
331e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
33269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
333e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        cropTask.execute();
334e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
335e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
336e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static boolean isScreenLarge(Resources res) {
337e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Configuration config = res.getConfiguration();
338e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return config.smallestScreenWidthDp >= 720;
339e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
340e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
341e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void cropImageAndSetWallpaper(Uri uri,
342e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
3432ab14daebac46307d2364733481edc3115bbf98cMichael Jurka        boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
344e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        // Get the crop
345e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
346e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka
347e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Display d = getWindowManager().getDefaultDisplay();
348e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
349e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Point displaySize = new Point();
350e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        d.getSize(displaySize);
351e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        boolean isPortrait = displaySize.x < displaySize.y;
352eed96a0b9088dcce121ffee476b4d7fa72686e77Michael Jurka
353eed96a0b9088dcce121ffee476b4d7fa72686e77Michael Jurka        Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(),
354eed96a0b9088dcce121ffee476b4d7fa72686e77Michael Jurka                getWindowManager());
355e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Get the crop
356e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        RectF cropRect = mCropView.getCrop();
35769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        int cropRotation = mCropView.getImageRotation();
358e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float cropScale = mCropView.getWidth() / (float) cropRect.width();
359e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
36069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        Point inSize = mCropView.getSourceDimensions();
36169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        Matrix rotateMatrix = new Matrix();
36269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rotateMatrix.setRotate(cropRotation);
36369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float[] rotatedInSize = new float[] { inSize.x, inSize.y };
36469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rotateMatrix.mapPoints(rotatedInSize);
36569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rotatedInSize[0] = Math.abs(rotatedInSize[0]);
36669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rotatedInSize[1] = Math.abs(rotatedInSize[1]);
36769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
368e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // ADJUST CROP WIDTH
369e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Extend the crop all the way to the right, for parallax
370e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        // (or all the way to the left, in RTL)
3712ab14daebac46307d2364733481edc3115bbf98cMichael Jurka        float extraSpace;
3722ab14daebac46307d2364733481edc3115bbf98cMichael Jurka        if (centerCrop) {
3732ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left);
3742ab14daebac46307d2364733481edc3115bbf98cMichael Jurka        } else {
3752ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
3762ab14daebac46307d2364733481edc3115bbf98cMichael Jurka        }
377e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Cap the amount of extra width
378eed96a0b9088dcce121ffee476b4d7fa72686e77Michael Jurka        float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
379e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        extraSpace = Math.min(extraSpace, maxExtraSpace);
380e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
3812ab14daebac46307d2364733481edc3115bbf98cMichael Jurka        if (centerCrop) {
3822ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            cropRect.left -= extraSpace / 2f;
3832ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            cropRect.right += extraSpace / 2f;
384e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        } else {
3852ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            if (ltr) {
3862ab14daebac46307d2364733481edc3115bbf98cMichael Jurka                cropRect.right += extraSpace;
3872ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            } else {
3882ab14daebac46307d2364733481edc3115bbf98cMichael Jurka                cropRect.left -= extraSpace;
3892ab14daebac46307d2364733481edc3115bbf98cMichael Jurka            }
390e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        }
391e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
392e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // ADJUST CROP HEIGHT
393e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (isPortrait) {
394eed96a0b9088dcce121ffee476b4d7fa72686e77Michael Jurka            cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
395e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else { // LANDSCAPE
396e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            float extraPortraitHeight =
397eed96a0b9088dcce121ffee476b4d7fa72686e77Michael Jurka                    defaultWallpaperSize.y / cropScale - cropRect.height();
398e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            float expandHeight =
39969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
400e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                            extraPortraitHeight / 2);
401e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropRect.top -= expandHeight;
402e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropRect.bottom += expandHeight;
403e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
404e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final int outWidth = (int) Math.round(cropRect.width() * cropScale);
405e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final int outHeight = (int) Math.round(cropRect.height() * cropScale);
406e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
407e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Runnable onEndCrop = new Runnable() {
408e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            public void run() {
409e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (finishActivityWhenDone) {
410e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    setResult(Activity.RESULT_OK);
411e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    finish();
412e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
413e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
414e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        };
415e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        BitmapCropTask cropTask = new BitmapCropTask(this, uri,
41669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
417e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (onBitmapCroppedHandler != null) {
418e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
419e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
420e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        cropTask.execute();
421e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
422e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
423e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public interface OnBitmapCroppedHandler {
424e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public void onBitmapCropped(byte[] imageBytes);
425e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
426e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
427e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka    protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
428e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Uri mInUri = null;
429e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Context mContext;
430e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        String mInFilePath;
431e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        byte[] mInImageBytes;
432e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int mInResId = 0;
433e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        RectF mCropBounds = null;
434e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int mOutWidth, mOutHeight;
43569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        int mRotation;
436e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        String mOutputFormat = "jpg"; // for now
437e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        boolean mSetWallpaper;
438e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        boolean mSaveCroppedBitmap;
439e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Bitmap mCroppedBitmap;
440e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Runnable mOnEndRunnable;
441e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        Resources mResources;
442e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        OnBitmapCroppedHandler mOnBitmapCroppedHandler;
443e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        boolean mNoCrop;
444e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
445e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public BitmapCropTask(Context c, String filePath,
44669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
447e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
448e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mContext = c;
449e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mInFilePath = filePath;
45069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            init(cropBounds, rotation,
45169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
452e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
453e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
454e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public BitmapCropTask(byte[] imageBytes,
45569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
456e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
457e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mInImageBytes = imageBytes;
45869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            init(cropBounds, rotation,
45969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
460e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
461e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
462e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        public BitmapCropTask(Context c, Uri inUri,
46369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
464e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
465e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            mContext = c;
466e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mInUri = inUri;
46769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            init(cropBounds, rotation,
46869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
469e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
470e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
471e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        public BitmapCropTask(Context c, Resources res, int inResId,
47269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                RectF cropBounds, int rotation, int outWidth, int outHeight,
473e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
474e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            mContext = c;
475e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mInResId = inResId;
476e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mResources = res;
47769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            init(cropBounds, rotation,
47869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
479e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
480e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
48169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
482e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
483e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mCropBounds = cropBounds;
48469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka            mRotation = rotation;
485e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mOutWidth = outWidth;
486e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mOutHeight = outHeight;
487e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mSetWallpaper = setWallpaper;
488e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mSaveCroppedBitmap = saveCroppedBitmap;
489e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mOnEndRunnable = onEndRunnable;
490e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
491e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
492e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
493e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mOnBitmapCroppedHandler = handler;
494e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
495e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
496e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public void setNoCrop(boolean value) {
497e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mNoCrop = value;
498e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
499e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
500e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public void setOnEndRunnable(Runnable onEndRunnable) {
501e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mOnEndRunnable = onEndRunnable;
502e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
503e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
504e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Helper to setup input stream
5057b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka        private InputStream regenerateInputStream() {
506e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
507e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
508e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        "image byte array given");
509e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            } else {
510e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                try {
511e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (mInUri != null) {
5127b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        return new BufferedInputStream(
513e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                                mContext.getContentResolver().openInputStream(mInUri));
514e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    } else if (mInFilePath != null) {
5157b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        return mContext.openFileInput(mInFilePath);
516e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    } else if (mInImageBytes != null) {
5177b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
518e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    } else {
5197b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        return new BufferedInputStream(mResources.openRawResource(mInResId));
520e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
521e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                } catch (FileNotFoundException e) {
522e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
523e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
524e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
5257b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            return null;
526e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
527e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
528e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public Point getImageBounds() {
5297b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            InputStream is = regenerateInputStream();
5307b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            if (is != null) {
531e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                BitmapFactory.Options options = new BitmapFactory.Options();
532e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                options.inJustDecodeBounds = true;
5337b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                BitmapFactory.decodeStream(is, null, options);
5347b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                Utils.closeSilently(is);
535e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (options.outWidth != 0 && options.outHeight != 0) {
536e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    return new Point(options.outWidth, options.outHeight);
537e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
538e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
539e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            return null;
540e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
541e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
542e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public void setCropBounds(RectF cropBounds) {
543e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mCropBounds = cropBounds;
544e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
545e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
546e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public Bitmap getCroppedBitmap() {
547e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            return mCroppedBitmap;
548e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
549e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        public boolean cropBitmap() {
550e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            boolean failure = false;
551e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
552e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
553e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            WallpaperManager wallpaperManager = null;
554e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            if (mSetWallpaper) {
555e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
556e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            }
5577b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka
5587b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka
5597b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            if (mSetWallpaper && mNoCrop) {
560e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                try {
5617b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    InputStream is = regenerateInputStream();
5627b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    if (is != null) {
5637b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        wallpaperManager.setStream(is);
5647b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        Utils.closeSilently(is);
5657b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    }
566e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                } catch (IOException e) {
567e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
568e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    failure = true;
569e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
570e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                return !failure;
5717b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka            } else {
572e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // Find crop bounds (scaled to original image size)
573e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                Rect roundedTrueCrop = new Rect();
57469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                Matrix rotateMatrix = new Matrix();
57569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                Matrix inverseRotateMatrix = new Matrix();
576e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka
577e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                Point bounds = getImageBounds();
57869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                if (mRotation > 0) {
57969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    rotateMatrix.setRotate(mRotation);
58069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    inverseRotateMatrix.setRotate(-mRotation);
58169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
58269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mCropBounds.roundOut(roundedTrueCrop);
58369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mCropBounds = new RectF(roundedTrueCrop);
58469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
5857b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    if (bounds == null) {
5867b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        Log.w(LOGTAG, "cannot get bounds for image");
5877b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        failure = true;
5887b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        return false;
5897b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    }
59069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
59169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
59269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    rotateMatrix.mapPoints(rotatedBounds);
59369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    rotatedBounds[0] = Math.abs(rotatedBounds[0]);
59469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    rotatedBounds[1] = Math.abs(rotatedBounds[1]);
59569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
59669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
59769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    inverseRotateMatrix.mapRect(mCropBounds);
59869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mCropBounds.offset(bounds.x/2, bounds.y/2);
59969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
60069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                }
60169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
602e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mCropBounds.roundOut(roundedTrueCrop);
603e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
604e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
605e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Log.w(LOGTAG, "crop has bad values for full size image");
606e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    failure = true;
607e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    return false;
608e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
609e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
610e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // See how much we're reducing the size of the image
611b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
612b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                        roundedTrueCrop.height() / mOutHeight));
613e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // Attempt to open a region decoder
614e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                BitmapRegionDecoder decoder = null;
615b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                InputStream is = null;
616e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                try {
617b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                    is = regenerateInputStream();
6187b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    if (is == null) {
6197b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
6207b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        failure = true;
6217b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        return false;
6227b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    }
6237b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    decoder = BitmapRegionDecoder.newInstance(is, false);
6247b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    Utils.closeSilently(is);
625e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                } catch (IOException e) {
626e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
627b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                } finally {
628b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                   Utils.closeSilently(is);
629b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                   is = null;
630e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
631e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
632e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                Bitmap crop = null;
633e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (decoder != null) {
634e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    // Do region decoding to get crop bitmap
635e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    BitmapFactory.Options options = new BitmapFactory.Options();
636e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (scaleDownSampleSize > 1) {
637e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        options.inSampleSize = scaleDownSampleSize;
638e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
639e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    crop = decoder.decodeRegion(roundedTrueCrop, options);
640e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    decoder.recycle();
641e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
642e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
643e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (crop == null) {
644e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    // BitmapRegionDecoder has failed, try to crop in-memory
645b2552648cec772c896ff31a7aa52ae48380459c0Michael Jurka                    is = regenerateInputStream();
646e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Bitmap fullSize = null;
6477b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                    if (is != null) {
648e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        BitmapFactory.Options options = new BitmapFactory.Options();
649e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        if (scaleDownSampleSize > 1) {
650e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                            options.inSampleSize = scaleDownSampleSize;
651e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        }
6527b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        fullSize = BitmapFactory.decodeStream(is, null, options);
6537b215cb92288aa0a21bc773511ddd537b8fbb459Michael Jurka                        Utils.closeSilently(is);
654e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
655e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (fullSize != null) {
656e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        // Find out the true sample size that was used by the decoder
657e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        scaleDownSampleSize = bounds.x / fullSize.getWidth();
65869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mCropBounds.left /= scaleDownSampleSize;
65969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mCropBounds.top /= scaleDownSampleSize;
66069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mCropBounds.bottom /= scaleDownSampleSize;
66169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mCropBounds.right /= scaleDownSampleSize;
66269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mCropBounds.roundOut(roundedTrueCrop);
66369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
664e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        // Adjust values to account for issues related to rounding
665e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        if (roundedTrueCrop.width() > fullSize.getWidth()) {
666e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            // Adjust the width
667e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
668e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        }
669e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        if (roundedTrueCrop.right > fullSize.getWidth()) {
670e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            // Adjust the left value
671e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            int adjustment = roundedTrueCrop.left -
672e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                                    Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
673e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            roundedTrueCrop.left -= adjustment;
674e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            roundedTrueCrop.right -= adjustment;
675e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        }
676e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        if (roundedTrueCrop.height() > fullSize.getHeight()) {
677e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            // Adjust the height
678e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
679e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        }
680e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        if (roundedTrueCrop.bottom > fullSize.getHeight()) {
681e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            // Adjust the top value
682e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            int adjustment = roundedTrueCrop.top -
683e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                                    Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
684e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            roundedTrueCrop.top -= adjustment;
685e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                            roundedTrueCrop.bottom -= adjustment;
686e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka                        }
687e39c9a953ca11319b747b3aa79f4ccd082b775b7Michael Jurka
688e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
689e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                                roundedTrueCrop.top, roundedTrueCrop.width(),
690e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                                roundedTrueCrop.height());
691e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
692e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
693e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
694e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (crop == null) {
695e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
696e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    failure = true;
697e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    return false;
698e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
69969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
70069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
70169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    rotateMatrix.mapPoints(dimsAfter);
70269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    dimsAfter[0] = Math.abs(dimsAfter[0]);
70369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    dimsAfter[1] = Math.abs(dimsAfter[1]);
70469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
70569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    if (!(mOutWidth > 0 && mOutHeight > 0)) {
70669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mOutWidth = Math.round(dimsAfter[0]);
70769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        mOutHeight = Math.round(dimsAfter[1]);
708e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
70969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
71069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
711e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
71269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
71369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    Matrix m = new Matrix();
71469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    if (mRotation == 0) {
71569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
71669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    } else {
71769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        Matrix m1 = new Matrix();
71869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
71969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        Matrix m2 = new Matrix();
72069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        m2.setRotate(mRotation);
72169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        Matrix m3 = new Matrix();
72269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
72369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        Matrix m4 = new Matrix();
72469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
72569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
72669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        Matrix c1 = new Matrix();
72769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        c1.setConcat(m2, m1);
72869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        Matrix c2 = new Matrix();
72969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        c2.setConcat(m4, m3);
73069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        m.setConcat(c2, c1);
73169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    }
73269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
733e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
734e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
735e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (tmp != null) {
736e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        Canvas c = new Canvas(tmp);
737e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                        Paint p = new Paint();
738e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                        p.setFilterBitmap(true);
739e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                        c.drawBitmap(crop, m, p);
740e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        crop = tmp;
741e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
742e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
743e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
744e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (mSaveCroppedBitmap) {
745e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    mCroppedBitmap = crop;
746e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
747e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
748e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // Get output compression format
749e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                CompressFormat cf =
750e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
751e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
752e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // Compress to byte array
753e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
754e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
755e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    // If we need to set to the wallpaper, set it
756e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                    if (mSetWallpaper && wallpaperManager != null) {
757e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                        try {
758e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                            byte[] outByteArray = tmpOut.toByteArray();
759e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
760e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                            if (mOnBitmapCroppedHandler != null) {
761e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
762e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                            }
763e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                        } catch (IOException e) {
764e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                            Log.w(LOGTAG, "cannot write stream to wallpaper", e);
765e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                            failure = true;
766e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        }
767e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
768e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                } else {
769e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    Log.w(LOGTAG, "cannot compress bitmap");
770e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    failure = true;
771e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
772e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
773e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            return !failure; // True if any of the operations failed
774e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
775e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
776e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        @Override
777e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        protected Boolean doInBackground(Void... params) {
778e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            return cropBitmap();
779e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
780e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
781e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        @Override
782e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        protected void onPostExecute(Boolean result) {
783e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            if (mOnEndRunnable != null) {
784e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                mOnEndRunnable.run();
785e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            }
786e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
787e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
788e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
789e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected static RectF getMaxCropRect(
790e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
791e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        RectF cropRect = new RectF();
792e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Get a crop rect that will fit this
793e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
794e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka             cropRect.top = 0;
795e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka             cropRect.bottom = inHeight;
796e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka             cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
797e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka             cropRect.right = inWidth - cropRect.left;
798e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka             if (leftAligned) {
799e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                 cropRect.right -= cropRect.left;
800e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                 cropRect.left = 0;
801e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka             }
802e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else {
803e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropRect.left = 0;
804e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropRect.right = inWidth;
805e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
806e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            cropRect.bottom = inHeight - cropRect.top;
807e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
808e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return cropRect;
809e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
810e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
811e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
812e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
813e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
814e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
815e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected static String getFileExtension(String requestFormat) {
816e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        String outputFormat = (requestFormat == null)
817e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ? "jpg"
818e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                : requestFormat;
819e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        outputFormat = outputFormat.toLowerCase();
820e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return (outputFormat.equals("png") || outputFormat.equals("gif"))
821e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ? "png" // We don't support gif compression.
822e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                : "jpg";
823e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
824e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka}
825