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