FilterShowActivity.java revision f7b8ac02984b3e8136db405471979b160b6485a6
1/*
2 * Copyright (C) 2012 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;
18
19import android.app.ActionBar;
20import android.app.AlertDialog;
21import android.app.ProgressDialog;
22import android.app.WallpaperManager;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.res.Configuration;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.Color;
31import android.graphics.Point;
32import android.graphics.drawable.Drawable;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Bundle;
36import android.provider.MediaStore;
37import android.support.v4.app.FragmentActivity;
38import android.support.v4.app.FragmentTransaction;
39import android.util.DisplayMetrics;
40import android.util.Log;
41import android.util.TypedValue;
42import android.view.*;
43import android.view.View.OnClickListener;
44import android.widget.AdapterView;
45import android.widget.AdapterView.OnItemClickListener;
46import android.widget.FrameLayout;
47import android.widget.ImageButton;
48import android.widget.LinearLayout;
49import android.widget.ListView;
50import android.widget.ShareActionProvider;
51import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
52import android.widget.Toast;
53
54import com.android.gallery3d.R;
55import com.android.gallery3d.data.LocalAlbum;
56import com.android.gallery3d.filtershow.cache.CachingPipeline;
57import com.android.gallery3d.filtershow.cache.FilteringPipeline;
58import com.android.gallery3d.filtershow.cache.ImageLoader;
59import com.android.gallery3d.filtershow.crop.CropExtras;
60import com.android.gallery3d.filtershow.editors.BasicEditor;
61import com.android.gallery3d.filtershow.editors.EditorCrop;
62import com.android.gallery3d.filtershow.editors.EditorDraw;
63import com.android.gallery3d.filtershow.editors.EditorFlip;
64import com.android.gallery3d.filtershow.editors.EditorInfo;
65import com.android.gallery3d.filtershow.editors.EditorManager;
66import com.android.gallery3d.filtershow.editors.EditorRedEye;
67import com.android.gallery3d.filtershow.editors.EditorRotate;
68import com.android.gallery3d.filtershow.editors.EditorStraighten;
69import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
70import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
71import com.android.gallery3d.filtershow.filters.*;
72import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
73import com.android.gallery3d.filtershow.imageshow.ImageCrop;
74import com.android.gallery3d.filtershow.imageshow.ImageShow;
75import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet;
76import com.android.gallery3d.filtershow.imageshow.MasterImage;
77import com.android.gallery3d.filtershow.presets.ImagePreset;
78import com.android.gallery3d.filtershow.provider.SharedImageProvider;
79import com.android.gallery3d.filtershow.state.StateAdapter;
80import com.android.gallery3d.filtershow.state.StatePanel;
81import com.android.gallery3d.filtershow.tools.BitmapTask;
82import com.android.gallery3d.filtershow.tools.SaveCopyTask;
83import com.android.gallery3d.filtershow.ui.FilterIconButton;
84import com.android.gallery3d.filtershow.ui.FramedTextButton;
85import com.android.gallery3d.filtershow.ui.Spline;
86import com.android.gallery3d.util.GalleryUtils;
87import com.android.photos.data.GalleryBitmapPool;
88
89import java.io.File;
90import java.io.IOException;
91import java.lang.ref.WeakReference;
92import java.util.Vector;
93
94public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
95        OnShareTargetSelectedListener {
96
97    private String mPanelFragmentTag = "StatePanel";
98
99    // fields for supporting crop action
100    public static final String CROP_ACTION = "com.android.camera.action.CROP";
101    private CropExtras mCropExtras = null;
102    private String mAction = "";
103    MasterImage mMasterImage = null;
104
105    private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
106
107    public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
108    public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
109    public static final int MAX_BMAP_IN_INTENT = 990000;
110    private final PanelController mPanelController = new PanelController();
111    private ImageLoader mImageLoader = null;
112    private ImageShow mImageShow = null;
113    private ImageTinyPlanet mImageTinyPlanet = null;
114
115    private View mSaveButton = null;
116
117    private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
118
119    private static final int SELECT_PICTURE = 1;
120    private static final String LOGTAG = "FilterShowActivity";
121    protected static final boolean ANIMATE_PANELS = true;
122    private static int mImageBorderSize = 4; // in percent
123
124    private boolean mShowingTinyPlanet = false;
125    private boolean mShowingImageStatePanel = false;
126
127    private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
128
129    private ShareActionProvider mShareActionProvider;
130    private File mSharedOutputFile = null;
131
132    private boolean mSharingImage = false;
133
134    private WeakReference<ProgressDialog> mSavingProgressDialog;
135
136    private LoadBitmapTask mLoadBitmapTask;
137    private FilterIconButton mNullFxFilter;
138    private FilterIconButton mNullBorderFilter;
139    private int mIconSeedSize = 140;
140
141    private View mImageCategoryPanel = null;
142
143    @Override
144    public void onCreate(Bundle savedInstanceState) {
145        super.onCreate(savedInstanceState);
146
147        clearGalleryBitmapPool();
148
149        CachingPipeline.createRenderscriptContext(this);
150        setupMasterImage();
151        setDefaultValues();
152        fillEditors();
153
154        loadXML();
155
156        if (getResources().getConfiguration().orientation
157                == Configuration.ORIENTATION_LANDSCAPE) {
158            mShowingImageStatePanel = true;
159        }
160
161        if (mShowingImageStatePanel && (savedInstanceState == null)) {
162            loadImageStatePanel();
163        }
164
165        setDefaultPreset();
166
167        processIntent();
168    }
169
170    private void loadXML() {
171        setContentView(R.layout.filtershow_activity);
172
173        ((ViewStub) findViewById(R.id.stateCategoryStub)).inflate();
174        ((ViewStub) findViewById(R.id.editorPanelStub)).inflate();
175
176        ActionBar actionBar = getActionBar();
177        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
178        actionBar.setCustomView(R.layout.filtershow_actionbar);
179
180        mSaveButton = actionBar.getCustomView();
181        mSaveButton.setOnClickListener(new OnClickListener() {
182            @Override
183            public void onClick(View view) {
184                saveImage();
185            }
186        });
187
188        mImageShow = (ImageShow) findViewById(R.id.imageShow);
189        mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet);
190        mImageViews.add(mImageShow);
191        mImageViews.add(mImageTinyPlanet);
192
193        setupEditors();
194
195        mEditorPlaceHolder.hide();
196
197        mImageShow.setImageLoader(mImageLoader);
198        mImageTinyPlanet.setImageLoader(mImageLoader);
199
200        mPanelController.clear();
201        mPanelController.setActivity(this);
202        mPanelController.setEditorPlaceHolder(mEditorPlaceHolder);
203
204        mPanelController.addImageView(findViewById(R.id.imageShow));
205        mPanelController.addImageView(findViewById(R.id.imageTinyPlanet));
206
207        mPanelController.addPanel(R.id.fxButton, R.id.fxList, 0);
208        mPanelController.addPanel(R.id.borderButton, R.id.bordersList, 1);
209        mPanelController.addPanel(R.id.geometryButton, R.id.geometryList, 2);
210        mPanelController.addPanel(R.id.colorsButton, R.id.colorsFxList, 3);
211
212        fillFx((LinearLayout) findViewById(R.id.listFilters), R.id.fxButton);
213        setupBorders();
214        fillGeometry();
215        fillFilters();
216
217        mPanelController.addView(findViewById(R.id.applyEffect));
218
219        setupStatePanel();
220
221        mImageCategoryPanel = findViewById(R.id.imageCategoryPanel);
222    }
223
224    public void hideCategoryPanel() {
225        mImageCategoryPanel.setVisibility(View.GONE);
226    }
227
228    public void showCategoryPanel() {
229        mImageCategoryPanel.setVisibility(View.VISIBLE);
230    }
231
232    public void setupStatePanel() {
233        mImageLoader.setAdapter(mMasterImage.getHistory());
234        mPanelController.setRowPanel(findViewById(R.id.secondRowPanel));
235        mPanelController.setUtilityPanel(this, findViewById(R.id.filterButtonsList));
236        mPanelController.setCurrentPanel(R.id.fxButton);
237    }
238
239    private void fillPanel(Vector<FilterRepresentation> representations, int layoutId, int buttonId) {
240        ImageButton button = (ImageButton) findViewById(buttonId);
241        LinearLayout layout = (LinearLayout) findViewById(layoutId);
242
243        for (FilterRepresentation representation : representations) {
244            setupFilterRepresentationButton(representation, layout, button);
245        }
246    }
247
248    private void fillFilters() {
249        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
250        FiltersManager filtersManager = FiltersManager.getManager();
251        filtersManager.addEffects(filtersRepresentations);
252        fillPanel(filtersRepresentations, R.id.listColorsFx, R.id.colorsButton);
253    }
254
255    private void fillGeometry() {
256        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
257        FiltersManager filtersManager = FiltersManager.getManager();
258
259        GeometryMetadata geo = new GeometryMetadata();
260        int[] editorsId = geo.getEditorIds();
261        for (int i = 0; i < editorsId.length; i++) {
262            int editorId = editorsId[i];
263            GeometryMetadata geometry = new GeometryMetadata(geo);
264            geometry.setEditorId(editorId);
265            EditorInfo editorInfo = (EditorInfo) mEditorPlaceHolder.getEditor(editorId);
266            geometry.setTextId(editorInfo.getTextId());
267            geometry.setOverlayId(editorInfo.getOverlayId());
268            geometry.setOverlayOnly(editorInfo.getOverlayOnly());
269            filtersRepresentations.add(geometry);
270        }
271
272        filtersManager.addTools(filtersRepresentations);
273        fillPanel(filtersRepresentations, R.id.listGeometry, R.id.geometryButton);
274    }
275
276    private void processIntent() {
277        Intent intent = getIntent();
278        if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
279            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
280        }
281
282        mAction = intent.getAction();
283
284        if (intent.getData() != null) {
285            startLoadBitmap(intent.getData());
286        } else {
287            pickImage();
288        }
289
290        // Handle behavior for various actions
291        if (mAction.equalsIgnoreCase(CROP_ACTION)) {
292            Bundle extras = intent.getExtras();
293            if (extras != null) {
294                mCropExtras = new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
295                        extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
296                        extras.getBoolean(CropExtras.KEY_SCALE, true) &&
297                                extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
298                        extras.getInt(CropExtras.KEY_ASPECT_X, 0),
299                        extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
300                        extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
301                        extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
302                        (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
303                        extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
304                        extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
305                        extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
306                        extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
307
308                if (mCropExtras.getShowWhenLocked()) {
309                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
310                }
311                mImageShow.getImagePreset().mGeoData.setCropExtras(mCropExtras);
312
313                // FIXME: moving to editors breaks the crop action
314                EditorCrop crop = (EditorCrop) mEditorPlaceHolder.getEditor(EditorCrop.ID);
315
316                crop.setExtras(mCropExtras);
317                String s = getString(R.string.Fixed);
318                crop.setAspectString(s);
319                crop.setCropActionFlag(true);
320                mPanelController.setFixedAspect(mCropExtras.getAspectX() > 0
321                        && mCropExtras.getAspectY() > 0);
322            }
323        }
324    }
325
326    private void setupEditors() {
327        mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
328        EditorManager.addEditors(mEditorPlaceHolder);
329        mEditorPlaceHolder.setOldViews(mImageViews);
330        mEditorPlaceHolder.setImageLoader(mImageLoader);
331    }
332
333    private void fillEditors() {
334        mEditorPlaceHolder.addEditor(new EditorDraw());
335        mEditorPlaceHolder.addEditor(new BasicEditor());
336        mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
337        mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
338        mEditorPlaceHolder.addEditor(new EditorRedEye());
339        mEditorPlaceHolder.addEditor(new EditorCrop());
340        mEditorPlaceHolder.addEditor(new EditorFlip());
341        mEditorPlaceHolder.addEditor(new EditorRotate());
342        mEditorPlaceHolder.addEditor(new EditorStraighten());
343    }
344
345    private void setDefaultValues() {
346        ImageFilter.setActivityForMemoryToasts(this);
347
348        Resources res = getResources();
349        FiltersManager.setResources(res);
350
351        ImageShow.setDefaultBackgroundColor(res.getColor(R.color.background_screen));
352        // TODO: get those values from XML.
353        FramedTextButton.setTextSize((int) getPixelsFromDip(14));
354        FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
355        FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
356        ImageShow.setTextSize((int) getPixelsFromDip(12));
357        ImageShow.setTextPadding((int) getPixelsFromDip(10));
358        ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4));
359        ImageShow.setOriginalTextSize((int) getPixelsFromDip(18));
360        ImageShow.setOriginalText(res.getString(R.string.original_picture_text));
361        mIconSeedSize = res.getDimensionPixelSize(R.dimen.thumbnail_size);
362        // TODO: pick correct value
363        // MasterImage.setIconSeedSize(mIconSeedSize);
364
365        Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
366        int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
367        Spline.setCurveHandle(curveHandle, curveHandleSize);
368        Spline.setCurveWidth((int) getPixelsFromDip(3));
369
370        ImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
371        ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
372        ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
373    }
374
375    private void startLoadBitmap(Uri uri) {
376        final View filters = findViewById(R.id.filtersPanel);
377        final View loading = findViewById(R.id.loading);
378        final View imageShow = findViewById(R.id.imageShow);
379        imageShow.setVisibility(View.INVISIBLE);
380        filters.setVisibility(View.INVISIBLE);
381        loading.setVisibility(View.VISIBLE);
382
383        View tinyPlanetView = findViewById(EditorTinyPlanet.ID);
384        if (tinyPlanetView != null) {
385            mShowingTinyPlanet = false;
386            tinyPlanetView.setVisibility(View.GONE);
387        }
388        mLoadBitmapTask = new LoadBitmapTask(tinyPlanetView);
389        mLoadBitmapTask.execute(uri);
390    }
391
392    private void setupBorders() {
393        LinearLayout list = (LinearLayout) findViewById(R.id.listBorders);
394        Vector<FilterRepresentation> borders = new Vector<FilterRepresentation>();
395        ImageButton borderButton = (ImageButton) findViewById(R.id.borderButton);
396
397        // The "no border" implementation
398        borders.add(new FilterImageBorderRepresentation(0));
399
400        // Google-build borders
401        FiltersManager.getManager().addBorders(borders);
402
403        // Regular borders
404        borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_4x5));
405        borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_brush));
406        borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_grunge));
407        borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_sumi_e));
408        borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_tape));
409        borders.add(new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize, 0));
410        borders.add(new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize,
411                mImageBorderSize));
412        borders.add(new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize, 0));
413        borders.add(new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize,
414                mImageBorderSize));
415        int creamColor = Color.argb(255, 237, 237, 227);
416        borders.add(new FilterColorBorderRepresentation(creamColor, mImageBorderSize, 0));
417        borders.add(new FilterColorBorderRepresentation(creamColor, mImageBorderSize,
418                mImageBorderSize));
419        for (int i = 0; i < borders.size(); i++) {
420            FilterRepresentation filter = borders.elementAt(i);
421            filter.setName(getString(R.string.borders));
422            if (i == 0) {
423                filter.setName(getString(R.string.none));
424            }
425            FilterIconButton b = setupFilterRepresentationButton(filter, list, borderButton);
426            if (i == 0) {
427                mNullBorderFilter = b;
428                mNullBorderFilter.setSelected(true);
429            }
430        }
431    }
432
433    private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
434        View mTinyPlanetButton;
435        int mBitmapSize;
436
437        public LoadBitmapTask(View button) {
438            mTinyPlanetButton = button;
439            mBitmapSize = getScreenImageSize();
440        }
441
442        @Override
443        protected Boolean doInBackground(Uri... params) {
444            if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) {
445                return false;
446            }
447            publishProgress(mImageLoader.queryLightCycle360());
448            return true;
449        }
450
451        @Override
452        protected void onProgressUpdate(Boolean... values) {
453            super.onProgressUpdate(values);
454            if (isCancelled()) {
455                return;
456            }
457            if (values[0]) {
458                mShowingTinyPlanet = true;
459                mTinyPlanetButton.setVisibility(View.VISIBLE);
460            }
461        }
462
463        @Override
464        protected void onPostExecute(Boolean result) {
465
466            if (isCancelled()) {
467                return;
468            }
469
470            if (!result) {
471                cannotLoadImage();
472            }
473
474            final View loading = findViewById(R.id.loading);
475            loading.setVisibility(View.GONE);
476            final View filters = findViewById(R.id.filtersPanel);
477            filters.setVisibility(View.VISIBLE);
478            if (PanelController.useAnimationsLayer()) {
479                float y = filters.getY();
480                filters.setY(y + filters.getHeight());
481                filters.animate().setDuration(600).y(y).withLayer().start();
482            }
483            final View imageShow = findViewById(R.id.imageShow);
484            imageShow.setVisibility(View.VISIBLE);
485
486            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
487            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
488            pipeline.setOriginal(largeBitmap);
489            float previewScale = (float) largeBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
490            pipeline.setPreviewScaleFactor(previewScale);
491            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
492            if (highresBitmap != null) {
493                float highResPreviewScale = (float) highresBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
494                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
495            }
496            pipeline.turnOnPipeline(true);
497            MasterImage.getImage().setOriginalGeometry(largeBitmap);
498            mLoadBitmapTask = null;
499
500            if (mAction == CROP_ACTION) {
501                mPanelController.showComponent(findViewById(EditorCrop.ID));
502            } else if (mAction == TINY_PLANET_ACTION) {
503                mPanelController.showComponent(findViewById(EditorTinyPlanet.ID));
504            }
505            super.onPostExecute(result);
506        }
507
508    }
509
510    private void clearGalleryBitmapPool() {
511        (new AsyncTask<Void, Void, Void>() {
512            @Override
513            protected Void doInBackground(Void... params) {
514                // Free memory held in Gallery's Bitmap pool.  May be O(n) for n bitmaps.
515                GalleryBitmapPool.getInstance().clear();
516                return null;
517            }
518        }).execute();
519    }
520
521    @Override
522    protected void onDestroy() {
523        if (mLoadBitmapTask != null) {
524            mLoadBitmapTask.cancel(false);
525        }
526        // TODO:  Using singletons is a bad design choice for many of these
527        // due static reference leaks and in general.  Please refactor.
528        FilteringPipeline.getPipeline().turnOnPipeline(false);
529        MasterImage.reset();
530        FilteringPipeline.reset();
531        ImageFilter.resetStatics();
532        FiltersManager.getPreviewManager().freeRSFilterScripts();
533        FiltersManager.getManager().freeRSFilterScripts();
534        FiltersManager.getHighresManager().freeRSFilterScripts();
535        FiltersManager.reset();
536        CachingPipeline.destroyRenderScriptContext();
537        super.onDestroy();
538    }
539
540    private int translateMainPanel(View viewPanel) {
541        int accessoryPanelWidth = viewPanel.getWidth();
542        if (accessoryPanelWidth == 0) {
543            // TODO: fixes this by using a fragment. Currently,
544            // the first time we get called the panel hasn't been
545            // layed out yet, so we get a size zero.
546            accessoryPanelWidth = (int) getPixelsFromDip(200);
547        }
548        int mainViewWidth = findViewById(R.id.mainView).getWidth();
549        int mainPanelWidth = mImageShow.getDisplayedImageBounds().width();
550        if (mainPanelWidth == 0) {
551            mainPanelWidth = mainViewWidth;
552        }
553        int filtersPanelWidth = findViewById(R.id.filtersPanel).getWidth();
554        if (mainPanelWidth < filtersPanelWidth) {
555            mainPanelWidth = filtersPanelWidth;
556        }
557        int leftOver = mainViewWidth - mainPanelWidth - accessoryPanelWidth;
558        if (leftOver < 0) {
559            return -accessoryPanelWidth;
560        }
561        return 0;
562    }
563
564    private int getScreenImageSize() {
565        DisplayMetrics metrics = new DisplayMetrics();
566        Display display = getWindowManager().getDefaultDisplay();
567        Point size = new Point();
568        display.getSize(size);
569        display.getMetrics(metrics);
570        int msize = Math.min(size.x, size.y);
571        return (133 * msize) / metrics.densityDpi;
572    }
573
574    private void showSavingProgress(String albumName) {
575        ProgressDialog progress;
576        if (mSavingProgressDialog != null) {
577            progress = mSavingProgressDialog.get();
578            if (progress != null) {
579                progress.show();
580                return;
581            }
582        }
583        // TODO: Allow cancellation of the saving process
584        String progressText;
585        if (albumName == null) {
586            progressText = getString(R.string.saving_image);
587        } else {
588            progressText = getString(R.string.filtershow_saving_image, albumName);
589        }
590        progress = ProgressDialog.show(this, "", progressText, true, false);
591        mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
592    }
593
594    private void hideSavingProgress() {
595        if (mSavingProgressDialog != null) {
596            ProgressDialog progress = mSavingProgressDialog.get();
597            if (progress != null)
598                progress.dismiss();
599        }
600    }
601
602    public void completeSaveImage(Uri saveUri) {
603        if (mSharingImage && mSharedOutputFile != null) {
604            // Image saved, we unblock the content provider
605            Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
606                    Uri.encode(mSharedOutputFile.getAbsolutePath()));
607            ContentValues values = new ContentValues();
608            values.put(SharedImageProvider.PREPARE, false);
609            getContentResolver().insert(uri, values);
610        }
611        setResult(RESULT_OK, new Intent().setData(saveUri));
612        hideSavingProgress();
613        finish();
614    }
615
616    @Override
617    public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
618        // First, let's tell the SharedImageProvider that it will need to wait
619        // for the image
620        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
621                Uri.encode(mSharedOutputFile.getAbsolutePath()));
622        ContentValues values = new ContentValues();
623        values.put(SharedImageProvider.PREPARE, true);
624        getContentResolver().insert(uri, values);
625        mSharingImage = true;
626
627        // Process and save the image in the background.
628        showSavingProgress(null);
629        mImageShow.saveImage(this, mSharedOutputFile);
630        return true;
631    }
632
633    private Intent getDefaultShareIntent() {
634        Intent intent = new Intent(Intent.ACTION_SEND);
635        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
636        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
637        intent.setType(SharedImageProvider.MIME_TYPE);
638        mSharedOutputFile = SaveCopyTask.getNewFile(this, mImageLoader.getUri());
639        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
640                Uri.encode(mSharedOutputFile.getAbsolutePath()));
641        intent.putExtra(Intent.EXTRA_STREAM, uri);
642        return intent;
643    }
644
645    @Override
646    public boolean onCreateOptionsMenu(Menu menu) {
647        getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
648        MenuItem showState = menu.findItem(R.id.showImageStateButton);
649        if (mShowingImageStatePanel) {
650            showState.setTitle(R.string.hide_imagestate_panel);
651        } else {
652            showState.setTitle(R.string.show_imagestate_panel);
653        }
654        mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
655                .getActionProvider();
656        mShareActionProvider.setShareIntent(getDefaultShareIntent());
657        mShareActionProvider.setOnShareTargetSelectedListener(this);
658
659        MenuItem undoItem = menu.findItem(R.id.undoButton);
660        MenuItem redoItem = menu.findItem(R.id.redoButton);
661        MenuItem resetItem = menu.findItem(R.id.resetHistoryButton);
662        mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
663        return true;
664    }
665
666    @Override
667    public void onPause() {
668        super.onPause();
669        if (mShareActionProvider != null) {
670            mShareActionProvider.setOnShareTargetSelectedListener(null);
671        }
672    }
673
674    @Override
675    public void onResume() {
676        super.onResume();
677        if (mShareActionProvider != null) {
678            mShareActionProvider.setOnShareTargetSelectedListener(this);
679        }
680    }
681
682    @Override
683    public boolean onOptionsItemSelected(MenuItem item) {
684        switch (item.getItemId()) {
685            case R.id.undoButton: {
686                mPanelController.resetParameters();
687                HistoryAdapter adapter = mMasterImage.getHistory();
688                int position = adapter.undo();
689                mMasterImage.onHistoryItemClick(position);
690                mImageShow.showToast("Undo");
691                invalidateViews();
692                return true;
693            }
694            case R.id.redoButton: {
695                HistoryAdapter adapter = mMasterImage.getHistory();
696                int position = adapter.redo();
697                mMasterImage.onHistoryItemClick(position);
698                mImageShow.showToast("Redo");
699                invalidateViews();
700                return true;
701            }
702            case R.id.resetHistoryButton: {
703                resetHistory();
704                return true;
705            }
706            case R.id.showImageStateButton: {
707                toggleImageStatePanel();
708                return true;
709            }
710            case android.R.id.home: {
711                saveImage();
712                return true;
713            }
714        }
715        return false;
716    }
717
718    public void enableSave(boolean enable) {
719        if (mSaveButton != null)
720            mSaveButton.setEnabled(enable);
721    }
722
723    public FilterIconButton setupFilterRepresentationButton(FilterRepresentation representation, LinearLayout panel, View button) {
724        LayoutInflater inflater =
725                (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
726        FilterIconButton icon = (FilterIconButton) inflater.inflate(R.layout.filtericonbutton,
727                panel, false);
728        if (representation.getTextId() != 0) {
729            representation.setName(getString(representation.getTextId()));
730        }
731        String text = representation.getName();
732        icon.setup(text, mPanelController, panel);
733        icon.setFilterRepresentation(representation);
734        icon.setId(representation.getEditorId());
735        mPanelController.addComponent(button, icon);
736        panel.addView(icon);
737        return icon;
738    }
739
740    private void fillFx(LinearLayout listFilters, int buttonId) {
741        // TODO: use listview
742        // TODO: load the filters straight from the filesystem
743
744        FilterFxRepresentation[] fxArray = new FilterFxRepresentation[18];
745        int p = 0;
746
747        int[] drawid = {
748                R.drawable.filtershow_fx_0005_punch,
749//                R.drawable.filtershow_fx_0000_vintage,
750//                R.drawable.filtershow_fx_0004_bw_contrast,
751                R.drawable.filtershow_fx_0002_bleach,
752                R.drawable.filtershow_fx_0001_instant,
753                R.drawable.filtershow_fx_0007_washout,
754                R.drawable.filtershow_fx_0003_blue_crush,
755                R.drawable.filtershow_fx_0008_washout_color,
756                R.drawable.filtershow_fx_0006_x_process
757        };
758
759        int[] fxNameid = {
760                R.string.ffx_punch,
761//                R.string.ffx_vintage,
762//                R.string.ffx_bw_contrast,
763                R.string.ffx_bleach,
764                R.string.ffx_instant,
765                R.string.ffx_washout,
766                R.string.ffx_blue_crush,
767                R.string.ffx_washout_color,
768                R.string.ffx_x_process
769        };
770
771        for (int i = 0; i < drawid.length; i++) {
772            FilterFxRepresentation fx = new FilterFxRepresentation(getString(fxNameid[i]), drawid[i], fxNameid[i]);
773            fxArray[p++] = fx;
774        }
775
776        ImageButton button = (ImageButton) findViewById(buttonId);
777
778        FilterFxRepresentation nullFx = new FilterFxRepresentation(getString(R.string.none), 0, R.string.none);
779        mNullFxFilter = setupFilterRepresentationButton(nullFx, listFilters, button);
780        mNullFxFilter.setSelected(true);
781
782        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
783        FiltersManager.getManager().addLooks(filtersRepresentations);
784        for (FilterRepresentation representation : filtersRepresentations) {
785            setupFilterRepresentationButton(representation, listFilters, button);
786        }
787
788        for (int i = 0; i < p; i++) {
789            setupFilterRepresentationButton(fxArray[i], listFilters, button);
790        }
791    }
792
793    public void setDefaultPreset() {
794        // Default preset (original)
795        ImagePreset preset = new ImagePreset(getString(R.string.history_original)); // empty
796        preset.setImageLoader(mImageLoader);
797
798        mMasterImage.setPreset(preset, true);
799    }
800
801    // //////////////////////////////////////////////////////////////////////////////
802    // Some utility functions
803    // TODO: finish the cleanup.
804
805    public void invalidateViews() {
806        for (ImageShow views : mImageViews) {
807            views.invalidate();
808            views.updateImage();
809        }
810    }
811
812    public void hideImageViews() {
813        for (View view : mImageViews) {
814            view.setVisibility(View.GONE);
815        }
816        mEditorPlaceHolder.hide();
817    }
818
819    // //////////////////////////////////////////////////////////////////////////////
820    // imageState panel...
821
822    private void toggleImageStatePanel() {
823        invalidateOptionsMenu();
824    }
825
826    private void loadImageStatePanel() {
827        StatePanel statePanel = new StatePanel();
828        if (findViewById(R.id.state_panel_container) != null) {
829            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
830            transaction.replace(R.id.state_panel_container, statePanel, mPanelFragmentTag);
831            transaction.commit();
832        }
833    }
834
835    @Override
836    public void onConfigurationChanged(Configuration newConfig)
837    {
838        super.onConfigurationChanged(newConfig);
839        setDefaultValues();
840        loadXML();
841        if (getResources().getConfiguration().orientation
842                == Configuration.ORIENTATION_LANDSCAPE) {
843            mShowingImageStatePanel = true;
844        } else if (mShowingImageStatePanel) {
845            toggleImageStatePanel();
846        }
847        if (mShowingImageStatePanel) {
848            loadImageStatePanel();
849        }
850        if (mShowingTinyPlanet == false) {
851            View tinyPlanetView = findViewById(EditorTinyPlanet.ID);
852            if (tinyPlanetView != null) {
853                tinyPlanetView.setVisibility(View.GONE);
854            }
855        }
856        final View loading = findViewById(R.id.loading);
857        loading.setVisibility(View.GONE);
858    }
859
860    public void setupMasterImage() {
861        mImageLoader = new ImageLoader(this, getApplicationContext());
862
863        HistoryAdapter mHistoryAdapter = new HistoryAdapter(
864                this, R.layout.filtershow_history_operation_row,
865                R.id.rowTextView);
866
867        StateAdapter mImageStateAdapter = new StateAdapter(this, 0);
868        MasterImage.reset();
869        mMasterImage = MasterImage.getImage();
870        mMasterImage.setHistoryAdapter(mHistoryAdapter);
871        mMasterImage.setStateAdapter(mImageStateAdapter);
872        mMasterImage.setActivity(this);
873        mMasterImage.setImageLoader(mImageLoader);
874
875        if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
876            mMasterImage.setSupportsHighRes(true);
877        } else {
878            mMasterImage.setSupportsHighRes(false);
879        }
880    }
881
882    public void dispatchNullFilterClick() {
883        mNullFxFilter.onClick(mNullFxFilter);
884        mNullBorderFilter.onClick(mNullBorderFilter);
885    }
886
887    void resetHistory() {
888        dispatchNullFilterClick();
889        HistoryAdapter adapter = mMasterImage.getHistory();
890        adapter.reset();
891        ImagePreset original = new ImagePreset(adapter.getItem(0));
892        mMasterImage.setPreset(original, true);
893        mPanelController.resetParameters();
894        invalidateViews();
895    }
896
897    @Override
898    public void onBackPressed() {
899        if (mPanelController.onBackPressed()) {
900            if (detectSpecialExitCases()) {
901                saveImage();
902            } else if(!mImageShow.hasModifications()) {
903                done();
904            } else {
905                AlertDialog.Builder builder = new AlertDialog.Builder(this);
906                builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
907                builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
908                    public void onClick(DialogInterface dialog, int id) {
909                        saveImage();
910                    }
911                });
912                builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
913                    public void onClick(DialogInterface dialog, int id) {
914                        done();
915                    }
916                });
917                builder.show();
918            }
919        }
920    }
921
922    public PanelController getPanelController() {
923        return mPanelController;
924    }
925
926    public void cannotLoadImage() {
927        CharSequence text = getString(R.string.cannot_load_image);
928        Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
929        toast.show();
930        finish();
931    }
932
933    // //////////////////////////////////////////////////////////////////////////////
934
935    public float getPixelsFromDip(float value) {
936        Resources r = getResources();
937        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
938                r.getDisplayMetrics());
939    }
940
941    @Override
942    public void onItemClick(AdapterView<?> parent, View view, int position,
943            long id) {
944        mMasterImage.onHistoryItemClick(position);
945        invalidateViews();
946    }
947
948    public void pickImage() {
949        Intent intent = new Intent();
950        intent.setType("image/*");
951        intent.setAction(Intent.ACTION_GET_CONTENT);
952        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
953                SELECT_PICTURE);
954    }
955
956    @Override
957    public void onActivityResult(int requestCode, int resultCode, Intent data) {
958        if (resultCode == RESULT_OK) {
959            if (requestCode == SELECT_PICTURE) {
960                Uri selectedImageUri = data.getData();
961                startLoadBitmap(selectedImageUri);
962            }
963        }
964    }
965
966    private boolean mSaveToExtraUri = false;
967    private boolean mSaveAsWallpaper = false;
968    private boolean mReturnAsExtra = false;
969    private boolean mOutputted = false;
970
971    public void saveImage() {
972        handleSpecialExitCases();
973        if (!mOutputted) {
974            if (mImageShow.hasModifications()) {
975                // Get the name of the album, to which the image will be saved
976                File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri());
977                int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
978                String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
979                showSavingProgress(albumName);
980                mImageShow.saveImage(this, null);
981            } else {
982                done();
983            }
984        }
985    }
986
987    public boolean detectSpecialExitCases() {
988        return mCropExtras != null && (mCropExtras.getExtraOutput() != null
989                || mCropExtras.getSetAsWallpaper() || mCropExtras.getReturnData());
990    }
991
992    public void handleSpecialExitCases() {
993        if (mCropExtras != null) {
994            if (mCropExtras.getExtraOutput() != null) {
995                mSaveToExtraUri = true;
996                mOutputted = true;
997            }
998            if (mCropExtras.getSetAsWallpaper()) {
999                mSaveAsWallpaper = true;
1000                mOutputted = true;
1001            }
1002            if (mCropExtras.getReturnData()) {
1003                mReturnAsExtra = true;
1004                mOutputted = true;
1005            }
1006            if (mOutputted) {
1007                mImageShow.getImagePreset().mGeoData.setUseCropExtrasFlag(true);
1008                showSavingProgress(null);
1009                mImageShow.returnFilteredResult(this);
1010            }
1011        }
1012    }
1013
1014    public void onFilteredResult(Bitmap filtered) {
1015        Intent intent = new Intent();
1016        intent.putExtra(CropExtras.KEY_CROPPED_RECT, mImageShow.getImageCropBounds());
1017        if (mSaveToExtraUri) {
1018            mImageShow.saveToUri(filtered, mCropExtras.getExtraOutput(),
1019                    mCropExtras.getOutputFormat(), this);
1020        }
1021        if (mSaveAsWallpaper) {
1022            setWallpaperInBackground(filtered);
1023        }
1024        if (mReturnAsExtra) {
1025            if (filtered != null) {
1026                int bmapSize = filtered.getRowBytes() * filtered.getHeight();
1027                /*
1028                 * Max size of Binder transaction buffer is 1Mb, so constrain
1029                 * Bitmap to be somewhat less than this, otherwise we get
1030                 * TransactionTooLargeExceptions.
1031                 */
1032                if (bmapSize > MAX_BMAP_IN_INTENT) {
1033                    Log.w(LOGTAG, "Bitmap too large to be returned via intent");
1034                } else {
1035                    intent.putExtra(CropExtras.KEY_DATA, filtered);
1036                }
1037            }
1038        }
1039        setResult(RESULT_OK, intent);
1040        if (!mSaveToExtraUri) {
1041            done();
1042        }
1043    }
1044
1045    void setWallpaperInBackground(final Bitmap bmap) {
1046        Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
1047        BitmapTask.Callbacks<FilterShowActivity> cb = new BitmapTask.Callbacks<FilterShowActivity>() {
1048            @Override
1049            public void onComplete(Bitmap result) {}
1050
1051            @Override
1052            public void onCancel() {}
1053
1054            @Override
1055            public Bitmap onExecute(FilterShowActivity param) {
1056                try {
1057                    WallpaperManager.getInstance(param).setBitmap(bmap);
1058                } catch (IOException e) {
1059                    Log.w(LOGTAG, "fail to set wall paper", e);
1060                }
1061                return null;
1062            }
1063        };
1064        (new BitmapTask<FilterShowActivity>(cb)).execute(this);
1065    }
1066
1067    public void done() {
1068        if (mOutputted) {
1069            hideSavingProgress();
1070        }
1071        finish();
1072    }
1073
1074    static {
1075        System.loadLibrary("jni_filtershow_filters");
1076    }
1077
1078}
1079