CropActivity.java revision 6fe165b7d28299d5b2f97deb135b233d84eb300f
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.filtershow.crop;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.WallpaperManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.Bitmap.CompressFormat;
27import android.graphics.BitmapFactory;
28import android.graphics.Point;
29import android.graphics.Rect;
30import android.graphics.RectF;
31import android.net.Uri;
32import android.os.AsyncTask;
33import android.os.Bundle;
34import android.provider.MediaStore;
35import android.util.DisplayMetrics;
36import android.util.Log;
37import android.util.TypedValue;
38import android.view.Display;
39import android.view.View;
40import android.view.View.OnClickListener;
41import android.view.WindowManager;
42import android.widget.Toast;
43
44import com.android.gallery3d.R;
45
46import java.io.FileNotFoundException;
47import java.io.IOException;
48import java.io.OutputStream;
49
50/**
51 * Activity for cropping an image.
52 */
53public class CropActivity extends Activity {
54    private static final String LOGTAG = "CropActivity";
55    private CropExtras mCropExtras = null;
56    private LoadBitmapTask mLoadBitmapTask = null;
57    private SaveBitmapTask mSaveBitmapTask = null;
58    private SetWallpaperTask mSetWallpaperTask = null;
59    private Bitmap mOriginalBitmap = null;
60    private CropView mCropView = null;
61    private int mActiveBackgroundIO = 0;
62    private Intent mResultIntent = null;
63    private static final int SELECT_PICTURE = 1; // request code for picker
64    private static final int DEFAULT_DENSITY = 133;
65    private static final int DEFAULT_COMPRESS_QUALITY = 90;
66    public static final int MAX_BMAP_IN_INTENT = 990000;
67
68    @Override
69    public void onCreate(Bundle savedInstanceState) {
70        super.onCreate(savedInstanceState);
71        Intent intent = getIntent();
72        mResultIntent = new Intent();
73        setResult(RESULT_CANCELED, mResultIntent);
74        mCropExtras = getExtrasFromIntent(intent);
75        if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
76            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
77        }
78
79        setContentView(R.layout.crop_activity);
80        mCropView = (CropView) findViewById(R.id.cropView);
81
82        if (intent.getData() != null) {
83            startLoadBitmap(intent.getData());
84        } else {
85            pickImage();
86        }
87        ActionBar actionBar = getActionBar();
88        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
89        actionBar.setCustomView(R.layout.filtershow_actionbar);
90
91        View saveButton = actionBar.getCustomView();
92        saveButton.setOnClickListener(new OnClickListener() {
93            @Override
94            public void onClick(View view) {
95                startFinishOutput();
96            }
97        });
98    }
99
100    @Override
101    protected void onDestroy() {
102        if (mLoadBitmapTask != null) {
103            mLoadBitmapTask.cancel(false);
104        }
105        super.onDestroy();
106    }
107
108    /**
109     * Opens a selector in Gallery to chose an image for use when none was given
110     * in the CROP intent.
111     */
112    public void pickImage() {
113        Intent intent = new Intent();
114        intent.setType("image/*");
115        intent.setAction(Intent.ACTION_GET_CONTENT);
116        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
117                SELECT_PICTURE);
118    }
119
120    /**
121     * Callback for pickImage().
122     */
123    @Override
124    public void onActivityResult(int requestCode, int resultCode, Intent data) {
125        if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
126            Uri selectedImageUri = data.getData();
127            startLoadBitmap(selectedImageUri);
128        }
129    }
130
131    /**
132     * Gets the crop extras from the intent, or null if none exist.
133     */
134    public static CropExtras getExtrasFromIntent(Intent intent) {
135        Bundle extras = intent.getExtras();
136        if (extras != null) {
137            return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
138                    extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
139                    extras.getBoolean(CropExtras.KEY_SCALE, true) &&
140                            extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
141                    extras.getInt(CropExtras.KEY_ASPECT_X, 0),
142                    extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
143                    extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
144                    extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
145                    (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
146                    extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
147                    extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
148                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
149                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
150        }
151        return null;
152    }
153
154    /**
155     * Gets screen size metric.
156     */
157    private int getScreenImageSize() {
158        DisplayMetrics metrics = new DisplayMetrics();
159        Display display = getWindowManager().getDefaultDisplay();
160        Point size = new Point();
161        display.getSize(size);
162        display.getMetrics(metrics);
163        int msize = Math.min(size.x, size.y);
164        // TODO: WTF
165        return (DEFAULT_DENSITY * msize) / metrics.densityDpi + 512;
166    }
167
168    /**
169     * Method that loads a bitmap in an async task.
170     */
171    private void startLoadBitmap(Uri uri) {
172        mActiveBackgroundIO++;
173        final View loading = findViewById(R.id.loading);
174        loading.setVisibility(View.VISIBLE);
175        mLoadBitmapTask = new LoadBitmapTask();
176        mLoadBitmapTask.execute(uri);
177    }
178
179    /**
180     * Method called on UI thread with loaded bitmap.
181     */
182    private void doneLoadBitmap(Bitmap bitmap) {
183        mActiveBackgroundIO--;
184        final View loading = findViewById(R.id.loading);
185        loading.setVisibility(View.GONE);
186        mOriginalBitmap = bitmap;
187        // TODO: move these to dimens folder
188        if (bitmap != null) {
189            mCropView.setup(bitmap, (int) getPixelsFromDip(55), (int) getPixelsFromDip(25));
190        } else {
191            Log.w(LOGTAG, "could not load image for cropping");
192            cannotLoadImage();
193            setResult(RESULT_CANCELED, mResultIntent);
194            done();
195        }
196    }
197
198    /**
199     * Display toast for image loading failure.
200     */
201    private void cannotLoadImage() {
202        CharSequence text = getString(R.string.cannot_load_image);
203        Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
204        toast.show();
205    }
206
207    /**
208     * AsyncTask for loading a bitmap into memory.
209     *
210     * @see #startLoadBitmap(Uri)
211     * @see #doneLoadBitmap(Bitmap)
212     */
213    private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
214        int mBitmapSize;
215        Context mContext;
216        Rect mOriginalBounds;
217
218        public LoadBitmapTask() {
219            mBitmapSize = getScreenImageSize();
220            Log.v(LOGTAG, "bitmap size: " + mBitmapSize);
221            mContext = getApplicationContext();
222            mOriginalBounds = new Rect();
223        }
224
225        @Override
226        protected Bitmap doInBackground(Uri... params) {
227            Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize,
228                    mOriginalBounds);
229            return bmap;
230        }
231
232        @Override
233        protected void onPostExecute(Bitmap result) {
234            doneLoadBitmap(result);
235            // super.onPostExecute(result);
236        }
237    }
238
239    private void startSaveBitmap(Bitmap bmap, Uri uri, String format) {
240        if (bmap == null || uri == null) {
241            throw new IllegalArgumentException("bad argument to startSaveBitmap");
242        }
243        mActiveBackgroundIO++;
244        final View loading = findViewById(R.id.loading);
245        loading.setVisibility(View.VISIBLE);
246        mSaveBitmapTask = new SaveBitmapTask(uri, format);
247        mSaveBitmapTask.execute(bmap);
248    }
249
250    private void doneSaveBitmap(Uri uri) {
251        mActiveBackgroundIO--;
252        final View loading = findViewById(R.id.loading);
253        loading.setVisibility(View.GONE);
254        if (uri == null) {
255            Log.w(LOGTAG, "failed to save bitmap");
256            setResult(RESULT_CANCELED, mResultIntent);
257            done();
258            return;
259        }
260        done();
261    }
262
263    private class SaveBitmapTask extends AsyncTask<Bitmap, Void, Boolean> {
264
265        OutputStream mOutStream = null;
266        String mOutputFormat = null;
267        Uri mOutUri = null;
268
269        public SaveBitmapTask(Uri uri, String outputFormat) {
270            mOutputFormat = outputFormat;
271            mOutStream = null;
272            mOutUri = uri;
273            try {
274                mOutStream = getContentResolver().openOutputStream(uri);
275            } catch (FileNotFoundException e) {
276                Log.w(LOGTAG, "cannot write output: " + mOutUri.toString(), e);
277            }
278        }
279
280        @Override
281        protected Boolean doInBackground(Bitmap... params) {
282            if (mOutStream == null) {
283                return false;
284            }
285            CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
286            return params[0].compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream);
287        }
288
289        @Override
290        protected void onPostExecute(Boolean result) {
291            if (result.booleanValue() == false) {
292                Log.w(LOGTAG, "could not compress to output: " + mOutUri.toString());
293                doneSaveBitmap(null);
294            }
295            doneSaveBitmap(mOutUri);
296        }
297    }
298
299    private void startSetWallpaper(Bitmap bmap) {
300        if (bmap == null) {
301            throw new IllegalArgumentException("bad argument to startSetWallpaper");
302        }
303        mActiveBackgroundIO++;
304        Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
305        mSetWallpaperTask = new SetWallpaperTask();
306        mSetWallpaperTask.execute(bmap);
307
308    }
309
310    private void doneSetWallpaper() {
311        mActiveBackgroundIO--;
312        done();
313    }
314
315    private class SetWallpaperTask extends AsyncTask<Bitmap, Void, Boolean> {
316        private final WallpaperManager mWPManager;
317
318        public SetWallpaperTask() {
319            mWPManager = WallpaperManager.getInstance(getApplicationContext());
320        }
321
322        @Override
323        protected Boolean doInBackground(Bitmap... params) {
324            try {
325                mWPManager.setBitmap(params[0]);
326            } catch (IOException e) {
327                Log.w(LOGTAG, "fail to set wall paper", e);
328            }
329            return true;
330        }
331
332        @Override
333        protected void onPostExecute(Boolean result) {
334            doneSetWallpaper();
335        }
336    }
337
338    private void startFinishOutput() {
339        if (mOriginalBitmap != null && mCropExtras != null) {
340            Bitmap cropped = null;
341            if (mCropExtras.getExtraOutput() != null) {
342                if (cropped == null) {
343                    cropped = getCroppedImage(mOriginalBitmap);
344                }
345                startSaveBitmap(cropped, mCropExtras.getExtraOutput(),
346                        mCropExtras.getOutputFormat());
347            }
348            if (mCropExtras.getSetAsWallpaper()) {
349                if (cropped == null) {
350                    cropped = getCroppedImage(mOriginalBitmap);
351                }
352                startSetWallpaper(cropped);
353            }
354            if (mCropExtras.getReturnData()) {
355                if (cropped == null) {
356                    cropped = getCroppedImage(mOriginalBitmap);
357                }
358                int bmapSize = cropped.getRowBytes() * cropped.getHeight();
359                if (bmapSize > MAX_BMAP_IN_INTENT) {
360                    Log.w(LOGTAG, "Bitmap too large to be returned via intent");
361                } else {
362                    mResultIntent.putExtra(CropExtras.KEY_DATA, cropped);
363                }
364            }
365            setResult(RESULT_OK, mResultIntent);
366        } else {
367            setResult(RESULT_CANCELED, mResultIntent);
368        }
369        done();
370    }
371
372    private void done() {
373        if (mActiveBackgroundIO == 0) {
374            finish();
375        }
376    }
377
378    private Bitmap getCroppedImage(Bitmap image) {
379        RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
380        RectF crop = getBitmapCrop(imageBounds);
381        if (crop == null) {
382            return image;
383        }
384        Rect intCrop = new Rect();
385        crop.roundOut(intCrop);
386        return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
387                intCrop.height());
388    }
389
390    private RectF getBitmapCrop(RectF imageBounds) {
391        RectF crop = new RectF();
392        if (!mCropView.getCropBounds(crop, imageBounds)) {
393            Log.w(LOGTAG, "could not get crop");
394            return null;
395        }
396        return crop;
397    }
398
399    /**
400     * Helper method for unit conversions.
401     */
402    public float getPixelsFromDip(float value) {
403        Resources r = getResources();
404        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
405                r.getDisplayMetrics());
406    }
407
408    private static CompressFormat convertExtensionToCompressFormat(String extension) {
409        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
410    }
411
412    private static String getFileExtension(String requestFormat) {
413        String outputFormat = (requestFormat == null)
414                ? "jpg"
415                : requestFormat;
416        outputFormat = outputFormat.toLowerCase();
417        return (outputFormat.equals("png") || outputFormat.equals("gif"))
418                ? "png" // We don't support gif compression.
419                : "jpg";
420    }
421
422}
423