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