FilterShowActivity.java revision ce9ceff5776a9b0479c30cbeb2a9388b44df1865
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.content.ContentValues;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.pm.ActivityInfo;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.Point;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.os.AsyncTask;
33import android.os.Bundle;
34import android.os.Handler;
35import android.support.v4.app.Fragment;
36import android.support.v4.app.FragmentActivity;
37import android.support.v4.app.FragmentTransaction;
38import android.util.DisplayMetrics;
39import android.util.Log;
40import android.util.TypedValue;
41import android.view.Display;
42import android.view.Menu;
43import android.view.MenuItem;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.view.ViewPropertyAnimator;
47import android.view.WindowManager;
48import android.widget.AdapterView;
49import android.widget.AdapterView.OnItemClickListener;
50import android.widget.FrameLayout;
51import android.widget.ShareActionProvider;
52import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
53import android.widget.Toast;
54
55import com.android.gallery3d.R;
56import com.android.gallery3d.data.LocalAlbum;
57import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
58import com.android.gallery3d.filtershow.pipeline.FilteringPipeline;
59import com.android.gallery3d.filtershow.cache.ImageLoader;
60import com.android.gallery3d.filtershow.category.Action;
61import com.android.gallery3d.filtershow.category.CategoryAdapter;
62import com.android.gallery3d.filtershow.category.CategoryView;
63import com.android.gallery3d.filtershow.category.MainPanel;
64import com.android.gallery3d.filtershow.editors.BasicEditor;
65import com.android.gallery3d.filtershow.editors.Editor;
66import com.android.gallery3d.filtershow.editors.EditorCrop;
67import com.android.gallery3d.filtershow.editors.EditorDraw;
68import com.android.gallery3d.filtershow.editors.EditorFlip;
69import com.android.gallery3d.filtershow.editors.EditorInfo;
70import com.android.gallery3d.filtershow.editors.EditorManager;
71import com.android.gallery3d.filtershow.editors.EditorPanel;
72import com.android.gallery3d.filtershow.editors.EditorRedEye;
73import com.android.gallery3d.filtershow.editors.EditorRotate;
74import com.android.gallery3d.filtershow.editors.EditorStraighten;
75import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
76import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
77import com.android.gallery3d.filtershow.filters.FilterFxRepresentation;
78import com.android.gallery3d.filtershow.filters.FilterImageBorderRepresentation;
79import com.android.gallery3d.filtershow.filters.FilterRepresentation;
80import com.android.gallery3d.filtershow.filters.FiltersManager;
81import com.android.gallery3d.filtershow.filters.ImageFilter;
82import com.android.gallery3d.filtershow.history.HistoryManager;
83import com.android.gallery3d.filtershow.history.HistoryItem;
84import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
85import com.android.gallery3d.filtershow.imageshow.ImageCrop;
86import com.android.gallery3d.filtershow.imageshow.ImageShow;
87import com.android.gallery3d.filtershow.imageshow.MasterImage;
88import com.android.gallery3d.filtershow.pipeline.ImagePreset;
89import com.android.gallery3d.filtershow.provider.SharedImageProvider;
90import com.android.gallery3d.filtershow.state.StateAdapter;
91import com.android.gallery3d.filtershow.tools.SaveCopyTask;
92import com.android.gallery3d.filtershow.tools.XmpPresets;
93import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
94import com.android.gallery3d.filtershow.ui.FramedTextButton;
95import com.android.gallery3d.filtershow.ui.Spline;
96import com.android.gallery3d.util.GalleryUtils;
97import com.android.gallery3d.util.UsageStatistics;
98import com.android.photos.data.GalleryBitmapPool;
99
100import java.io.File;
101import java.lang.ref.WeakReference;
102import java.util.Vector;
103
104public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
105        OnShareTargetSelectedListener {
106
107    private String mAction = "";
108    MasterImage mMasterImage = null;
109
110    private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
111
112    public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
113    public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
114    private ImageLoader mImageLoader = null;
115    private ImageShow mImageShow = null;
116
117    private View mSaveButton = null;
118
119    private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
120
121    private static final int SELECT_PICTURE = 1;
122    private static final String LOGTAG = "FilterShowActivity";
123    protected static final boolean ANIMATE_PANELS = true;
124
125    private boolean mShowingTinyPlanet = false;
126    private boolean mShowingImageStatePanel = false;
127
128    private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
129
130    private ShareActionProvider mShareActionProvider;
131    private File mSharedOutputFile = null;
132
133    private boolean mSharingImage = false;
134
135    private WeakReference<ProgressDialog> mSavingProgressDialog;
136
137    private LoadBitmapTask mLoadBitmapTask;
138    private boolean mLoading = true;
139
140    private Uri mOriginalImageUri = null;
141    private ImagePreset mOriginalPreset = null;
142
143    private Uri mSelectedImageUri = null;
144
145    private CategoryAdapter mCategoryLooksAdapter = null;
146    private CategoryAdapter mCategoryBordersAdapter = null;
147    private CategoryAdapter mCategoryGeometryAdapter = null;
148    private CategoryAdapter mCategoryFiltersAdapter = null;
149    private int mCurrentPanel = MainPanel.LOOKS;
150
151    @Override
152    public void onCreate(Bundle savedInstanceState) {
153        super.onCreate(savedInstanceState);
154
155        boolean onlyUsePortrait = getResources().getBoolean(R.bool.only_use_portrait);
156        if (onlyUsePortrait) {
157            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
158        }
159        MasterImage.setMaster(mMasterImage);
160
161        clearGalleryBitmapPool();
162
163        CachingPipeline.createRenderscriptContext(this);
164        setupMasterImage();
165        setDefaultValues();
166        fillEditors();
167
168        loadXML();
169        loadMainPanel();
170
171        setDefaultPreset();
172
173        extractXMPData();
174        processIntent();
175        UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_EDITOR, "Main");
176        UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
177                UsageStatistics.CATEGORY_LIFECYCLE, UsageStatistics.LIFECYCLE_START);
178    }
179
180    public boolean isShowingImageStatePanel() {
181        return mShowingImageStatePanel;
182    }
183
184    public void loadMainPanel() {
185        if (findViewById(R.id.main_panel_container) == null) {
186            return;
187        }
188        MainPanel panel = new MainPanel();
189        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
190        transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
191        transaction.commit();
192    }
193
194    public void loadEditorPanel(FilterRepresentation representation,
195                                final Editor currentEditor) {
196        if (representation.getEditorId() == ImageOnlyEditor.ID) {
197            currentEditor.reflectCurrentFilter();
198            return;
199        }
200        final int currentId = currentEditor.getID();
201        Runnable showEditor = new Runnable() {
202            @Override
203            public void run() {
204                EditorPanel panel = new EditorPanel();
205                panel.setEditor(currentId);
206                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
207                transaction.remove(getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG));
208                transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
209                transaction.commit();
210            }
211        };
212        Fragment main = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
213        boolean doAnimation = false;
214        if (mShowingImageStatePanel
215                && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
216            doAnimation = true;
217        }
218        if (doAnimation && main != null && main instanceof MainPanel) {
219            MainPanel mainPanel = (MainPanel) main;
220            View container = mainPanel.getView().findViewById(R.id.category_panel_container);
221            View bottom = mainPanel.getView().findViewById(R.id.bottom_panel);
222            int panelHeight = container.getHeight() + bottom.getHeight();
223            ViewPropertyAnimator anim = mainPanel.getView().animate();
224            anim.translationY(panelHeight).start();
225            final Handler handler = new Handler();
226            handler.postDelayed(showEditor, anim.getDuration());
227        } else {
228            showEditor.run();
229        }
230    }
231
232    private void loadXML() {
233        setContentView(R.layout.filtershow_activity);
234
235        ActionBar actionBar = getActionBar();
236        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
237        actionBar.setCustomView(R.layout.filtershow_actionbar);
238
239        mSaveButton = actionBar.getCustomView();
240        mSaveButton.setOnClickListener(new OnClickListener() {
241            @Override
242            public void onClick(View view) {
243                saveImage();
244            }
245        });
246
247        mImageShow = (ImageShow) findViewById(R.id.imageShow);
248        mImageViews.add(mImageShow);
249
250        setupEditors();
251
252        mEditorPlaceHolder.hide();
253
254        mImageShow.setImageLoader(mImageLoader);
255
256        fillFx();
257        fillBorders();
258        fillGeometry();
259        fillFilters();
260
261        setupStatePanel();
262    }
263
264    public void setupStatePanel() {
265        mImageLoader.setHistoryManager(mMasterImage.getHistory());
266    }
267
268    private void fillFilters() {
269        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
270        FiltersManager filtersManager = FiltersManager.getManager();
271        filtersManager.addEffects(filtersRepresentations);
272
273        mCategoryFiltersAdapter = new CategoryAdapter(this);
274        for (FilterRepresentation representation : filtersRepresentations) {
275            if (representation.getTextId() != 0) {
276                representation.setName(getString(representation.getTextId()));
277            }
278            mCategoryFiltersAdapter.add(new Action(this, representation));
279        }
280    }
281
282    private void fillGeometry() {
283        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
284        FiltersManager filtersManager = FiltersManager.getManager();
285
286        GeometryMetadata geo = new GeometryMetadata();
287        int[] editorsId = geo.getEditorIds();
288        for (int i = 0; i < editorsId.length; i++) {
289            int editorId = editorsId[i];
290            GeometryMetadata geometry = new GeometryMetadata(geo);
291            geometry.setEditorId(editorId);
292            EditorInfo editorInfo = (EditorInfo) mEditorPlaceHolder.getEditor(editorId);
293            geometry.setTextId(editorInfo.getTextId());
294            geometry.setOverlayId(editorInfo.getOverlayId());
295            geometry.setOverlayOnly(editorInfo.getOverlayOnly());
296            if (geometry.getTextId() != 0) {
297                geometry.setName(getString(geometry.getTextId()));
298            }
299            filtersRepresentations.add(geometry);
300        }
301
302        filtersManager.addTools(filtersRepresentations);
303
304        mCategoryGeometryAdapter = new CategoryAdapter(this);
305        for (FilterRepresentation representation : filtersRepresentations) {
306            mCategoryGeometryAdapter.add(new Action(this, representation));
307        }
308    }
309
310    private void processIntent() {
311        Intent intent = getIntent();
312        if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
313            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
314        }
315
316        mAction = intent.getAction();
317        mSelectedImageUri = intent.getData();
318        Uri loadUri = mSelectedImageUri;
319        if (mOriginalImageUri != null) {
320            loadUri = mOriginalImageUri;
321        }
322        if (loadUri != null) {
323            startLoadBitmap(loadUri);
324        } else {
325            pickImage();
326        }
327    }
328
329    private void setupEditors() {
330        mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
331        EditorManager.addEditors(mEditorPlaceHolder);
332        mEditorPlaceHolder.setOldViews(mImageViews);
333        mEditorPlaceHolder.setImageLoader(mImageLoader);
334    }
335
336    private void fillEditors() {
337        mEditorPlaceHolder.addEditor(new EditorDraw());
338        mEditorPlaceHolder.addEditor(new BasicEditor());
339        mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
340        mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
341        mEditorPlaceHolder.addEditor(new EditorRedEye());
342        mEditorPlaceHolder.addEditor(new EditorCrop());
343        mEditorPlaceHolder.addEditor(new EditorFlip());
344        mEditorPlaceHolder.addEditor(new EditorRotate());
345        mEditorPlaceHolder.addEditor(new EditorStraighten());
346    }
347
348    private void setDefaultValues() {
349        ImageFilter.setActivityForMemoryToasts(this);
350
351        Resources res = getResources();
352        FiltersManager.setResources(res);
353
354        CategoryView.setMargin((int) getPixelsFromDip(8));
355        CategoryView.setTextSize((int) getPixelsFromDip(16));
356
357        // TODO: get those values from XML.
358        FramedTextButton.setTextSize((int) getPixelsFromDip(14));
359        FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
360        FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
361
362        Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
363        int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
364        Spline.setCurveHandle(curveHandle, curveHandleSize);
365        Spline.setCurveWidth((int) getPixelsFromDip(3));
366
367        ImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
368        ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
369        ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
370    }
371
372    private void startLoadBitmap(Uri uri) {
373        mLoading = true;
374        final View loading = findViewById(R.id.loading);
375        final View imageShow = findViewById(R.id.imageShow);
376        imageShow.setVisibility(View.INVISIBLE);
377        loading.setVisibility(View.VISIBLE);
378        mShowingTinyPlanet = false;
379        mLoadBitmapTask = new LoadBitmapTask();
380        mLoadBitmapTask.execute(uri);
381    }
382
383    private void fillBorders() {
384        Vector<FilterRepresentation> borders = new Vector<FilterRepresentation>();
385
386        // The "no border" implementation
387        borders.add(new FilterImageBorderRepresentation(0));
388
389        // Google-build borders
390        FiltersManager.getManager().addBorders(this, borders);
391
392        for (int i = 0; i < borders.size(); i++) {
393            FilterRepresentation filter = borders.elementAt(i);
394            filter.setName(getString(R.string.borders));
395            if (i == 0) {
396                filter.setName(getString(R.string.none));
397            }
398        }
399
400        mCategoryBordersAdapter = new CategoryAdapter(this);
401        for (FilterRepresentation representation : borders) {
402            if (representation.getTextId() != 0) {
403                representation.setName(getString(representation.getTextId()));
404            }
405            mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
406        }
407    }
408
409    public CategoryAdapter getCategoryLooksAdapter() {
410        return mCategoryLooksAdapter;
411    }
412
413    public CategoryAdapter getCategoryBordersAdapter() {
414        return mCategoryBordersAdapter;
415    }
416
417    public CategoryAdapter getCategoryGeometryAdapter() {
418        return mCategoryGeometryAdapter;
419    }
420
421    public CategoryAdapter getCategoryFiltersAdapter() {
422        return mCategoryFiltersAdapter;
423    }
424
425    public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
426        if (filterRepresentation == null) {
427            return;
428        }
429        ImagePreset oldPreset = MasterImage.getImage().getPreset();
430        ImagePreset copy = new ImagePreset(oldPreset);
431        copy.removeFilter(filterRepresentation);
432        MasterImage.getImage().setPreset(copy, copy.getLastRepresentation(), true);
433        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
434            FilterRepresentation lastRepresentation = copy.getLastRepresentation();
435            MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
436        }
437    }
438
439    public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
440        if (filterRepresentation == null) {
441            return;
442        }
443        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
444            return;
445        }
446        ImagePreset oldPreset = MasterImage.getImage().getPreset();
447        ImagePreset copy = new ImagePreset(oldPreset);
448        FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
449        if (representation == null) {
450            copy.addFilter(filterRepresentation);
451        } else {
452            if (filterRepresentation.allowsSingleInstanceOnly()) {
453                // Don't just update the filter representation. Centralize the
454                // logic in the addFilter(), such that we can keep "None" as
455                // null.
456                copy.removeFilter(representation);
457                copy.addFilter(filterRepresentation);
458            }
459        }
460        MasterImage.getImage().setPreset(copy, filterRepresentation, true);
461        MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
462    }
463
464    public void showRepresentation(FilterRepresentation representation) {
465        if (representation == null) {
466            return;
467        }
468
469        // TODO: this check is needed because the GeometryMetadata doesn't quite
470        // follow the same pattern as the other filters to update/sync their values.
471        // We thus need to not call useFilterRepresentation() for now, as it
472        // would override the current Geometry. Once GeometryMetadata is fixed,
473        // let's remove the check and call useFilterRepresentation all the time.
474        if (!(representation instanceof GeometryMetadata)) {
475            useFilterRepresentation(representation);
476        }
477
478        // show representation
479        Editor mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
480        loadEditorPanel(representation, mCurrentEditor);
481    }
482
483    public Editor getEditor(int editorID) {
484        return mEditorPlaceHolder.getEditor(editorID);
485    }
486
487    public void setCurrentPanel(int currentPanel) {
488        mCurrentPanel = currentPanel;
489    }
490
491    public int getCurrentPanel() {
492        return mCurrentPanel;
493    }
494
495    public void updateCategories() {
496        ImagePreset preset = mMasterImage.getPreset();
497        mCategoryLooksAdapter.reflectImagePreset(preset);
498        mCategoryBordersAdapter.reflectImagePreset(preset);
499    }
500
501    private class LoadHighresBitmapTask extends AsyncTask<Void, Void, Boolean> {
502        @Override
503        protected Boolean doInBackground(Void... params) {
504            mImageLoader.loadHighResBitmap();
505            return true;
506        }
507
508        @Override
509        protected void onPostExecute(Boolean result) {
510            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
511            if (highresBitmap != null) {
512                FilteringPipeline pipeline = FilteringPipeline.getPipeline();
513                float highResPreviewScale = (float) highresBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
514                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
515            }
516        }
517    }
518
519    private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
520        int mBitmapSize;
521
522        public LoadBitmapTask() {
523            mBitmapSize = getScreenImageSize();
524        }
525
526        @Override
527        protected Boolean doInBackground(Uri... params) {
528            if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) {
529                return false;
530            }
531            publishProgress(mImageLoader.queryLightCycle360());
532            return true;
533        }
534
535        @Override
536        protected void onProgressUpdate(Boolean... values) {
537            super.onProgressUpdate(values);
538            if (isCancelled()) {
539                return;
540            }
541            if (values[0]) {
542                mShowingTinyPlanet = true;
543            }
544        }
545
546        @Override
547        protected void onPostExecute(Boolean result) {
548            MasterImage.setMaster(mMasterImage);
549            if (isCancelled()) {
550                return;
551            }
552
553            if (!result) {
554                cannotLoadImage();
555            }
556
557            if (null == CachingPipeline.getRenderScriptContext()){
558                Log.v(LOGTAG,"RenderScript context destroyed during load");
559                return;
560            }
561            final View loading = findViewById(R.id.loading);
562            loading.setVisibility(View.GONE);
563            final View imageShow = findViewById(R.id.imageShow);
564            imageShow.setVisibility(View.VISIBLE);
565
566            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
567            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
568            pipeline.setOriginal(largeBitmap);
569            float previewScale = (float) largeBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
570            pipeline.setPreviewScaleFactor(previewScale);
571            if (!mShowingTinyPlanet) {
572                mCategoryFiltersAdapter.removeTinyPlanet();
573            }
574            pipeline.turnOnPipeline(true);
575            MasterImage.getImage().setOriginalGeometry(largeBitmap);
576            mCategoryLooksAdapter.imageLoaded();
577            mCategoryBordersAdapter.imageLoaded();
578            mCategoryGeometryAdapter.imageLoaded();
579            mCategoryFiltersAdapter.imageLoaded();
580            mLoadBitmapTask = null;
581
582            if (mOriginalPreset != null) {
583                MasterImage.getImage().setLoadedPreset(mOriginalPreset);
584                MasterImage.getImage().setPreset(mOriginalPreset,
585                        mOriginalPreset.getLastRepresentation(), true);
586                mOriginalPreset = null;
587            }
588
589            if (mAction == TINY_PLANET_ACTION) {
590                showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
591            }
592            mLoading = false;
593            MasterImage.getImage().notifyGeometryChange();
594            LoadHighresBitmapTask highresLoad = new LoadHighresBitmapTask();
595            highresLoad.execute();
596            super.onPostExecute(result);
597        }
598
599    }
600
601    private void clearGalleryBitmapPool() {
602        (new AsyncTask<Void, Void, Void>() {
603            @Override
604            protected Void doInBackground(Void... params) {
605                // Free memory held in Gallery's Bitmap pool.  May be O(n) for n bitmaps.
606                GalleryBitmapPool.getInstance().clear();
607                return null;
608            }
609        }).execute();
610    }
611
612    @Override
613    protected void onDestroy() {
614        if (mLoadBitmapTask != null) {
615            mLoadBitmapTask.cancel(false);
616        }
617        // TODO:  refactor, don't use so many singletons.
618        FilteringPipeline.getPipeline().turnOnPipeline(false);
619        MasterImage.reset();
620        FilteringPipeline.reset();
621        ImageFilter.resetStatics();
622        FiltersManager.getPreviewManager().freeRSFilterScripts();
623        FiltersManager.getManager().freeRSFilterScripts();
624        FiltersManager.getHighresManager().freeRSFilterScripts();
625        FiltersManager.reset();
626        CachingPipeline.destroyRenderScriptContext();
627        super.onDestroy();
628    }
629
630    private int getScreenImageSize() {
631        DisplayMetrics metrics = new DisplayMetrics();
632        Display display = getWindowManager().getDefaultDisplay();
633        Point size = new Point();
634        display.getSize(size);
635        display.getMetrics(metrics);
636        int msize = Math.min(size.x, size.y);
637        return (133 * msize) / metrics.densityDpi;
638    }
639
640    private void showSavingProgress(String albumName) {
641        ProgressDialog progress;
642        if (mSavingProgressDialog != null) {
643            progress = mSavingProgressDialog.get();
644            if (progress != null) {
645                progress.show();
646                return;
647            }
648        }
649        // TODO: Allow cancellation of the saving process
650        String progressText;
651        if (albumName == null) {
652            progressText = getString(R.string.saving_image);
653        } else {
654            progressText = getString(R.string.filtershow_saving_image, albumName);
655        }
656        progress = ProgressDialog.show(this, "", progressText, true, false);
657        mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
658    }
659
660    private void hideSavingProgress() {
661        if (mSavingProgressDialog != null) {
662            ProgressDialog progress = mSavingProgressDialog.get();
663            if (progress != null)
664                progress.dismiss();
665        }
666    }
667
668    public void completeSaveImage(Uri saveUri) {
669        if (mSharingImage && mSharedOutputFile != null) {
670            // Image saved, we unblock the content provider
671            Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
672                    Uri.encode(mSharedOutputFile.getAbsolutePath()));
673            ContentValues values = new ContentValues();
674            values.put(SharedImageProvider.PREPARE, false);
675            getContentResolver().insert(uri, values);
676        }
677        setResult(RESULT_OK, new Intent().setData(saveUri));
678        hideSavingProgress();
679        finish();
680    }
681
682    @Override
683    public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
684        // First, let's tell the SharedImageProvider that it will need to wait
685        // for the image
686        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
687                Uri.encode(mSharedOutputFile.getAbsolutePath()));
688        ContentValues values = new ContentValues();
689        values.put(SharedImageProvider.PREPARE, true);
690        getContentResolver().insert(uri, values);
691        mSharingImage = true;
692
693        // Process and save the image in the background.
694        showSavingProgress(null);
695        mImageShow.saveImage(this, mSharedOutputFile);
696        return true;
697    }
698
699    private Intent getDefaultShareIntent() {
700        Intent intent = new Intent(Intent.ACTION_SEND);
701        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
702        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
703        intent.setType(SharedImageProvider.MIME_TYPE);
704        mSharedOutputFile = SaveCopyTask.getNewFile(this, mImageLoader.getUri());
705        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
706                Uri.encode(mSharedOutputFile.getAbsolutePath()));
707        intent.putExtra(Intent.EXTRA_STREAM, uri);
708        return intent;
709    }
710
711    @Override
712    public boolean onCreateOptionsMenu(Menu menu) {
713        getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
714        MenuItem showState = menu.findItem(R.id.showImageStateButton);
715        if (mShowingImageStatePanel) {
716            showState.setTitle(R.string.hide_imagestate_panel);
717        } else {
718            showState.setTitle(R.string.show_imagestate_panel);
719        }
720        mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
721                .getActionProvider();
722        mShareActionProvider.setShareIntent(getDefaultShareIntent());
723        mShareActionProvider.setOnShareTargetSelectedListener(this);
724
725        MenuItem undoItem = menu.findItem(R.id.undoButton);
726        MenuItem redoItem = menu.findItem(R.id.redoButton);
727        MenuItem resetItem = menu.findItem(R.id.resetHistoryButton);
728        mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
729        return true;
730    }
731
732    @Override
733    public void onPause() {
734        super.onPause();
735        rsPause();
736        if (mShareActionProvider != null) {
737            mShareActionProvider.setOnShareTargetSelectedListener(null);
738        }
739    }
740
741    @Override
742    public void onResume() {
743        super.onResume();
744        rsResume();
745        if (mShareActionProvider != null) {
746            mShareActionProvider.setOnShareTargetSelectedListener(this);
747        }
748    }
749
750    private void rsResume() {
751        ImageFilter.setActivityForMemoryToasts(this);
752        MasterImage.setMaster(mMasterImage);
753        if (CachingPipeline.getRenderScriptContext() == null) {
754            CachingPipeline.createRenderscriptContext(this);
755        }
756        FiltersManager.setResources(getResources());
757        if (!mLoading) {
758            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
759            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
760            pipeline.setOriginal(largeBitmap);
761            float previewScale = (float) largeBitmap.getWidth() /
762                    (float) mImageLoader.getOriginalBounds().width();
763            pipeline.setPreviewScaleFactor(previewScale);
764            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
765            if (highresBitmap != null) {
766                float highResPreviewScale = (float) highresBitmap.getWidth() /
767                        (float) mImageLoader.getOriginalBounds().width();
768                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
769            }
770            pipeline.turnOnPipeline(true);
771            MasterImage.getImage().setOriginalGeometry(largeBitmap);
772        }
773    }
774
775    private void rsPause() {
776        FilteringPipeline.getPipeline().turnOnPipeline(false);
777        FilteringPipeline.reset();
778        ImageFilter.resetStatics();
779        FiltersManager.getPreviewManager().freeRSFilterScripts();
780        FiltersManager.getManager().freeRSFilterScripts();
781        FiltersManager.getHighresManager().freeRSFilterScripts();
782        FiltersManager.reset();
783        CachingPipeline.destroyRenderScriptContext();
784    }
785
786    @Override
787    public boolean onOptionsItemSelected(MenuItem item) {
788        switch (item.getItemId()) {
789            case R.id.undoButton: {
790                HistoryManager adapter = mMasterImage.getHistory();
791                int position = adapter.undo();
792                mMasterImage.onHistoryItemClick(position);
793                backToMain();
794                invalidateViews();
795                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
796                        UsageStatistics.CATEGORY_BUTTON_PRESS, "Undo");
797                return true;
798            }
799            case R.id.redoButton: {
800                HistoryManager adapter = mMasterImage.getHistory();
801                int position = adapter.redo();
802                mMasterImage.onHistoryItemClick(position);
803                invalidateViews();
804                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
805                        UsageStatistics.CATEGORY_BUTTON_PRESS, "Redo");
806                return true;
807            }
808            case R.id.resetHistoryButton: {
809                resetHistory();
810                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
811                        UsageStatistics.CATEGORY_BUTTON_PRESS, "ResetHistory");
812                return true;
813            }
814            case R.id.showImageStateButton: {
815                toggleImageStatePanel();
816                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
817                        UsageStatistics.CATEGORY_BUTTON_PRESS,
818                        mShowingImageStatePanel ? "ShowPanel" : "HidePanel");
819                return true;
820            }
821            case android.R.id.home: {
822                saveImage();
823                return true;
824            }
825        }
826        return false;
827    }
828
829    public void enableSave(boolean enable) {
830        if (mSaveButton != null) {
831            mSaveButton.setEnabled(enable);
832        }
833    }
834
835    private void fillFx() {
836        FilterFxRepresentation nullFx =
837                new FilterFxRepresentation(getString(R.string.none), 0, R.string.none);
838        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
839        FiltersManager.getManager().addLooks(this, filtersRepresentations);
840
841        mCategoryLooksAdapter = new CategoryAdapter(this);
842        int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
843        mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
844        mCategoryLooksAdapter.add(new Action(this, nullFx, Action.FULL_VIEW));
845        for (FilterRepresentation representation : filtersRepresentations) {
846            mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
847        }
848    }
849
850    public void setDefaultPreset() {
851        // Default preset (original)
852        ImagePreset preset = new ImagePreset(); // empty
853        preset.setImageLoader(mImageLoader);
854
855        mMasterImage.setPreset(preset, preset.getLastRepresentation(), true);
856    }
857
858    // //////////////////////////////////////////////////////////////////////////////
859    // Some utility functions
860    // TODO: finish the cleanup.
861
862    public void invalidateViews() {
863        for (ImageShow views : mImageViews) {
864            views.invalidate();
865            views.updateImage();
866        }
867    }
868
869    public void hideImageViews() {
870        for (View view : mImageViews) {
871            view.setVisibility(View.GONE);
872        }
873        mEditorPlaceHolder.hide();
874    }
875
876    // //////////////////////////////////////////////////////////////////////////////
877    // imageState panel...
878
879    public void toggleImageStatePanel() {
880        invalidateOptionsMenu();
881        mShowingImageStatePanel = !mShowingImageStatePanel;
882        Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
883        if (panel != null) {
884            if (panel instanceof EditorPanel) {
885                EditorPanel editorPanel = (EditorPanel) panel;
886                editorPanel.showImageStatePanel(mShowingImageStatePanel);
887            } else if (panel instanceof MainPanel) {
888                MainPanel mainPanel = (MainPanel) panel;
889                mainPanel.showImageStatePanel(mShowingImageStatePanel);
890            }
891        }
892    }
893
894    @Override
895    public void onConfigurationChanged(Configuration newConfig)
896    {
897        super.onConfigurationChanged(newConfig);
898        setDefaultValues();
899        loadXML();
900        loadMainPanel();
901
902        // mLoadBitmapTask==null implies you have looked at the intent
903        if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
904            mCategoryFiltersAdapter.removeTinyPlanet();
905        }
906        final View loading = findViewById(R.id.loading);
907        loading.setVisibility(View.GONE);
908    }
909
910    public void setupMasterImage() {
911        mImageLoader = new ImageLoader(this, getApplicationContext());
912
913        HistoryManager mHistoryManager = new HistoryManager();
914        StateAdapter mImageStateAdapter = new StateAdapter(this, 0);
915        MasterImage.reset();
916        mMasterImage = MasterImage.getImage();
917        mMasterImage.setHistoryManager(mHistoryManager);
918        mMasterImage.setStateAdapter(mImageStateAdapter);
919        mMasterImage.setActivity(this);
920        mMasterImage.setImageLoader(mImageLoader);
921
922        if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
923            mMasterImage.setSupportsHighRes(true);
924        } else {
925            mMasterImage.setSupportsHighRes(false);
926        }
927    }
928
929    void resetHistory() {
930        HistoryManager adapter = mMasterImage.getHistory();
931        adapter.reset();
932        HistoryItem historyItem = adapter.getItem(0);
933        ImagePreset original = new ImagePreset(historyItem.getImagePreset());
934        mMasterImage.setPreset(original, historyItem.getFilterRepresentation(), true);
935        invalidateViews();
936        backToMain();
937    }
938
939    public void showDefaultImageView() {
940        mEditorPlaceHolder.hide();
941        mImageShow.setVisibility(View.VISIBLE);
942        MasterImage.getImage().setCurrentFilter(null);
943        MasterImage.getImage().setCurrentFilterRepresentation(null);
944    }
945
946    public void backToMain() {
947        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
948        if (currentPanel instanceof MainPanel) {
949            return;
950        }
951        loadMainPanel();
952        showDefaultImageView();
953    }
954
955    @Override
956    public void onBackPressed() {
957        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
958        if (currentPanel instanceof MainPanel) {
959            if (!mImageShow.hasModifications()) {
960                done();
961            } else {
962                AlertDialog.Builder builder = new AlertDialog.Builder(this);
963                builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
964                builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
965                    @Override
966                    public void onClick(DialogInterface dialog, int id) {
967                        saveImage();
968                    }
969                });
970                builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
971                    @Override
972                    public void onClick(DialogInterface dialog, int id) {
973                        done();
974                    }
975                });
976                builder.show();
977            }
978        } else {
979            backToMain();
980        }
981    }
982
983    public void cannotLoadImage() {
984        Toast.makeText(this, R.string.cannot_load_image, Toast.LENGTH_SHORT).show();
985        finish();
986    }
987
988    // //////////////////////////////////////////////////////////////////////////////
989
990    public float getPixelsFromDip(float value) {
991        Resources r = getResources();
992        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
993                r.getDisplayMetrics());
994    }
995
996    @Override
997    public void onItemClick(AdapterView<?> parent, View view, int position,
998            long id) {
999        mMasterImage.onHistoryItemClick(position);
1000        invalidateViews();
1001    }
1002
1003    public void pickImage() {
1004        Intent intent = new Intent();
1005        intent.setType("image/*");
1006        intent.setAction(Intent.ACTION_GET_CONTENT);
1007        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
1008                SELECT_PICTURE);
1009    }
1010
1011    @Override
1012    public void onActivityResult(int requestCode, int resultCode, Intent data) {
1013        if (resultCode == RESULT_OK) {
1014            if (requestCode == SELECT_PICTURE) {
1015                Uri selectedImageUri = data.getData();
1016                startLoadBitmap(selectedImageUri);
1017            }
1018        }
1019    }
1020
1021
1022    public void saveImage() {
1023        if (mImageShow.hasModifications()) {
1024            // Get the name of the album, to which the image will be saved
1025            File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mSelectedImageUri);
1026            int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
1027            String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
1028            showSavingProgress(albumName);
1029            mImageShow.saveImage(this, null);
1030        } else {
1031            done();
1032        }
1033    }
1034
1035
1036    public void done() {
1037        hideSavingProgress();
1038        if (mLoadBitmapTask != null) {
1039            mLoadBitmapTask.cancel(false);
1040        }
1041        finish();
1042    }
1043
1044    private void extractXMPData() {
1045        XMresults res = XmpPresets.extractXMPData(
1046                getBaseContext(), mMasterImage, getIntent().getData());
1047        if (res == null)
1048            return;
1049
1050        mOriginalImageUri = res.originalimage;
1051        mOriginalPreset = res.preset;
1052    }
1053
1054    public Uri getSelectedImageUri() {
1055        return mSelectedImageUri;
1056    }
1057
1058    static {
1059        System.loadLibrary("jni_filtershow_filters");
1060    }
1061
1062
1063}
1064