FilterShowActivity.java revision 2332f652d86e98887996505002d34b4f7940c34a
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        // TODO: get those values from XML.
355        FramedTextButton.setTextSize((int) getPixelsFromDip(14));
356        FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
357        FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
358
359        Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
360        int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
361        Spline.setCurveHandle(curveHandle, curveHandleSize);
362        Spline.setCurveWidth((int) getPixelsFromDip(3));
363
364        ImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
365        ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
366        ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
367    }
368
369    private void startLoadBitmap(Uri uri) {
370        mLoading = true;
371        final View loading = findViewById(R.id.loading);
372        final View imageShow = findViewById(R.id.imageShow);
373        imageShow.setVisibility(View.INVISIBLE);
374        loading.setVisibility(View.VISIBLE);
375        mShowingTinyPlanet = false;
376        mLoadBitmapTask = new LoadBitmapTask();
377        mLoadBitmapTask.execute(uri);
378    }
379
380    private void fillBorders() {
381        Vector<FilterRepresentation> borders = new Vector<FilterRepresentation>();
382
383        // The "no border" implementation
384        borders.add(new FilterImageBorderRepresentation(0));
385
386        // Google-build borders
387        FiltersManager.getManager().addBorders(this, borders);
388
389        for (int i = 0; i < borders.size(); i++) {
390            FilterRepresentation filter = borders.elementAt(i);
391            filter.setName(getString(R.string.borders));
392            if (i == 0) {
393                filter.setName(getString(R.string.none));
394            }
395        }
396
397        mCategoryBordersAdapter = new CategoryAdapter(this);
398        for (FilterRepresentation representation : borders) {
399            if (representation.getTextId() != 0) {
400                representation.setName(getString(representation.getTextId()));
401            }
402            mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
403        }
404    }
405
406    public CategoryAdapter getCategoryLooksAdapter() {
407        return mCategoryLooksAdapter;
408    }
409
410    public CategoryAdapter getCategoryBordersAdapter() {
411        return mCategoryBordersAdapter;
412    }
413
414    public CategoryAdapter getCategoryGeometryAdapter() {
415        return mCategoryGeometryAdapter;
416    }
417
418    public CategoryAdapter getCategoryFiltersAdapter() {
419        return mCategoryFiltersAdapter;
420    }
421
422    public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
423        if (filterRepresentation == null) {
424            return;
425        }
426        ImagePreset oldPreset = MasterImage.getImage().getPreset();
427        ImagePreset copy = new ImagePreset(oldPreset);
428        copy.removeFilter(filterRepresentation);
429        MasterImage.getImage().setPreset(copy, copy.getLastRepresentation(), true);
430        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
431            FilterRepresentation lastRepresentation = copy.getLastRepresentation();
432            MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
433        }
434    }
435
436    public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
437        if (filterRepresentation == null) {
438            return;
439        }
440        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
441            return;
442        }
443        ImagePreset oldPreset = MasterImage.getImage().getPreset();
444        ImagePreset copy = new ImagePreset(oldPreset);
445        FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
446        if (representation == null) {
447            copy.addFilter(filterRepresentation);
448        } else {
449            if (filterRepresentation.allowsSingleInstanceOnly()) {
450                // Don't just update the filter representation. Centralize the
451                // logic in the addFilter(), such that we can keep "None" as
452                // null.
453                copy.removeFilter(representation);
454                copy.addFilter(filterRepresentation);
455            }
456        }
457        MasterImage.getImage().setPreset(copy, filterRepresentation, true);
458        MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
459    }
460
461    public void showRepresentation(FilterRepresentation representation) {
462        if (representation == null) {
463            return;
464        }
465
466        // TODO: this check is needed because the GeometryMetadata doesn't quite
467        // follow the same pattern as the other filters to update/sync their values.
468        // We thus need to not call useFilterRepresentation() for now, as it
469        // would override the current Geometry. Once GeometryMetadata is fixed,
470        // let's remove the check and call useFilterRepresentation all the time.
471        if (!(representation instanceof GeometryMetadata)) {
472            useFilterRepresentation(representation);
473        }
474
475        // show representation
476        Editor mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
477        loadEditorPanel(representation, mCurrentEditor);
478    }
479
480    public Editor getEditor(int editorID) {
481        return mEditorPlaceHolder.getEditor(editorID);
482    }
483
484    public void setCurrentPanel(int currentPanel) {
485        mCurrentPanel = currentPanel;
486    }
487
488    public int getCurrentPanel() {
489        return mCurrentPanel;
490    }
491
492    public void updateCategories() {
493        ImagePreset preset = mMasterImage.getPreset();
494        mCategoryLooksAdapter.reflectImagePreset(preset);
495        mCategoryBordersAdapter.reflectImagePreset(preset);
496    }
497
498    private class LoadHighresBitmapTask extends AsyncTask<Void, Void, Boolean> {
499        @Override
500        protected Boolean doInBackground(Void... params) {
501            mImageLoader.loadHighResBitmap();
502            return true;
503        }
504
505        @Override
506        protected void onPostExecute(Boolean result) {
507            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
508            if (highresBitmap != null) {
509                FilteringPipeline pipeline = FilteringPipeline.getPipeline();
510                float highResPreviewScale = (float) highresBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
511                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
512            }
513        }
514    }
515
516    private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
517        int mBitmapSize;
518
519        public LoadBitmapTask() {
520            mBitmapSize = getScreenImageSize();
521        }
522
523        @Override
524        protected Boolean doInBackground(Uri... params) {
525            if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) {
526                return false;
527            }
528            publishProgress(mImageLoader.queryLightCycle360());
529            return true;
530        }
531
532        @Override
533        protected void onProgressUpdate(Boolean... values) {
534            super.onProgressUpdate(values);
535            if (isCancelled()) {
536                return;
537            }
538            if (values[0]) {
539                mShowingTinyPlanet = true;
540            }
541        }
542
543        @Override
544        protected void onPostExecute(Boolean result) {
545            MasterImage.setMaster(mMasterImage);
546            if (isCancelled()) {
547                return;
548            }
549
550            if (!result) {
551                cannotLoadImage();
552            }
553
554            if (null == CachingPipeline.getRenderScriptContext()){
555                Log.v(LOGTAG,"RenderScript context destroyed during load");
556                return;
557            }
558            final View loading = findViewById(R.id.loading);
559            loading.setVisibility(View.GONE);
560            final View imageShow = findViewById(R.id.imageShow);
561            imageShow.setVisibility(View.VISIBLE);
562
563            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
564            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
565            pipeline.setOriginal(largeBitmap);
566            float previewScale = (float) largeBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
567            pipeline.setPreviewScaleFactor(previewScale);
568            if (!mShowingTinyPlanet) {
569                mCategoryFiltersAdapter.removeTinyPlanet();
570            }
571            pipeline.turnOnPipeline(true);
572            MasterImage.getImage().setOriginalGeometry(largeBitmap);
573            mCategoryLooksAdapter.imageLoaded();
574            mCategoryBordersAdapter.imageLoaded();
575            mCategoryGeometryAdapter.imageLoaded();
576            mCategoryFiltersAdapter.imageLoaded();
577            mLoadBitmapTask = null;
578
579            if (mOriginalPreset != null) {
580                MasterImage.getImage().setLoadedPreset(mOriginalPreset);
581                MasterImage.getImage().setPreset(mOriginalPreset,
582                        mOriginalPreset.getLastRepresentation(), true);
583                mOriginalPreset = null;
584            }
585
586            if (mAction == TINY_PLANET_ACTION) {
587                showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
588            }
589            mLoading = false;
590            MasterImage.getImage().notifyGeometryChange();
591            LoadHighresBitmapTask highresLoad = new LoadHighresBitmapTask();
592            highresLoad.execute();
593            super.onPostExecute(result);
594        }
595
596    }
597
598    private void clearGalleryBitmapPool() {
599        (new AsyncTask<Void, Void, Void>() {
600            @Override
601            protected Void doInBackground(Void... params) {
602                // Free memory held in Gallery's Bitmap pool.  May be O(n) for n bitmaps.
603                GalleryBitmapPool.getInstance().clear();
604                return null;
605            }
606        }).execute();
607    }
608
609    @Override
610    protected void onDestroy() {
611        if (mLoadBitmapTask != null) {
612            mLoadBitmapTask.cancel(false);
613        }
614        // TODO:  refactor, don't use so many singletons.
615        FilteringPipeline.getPipeline().turnOnPipeline(false);
616        MasterImage.reset();
617        FilteringPipeline.reset();
618        ImageFilter.resetStatics();
619        FiltersManager.getPreviewManager().freeRSFilterScripts();
620        FiltersManager.getManager().freeRSFilterScripts();
621        FiltersManager.getHighresManager().freeRSFilterScripts();
622        FiltersManager.reset();
623        CachingPipeline.destroyRenderScriptContext();
624        super.onDestroy();
625    }
626
627    private int getScreenImageSize() {
628        DisplayMetrics metrics = new DisplayMetrics();
629        Display display = getWindowManager().getDefaultDisplay();
630        Point size = new Point();
631        display.getSize(size);
632        display.getMetrics(metrics);
633        int msize = Math.min(size.x, size.y);
634        return (133 * msize) / metrics.densityDpi;
635    }
636
637    private void showSavingProgress(String albumName) {
638        ProgressDialog progress;
639        if (mSavingProgressDialog != null) {
640            progress = mSavingProgressDialog.get();
641            if (progress != null) {
642                progress.show();
643                return;
644            }
645        }
646        // TODO: Allow cancellation of the saving process
647        String progressText;
648        if (albumName == null) {
649            progressText = getString(R.string.saving_image);
650        } else {
651            progressText = getString(R.string.filtershow_saving_image, albumName);
652        }
653        progress = ProgressDialog.show(this, "", progressText, true, false);
654        mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
655    }
656
657    private void hideSavingProgress() {
658        if (mSavingProgressDialog != null) {
659            ProgressDialog progress = mSavingProgressDialog.get();
660            if (progress != null)
661                progress.dismiss();
662        }
663    }
664
665    public void completeSaveImage(Uri saveUri) {
666        if (mSharingImage && mSharedOutputFile != null) {
667            // Image saved, we unblock the content provider
668            Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
669                    Uri.encode(mSharedOutputFile.getAbsolutePath()));
670            ContentValues values = new ContentValues();
671            values.put(SharedImageProvider.PREPARE, false);
672            getContentResolver().insert(uri, values);
673        }
674        setResult(RESULT_OK, new Intent().setData(saveUri));
675        hideSavingProgress();
676        finish();
677    }
678
679    @Override
680    public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
681        // First, let's tell the SharedImageProvider that it will need to wait
682        // for the image
683        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
684                Uri.encode(mSharedOutputFile.getAbsolutePath()));
685        ContentValues values = new ContentValues();
686        values.put(SharedImageProvider.PREPARE, true);
687        getContentResolver().insert(uri, values);
688        mSharingImage = true;
689
690        // Process and save the image in the background.
691        showSavingProgress(null);
692        mImageShow.saveImage(this, mSharedOutputFile);
693        return true;
694    }
695
696    private Intent getDefaultShareIntent() {
697        Intent intent = new Intent(Intent.ACTION_SEND);
698        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
699        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
700        intent.setType(SharedImageProvider.MIME_TYPE);
701        mSharedOutputFile = SaveCopyTask.getNewFile(this, mImageLoader.getUri());
702        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
703                Uri.encode(mSharedOutputFile.getAbsolutePath()));
704        intent.putExtra(Intent.EXTRA_STREAM, uri);
705        return intent;
706    }
707
708    @Override
709    public boolean onCreateOptionsMenu(Menu menu) {
710        getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
711        MenuItem showState = menu.findItem(R.id.showImageStateButton);
712        if (mShowingImageStatePanel) {
713            showState.setTitle(R.string.hide_imagestate_panel);
714        } else {
715            showState.setTitle(R.string.show_imagestate_panel);
716        }
717        mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
718                .getActionProvider();
719        mShareActionProvider.setShareIntent(getDefaultShareIntent());
720        mShareActionProvider.setOnShareTargetSelectedListener(this);
721
722        MenuItem undoItem = menu.findItem(R.id.undoButton);
723        MenuItem redoItem = menu.findItem(R.id.redoButton);
724        MenuItem resetItem = menu.findItem(R.id.resetHistoryButton);
725        mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
726        return true;
727    }
728
729    @Override
730    public void onPause() {
731        super.onPause();
732        rsPause();
733        if (mShareActionProvider != null) {
734            mShareActionProvider.setOnShareTargetSelectedListener(null);
735        }
736    }
737
738    @Override
739    public void onResume() {
740        super.onResume();
741        rsResume();
742        if (mShareActionProvider != null) {
743            mShareActionProvider.setOnShareTargetSelectedListener(this);
744        }
745    }
746
747    private void rsResume() {
748        ImageFilter.setActivityForMemoryToasts(this);
749        MasterImage.setMaster(mMasterImage);
750        if (CachingPipeline.getRenderScriptContext() == null) {
751            CachingPipeline.createRenderscriptContext(this);
752        }
753        FiltersManager.setResources(getResources());
754        if (!mLoading) {
755            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
756            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
757            pipeline.setOriginal(largeBitmap);
758            float previewScale = (float) largeBitmap.getWidth() /
759                    (float) mImageLoader.getOriginalBounds().width();
760            pipeline.setPreviewScaleFactor(previewScale);
761            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
762            if (highresBitmap != null) {
763                float highResPreviewScale = (float) highresBitmap.getWidth() /
764                        (float) mImageLoader.getOriginalBounds().width();
765                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
766            }
767            pipeline.turnOnPipeline(true);
768            MasterImage.getImage().setOriginalGeometry(largeBitmap);
769        }
770    }
771
772    private void rsPause() {
773        FilteringPipeline.getPipeline().turnOnPipeline(false);
774        FilteringPipeline.reset();
775        ImageFilter.resetStatics();
776        FiltersManager.getPreviewManager().freeRSFilterScripts();
777        FiltersManager.getManager().freeRSFilterScripts();
778        FiltersManager.getHighresManager().freeRSFilterScripts();
779        FiltersManager.reset();
780        CachingPipeline.destroyRenderScriptContext();
781    }
782
783    @Override
784    public boolean onOptionsItemSelected(MenuItem item) {
785        switch (item.getItemId()) {
786            case R.id.undoButton: {
787                HistoryManager adapter = mMasterImage.getHistory();
788                int position = adapter.undo();
789                mMasterImage.onHistoryItemClick(position);
790                backToMain();
791                invalidateViews();
792                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
793                        UsageStatistics.CATEGORY_BUTTON_PRESS, "Undo");
794                return true;
795            }
796            case R.id.redoButton: {
797                HistoryManager adapter = mMasterImage.getHistory();
798                int position = adapter.redo();
799                mMasterImage.onHistoryItemClick(position);
800                invalidateViews();
801                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
802                        UsageStatistics.CATEGORY_BUTTON_PRESS, "Redo");
803                return true;
804            }
805            case R.id.resetHistoryButton: {
806                resetHistory();
807                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
808                        UsageStatistics.CATEGORY_BUTTON_PRESS, "ResetHistory");
809                return true;
810            }
811            case R.id.showImageStateButton: {
812                toggleImageStatePanel();
813                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
814                        UsageStatistics.CATEGORY_BUTTON_PRESS,
815                        mShowingImageStatePanel ? "ShowPanel" : "HidePanel");
816                return true;
817            }
818            case android.R.id.home: {
819                saveImage();
820                return true;
821            }
822        }
823        return false;
824    }
825
826    public void enableSave(boolean enable) {
827        if (mSaveButton != null) {
828            mSaveButton.setEnabled(enable);
829        }
830    }
831
832    private void fillFx() {
833        FilterFxRepresentation nullFx =
834                new FilterFxRepresentation(getString(R.string.none), 0, R.string.none);
835        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
836        FiltersManager.getManager().addLooks(this, filtersRepresentations);
837
838        mCategoryLooksAdapter = new CategoryAdapter(this);
839        int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
840        mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
841        mCategoryLooksAdapter.add(new Action(this, nullFx, Action.FULL_VIEW));
842        for (FilterRepresentation representation : filtersRepresentations) {
843            mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
844        }
845    }
846
847    public void setDefaultPreset() {
848        // Default preset (original)
849        ImagePreset preset = new ImagePreset(); // empty
850        preset.setImageLoader(mImageLoader);
851
852        mMasterImage.setPreset(preset, preset.getLastRepresentation(), true);
853    }
854
855    // //////////////////////////////////////////////////////////////////////////////
856    // Some utility functions
857    // TODO: finish the cleanup.
858
859    public void invalidateViews() {
860        for (ImageShow views : mImageViews) {
861            views.invalidate();
862            views.updateImage();
863        }
864    }
865
866    public void hideImageViews() {
867        for (View view : mImageViews) {
868            view.setVisibility(View.GONE);
869        }
870        mEditorPlaceHolder.hide();
871    }
872
873    // //////////////////////////////////////////////////////////////////////////////
874    // imageState panel...
875
876    public void toggleImageStatePanel() {
877        invalidateOptionsMenu();
878        mShowingImageStatePanel = !mShowingImageStatePanel;
879        Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
880        if (panel != null) {
881            if (panel instanceof EditorPanel) {
882                EditorPanel editorPanel = (EditorPanel) panel;
883                editorPanel.showImageStatePanel(mShowingImageStatePanel);
884            } else if (panel instanceof MainPanel) {
885                MainPanel mainPanel = (MainPanel) panel;
886                mainPanel.showImageStatePanel(mShowingImageStatePanel);
887            }
888        }
889    }
890
891    @Override
892    public void onConfigurationChanged(Configuration newConfig)
893    {
894        super.onConfigurationChanged(newConfig);
895        setDefaultValues();
896        loadXML();
897        loadMainPanel();
898
899        // mLoadBitmapTask==null implies you have looked at the intent
900        if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
901            mCategoryFiltersAdapter.removeTinyPlanet();
902        }
903        final View loading = findViewById(R.id.loading);
904        loading.setVisibility(View.GONE);
905    }
906
907    public void setupMasterImage() {
908        mImageLoader = new ImageLoader(this, getApplicationContext());
909
910        HistoryManager mHistoryManager = new HistoryManager();
911        StateAdapter mImageStateAdapter = new StateAdapter(this, 0);
912        MasterImage.reset();
913        mMasterImage = MasterImage.getImage();
914        mMasterImage.setHistoryManager(mHistoryManager);
915        mMasterImage.setStateAdapter(mImageStateAdapter);
916        mMasterImage.setActivity(this);
917        mMasterImage.setImageLoader(mImageLoader);
918
919        if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
920            mMasterImage.setSupportsHighRes(true);
921        } else {
922            mMasterImage.setSupportsHighRes(false);
923        }
924    }
925
926    void resetHistory() {
927        HistoryManager adapter = mMasterImage.getHistory();
928        adapter.reset();
929        HistoryItem historyItem = adapter.getItem(0);
930        ImagePreset original = new ImagePreset(historyItem.getImagePreset());
931        mMasterImage.setPreset(original, historyItem.getFilterRepresentation(), true);
932        invalidateViews();
933        backToMain();
934    }
935
936    public void showDefaultImageView() {
937        mEditorPlaceHolder.hide();
938        mImageShow.setVisibility(View.VISIBLE);
939        MasterImage.getImage().setCurrentFilter(null);
940        MasterImage.getImage().setCurrentFilterRepresentation(null);
941    }
942
943    public void backToMain() {
944        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
945        if (currentPanel instanceof MainPanel) {
946            return;
947        }
948        loadMainPanel();
949        showDefaultImageView();
950    }
951
952    @Override
953    public void onBackPressed() {
954        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
955        if (currentPanel instanceof MainPanel) {
956            if (!mImageShow.hasModifications()) {
957                done();
958            } else {
959                AlertDialog.Builder builder = new AlertDialog.Builder(this);
960                builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
961                builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
962                    @Override
963                    public void onClick(DialogInterface dialog, int id) {
964                        saveImage();
965                    }
966                });
967                builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
968                    @Override
969                    public void onClick(DialogInterface dialog, int id) {
970                        done();
971                    }
972                });
973                builder.show();
974            }
975        } else {
976            backToMain();
977        }
978    }
979
980    public void cannotLoadImage() {
981        Toast.makeText(this, R.string.cannot_load_image, Toast.LENGTH_SHORT).show();
982        finish();
983    }
984
985    // //////////////////////////////////////////////////////////////////////////////
986
987    public float getPixelsFromDip(float value) {
988        Resources r = getResources();
989        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
990                r.getDisplayMetrics());
991    }
992
993    @Override
994    public void onItemClick(AdapterView<?> parent, View view, int position,
995            long id) {
996        mMasterImage.onHistoryItemClick(position);
997        invalidateViews();
998    }
999
1000    public void pickImage() {
1001        Intent intent = new Intent();
1002        intent.setType("image/*");
1003        intent.setAction(Intent.ACTION_GET_CONTENT);
1004        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
1005                SELECT_PICTURE);
1006    }
1007
1008    @Override
1009    public void onActivityResult(int requestCode, int resultCode, Intent data) {
1010        if (resultCode == RESULT_OK) {
1011            if (requestCode == SELECT_PICTURE) {
1012                Uri selectedImageUri = data.getData();
1013                startLoadBitmap(selectedImageUri);
1014            }
1015        }
1016    }
1017
1018
1019    public void saveImage() {
1020        if (mImageShow.hasModifications()) {
1021            // Get the name of the album, to which the image will be saved
1022            File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mSelectedImageUri);
1023            int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
1024            String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
1025            showSavingProgress(albumName);
1026            mImageShow.saveImage(this, null);
1027        } else {
1028            done();
1029        }
1030    }
1031
1032
1033    public void done() {
1034        hideSavingProgress();
1035        if (mLoadBitmapTask != null) {
1036            mLoadBitmapTask.cancel(false);
1037        }
1038        finish();
1039    }
1040
1041    private void extractXMPData() {
1042        XMresults res = XmpPresets.extractXMPData(
1043                getBaseContext(), mMasterImage, getIntent().getData());
1044        if (res == null)
1045            return;
1046
1047        mOriginalImageUri = res.originalimage;
1048        mOriginalPreset = res.preset;
1049    }
1050
1051    public Uri getSelectedImageUri() {
1052        return mSelectedImageUri;
1053    }
1054
1055    static {
1056        System.loadLibrary("jni_filtershow_filters");
1057    }
1058
1059
1060}
1061