FilterShowActivity.java revision 2d9330020b929cc75bbdfd645d3dc9278ca78779
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.Color;
33import android.graphics.Matrix;
34import android.graphics.Point;
35import android.graphics.Rect;
36import android.graphics.RectF;
37import android.graphics.drawable.ColorDrawable;
38import android.graphics.drawable.Drawable;
39import android.net.Uri;
40import android.os.AsyncTask;
41import android.os.Bundle;
42import android.os.CancellationSignal;
43import android.os.Handler;
44import android.os.IBinder;
45import android.support.v4.app.DialogFragment;
46import android.support.v4.app.Fragment;
47import android.support.v4.app.FragmentActivity;
48import android.support.v4.app.FragmentTransaction;
49import android.util.DisplayMetrics;
50import android.util.Log;
51import android.util.TypedValue;
52import android.view.Menu;
53import android.view.MenuItem;
54import android.view.MotionEvent;
55import android.view.View;
56import android.view.View.OnClickListener;
57import android.view.ViewPropertyAnimator;
58import android.view.WindowManager;
59import android.widget.AdapterView;
60import android.widget.AdapterView.OnItemClickListener;
61import android.widget.FrameLayout;
62import android.widget.ShareActionProvider;
63import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
64import android.widget.Spinner;
65import android.widget.Toast;
66
67import com.android.gallery3d.R;
68import com.android.gallery3d.app.PhotoPage;
69import com.android.gallery3d.data.LocalAlbum;
70import com.android.gallery3d.filtershow.cache.ImageLoader;
71import com.android.gallery3d.filtershow.category.Action;
72import com.android.gallery3d.filtershow.category.CategoryAdapter;
73import com.android.gallery3d.filtershow.category.CategorySelected;
74import com.android.gallery3d.filtershow.category.CategoryView;
75import com.android.gallery3d.filtershow.category.MainPanel;
76import com.android.gallery3d.filtershow.category.SwipableView;
77import com.android.gallery3d.filtershow.data.UserPresetsManager;
78import com.android.gallery3d.filtershow.editors.BasicEditor;
79import com.android.gallery3d.filtershow.editors.Editor;
80import com.android.gallery3d.filtershow.editors.EditorChanSat;
81import com.android.gallery3d.filtershow.editors.EditorColorBorder;
82import com.android.gallery3d.filtershow.editors.EditorCrop;
83import com.android.gallery3d.filtershow.editors.EditorDraw;
84import com.android.gallery3d.filtershow.editors.EditorGrad;
85import com.android.gallery3d.filtershow.editors.EditorManager;
86import com.android.gallery3d.filtershow.editors.EditorMirror;
87import com.android.gallery3d.filtershow.editors.EditorPanel;
88import com.android.gallery3d.filtershow.editors.EditorRedEye;
89import com.android.gallery3d.filtershow.editors.EditorRotate;
90import com.android.gallery3d.filtershow.editors.EditorStraighten;
91import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
92import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
93import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
94import com.android.gallery3d.filtershow.filters.FilterRepresentation;
95import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
96import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
97import com.android.gallery3d.filtershow.filters.FiltersManager;
98import com.android.gallery3d.filtershow.filters.ImageFilter;
99import com.android.gallery3d.filtershow.history.HistoryItem;
100import com.android.gallery3d.filtershow.history.HistoryManager;
101import com.android.gallery3d.filtershow.imageshow.ImageShow;
102import com.android.gallery3d.filtershow.imageshow.MasterImage;
103import com.android.gallery3d.filtershow.imageshow.Spline;
104import com.android.gallery3d.filtershow.info.InfoPanel;
105import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
106import com.android.gallery3d.filtershow.pipeline.ImagePreset;
107import com.android.gallery3d.filtershow.pipeline.ProcessingService;
108import com.android.gallery3d.filtershow.presets.PresetManagementDialog;
109import com.android.gallery3d.filtershow.presets.UserPresetsAdapter;
110import com.android.gallery3d.filtershow.provider.SharedImageProvider;
111import com.android.gallery3d.filtershow.state.StateAdapter;
112import com.android.gallery3d.filtershow.tools.SaveImage;
113import com.android.gallery3d.filtershow.tools.XmpPresets;
114import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
115import com.android.gallery3d.filtershow.ui.ExportDialog;
116import com.android.gallery3d.filtershow.ui.FramedTextButton;
117import com.android.gallery3d.util.GalleryUtils;
118import com.android.gallery3d.util.PrintJob;
119import com.android.gallery3d.util.UsageStatistics;
120import com.android.photos.data.GalleryBitmapPool;
121
122import java.io.File;
123import java.io.FileDescriptor;
124import java.io.FileOutputStream;
125import java.lang.ref.WeakReference;
126import java.util.ArrayList;
127import java.util.Vector;
128
129public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
130        OnShareTargetSelectedListener {
131
132    private String mAction = "";
133    MasterImage mMasterImage = null;
134
135    private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
136
137    public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
138    public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
139    private ImageShow mImageShow = null;
140
141    private View mSaveButton = null;
142
143    private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
144    private Editor mCurrentEditor = null;
145
146    private static final int SELECT_PICTURE = 1;
147    private static final String LOGTAG = "FilterShowActivity";
148
149    private boolean mShowingTinyPlanet = false;
150    private boolean mShowingImageStatePanel = false;
151    private boolean mShowingVersionsPanel = false;
152
153    private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
154
155    private ShareActionProvider mShareActionProvider;
156    private File mSharedOutputFile = null;
157
158    private boolean mSharingImage = false;
159
160    private WeakReference<ProgressDialog> mSavingProgressDialog;
161
162    private LoadBitmapTask mLoadBitmapTask;
163
164    private Uri mOriginalImageUri = null;
165    private ImagePreset mOriginalPreset = null;
166
167    private Uri mSelectedImageUri = null;
168
169    private ArrayList<Action> mActions = new ArrayList<Action>();
170    private UserPresetsManager mUserPresetsManager = null;
171    private UserPresetsAdapter mUserPresetsAdapter = null;
172    private CategoryAdapter mCategoryLooksAdapter = null;
173    private CategoryAdapter mCategoryBordersAdapter = null;
174    private CategoryAdapter mCategoryGeometryAdapter = null;
175    private CategoryAdapter mCategoryFiltersAdapter = null;
176    private CategoryAdapter mCategoryVersionsAdapter = null;
177    private int mCurrentPanel = MainPanel.LOOKS;
178    private Vector<FilterUserPresetRepresentation> mVersions =
179            new Vector<FilterUserPresetRepresentation>();
180    private int mVersionsCounter = 0;
181
182    private boolean mHandlingSwipeButton = false;
183    private View mHandledSwipeView = null;
184    private float mHandledSwipeViewLastDelta = 0;
185    private float mSwipeStartX = 0;
186    private float mSwipeStartY = 0;
187
188    private ProcessingService mBoundService;
189    private boolean mIsBound = false;
190    private Menu mMenu;
191
192    public ProcessingService getProcessingService() {
193        return mBoundService;
194    }
195
196    public boolean isSimpleEditAction() {
197        return !PhotoPage.ACTION_NEXTGEN_EDIT.equalsIgnoreCase(mAction);
198    }
199
200    private ServiceConnection mConnection = new ServiceConnection() {
201        @Override
202        public void onServiceConnected(ComponentName className, IBinder service) {
203            /*
204             * This is called when the connection with the service has been
205             * established, giving us the service object we can use to
206             * interact with the service.  Because we have bound to a explicit
207             * service that we know is running in our own process, we can
208             * cast its IBinder to a concrete class and directly access it.
209             */
210            mBoundService = ((ProcessingService.LocalBinder)service).getService();
211            mBoundService.setFiltershowActivity(FilterShowActivity.this);
212            mBoundService.onStart();
213        }
214
215        @Override
216        public void onServiceDisconnected(ComponentName className) {
217            /*
218             * This is called when the connection with the service has been
219             * unexpectedly disconnected -- that is, its process crashed.
220             * Because it is running in our same process, we should never
221             * see this happen.
222             */
223            mBoundService = null;
224        }
225    };
226
227    void doBindService() {
228        /*
229         * Establish a connection with the service.  We use an explicit
230         * class name because we want a specific service implementation that
231         * we know will be running in our own process (and thus won't be
232         * supporting component replacement by other applications).
233         */
234        bindService(new Intent(FilterShowActivity.this, ProcessingService.class),
235                mConnection, Context.BIND_AUTO_CREATE);
236        mIsBound = true;
237    }
238
239    void doUnbindService() {
240        if (mIsBound) {
241            // Detach our existing connection.
242            unbindService(mConnection);
243            mIsBound = false;
244        }
245    }
246
247    public void updateUIAfterServiceStarted() {
248        MasterImage.setMaster(mMasterImage);
249        ImageFilter.setActivityForMemoryToasts(this);
250        mUserPresetsManager = new UserPresetsManager(this);
251        mUserPresetsAdapter = new UserPresetsAdapter(this);
252
253        setupMasterImage();
254        setupMenu();
255        setDefaultValues();
256        fillEditors();
257        getWindow().setBackgroundDrawable(new ColorDrawable(0));
258        loadXML();
259
260        fillCategories();
261        loadMainPanel();
262        extractXMPData();
263        processIntent();
264    }
265
266    @Override
267    public void onCreate(Bundle savedInstanceState) {
268        super.onCreate(savedInstanceState);
269
270        boolean onlyUsePortrait = getResources().getBoolean(R.bool.only_use_portrait);
271        if (onlyUsePortrait) {
272            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
273        }
274
275        clearGalleryBitmapPool();
276        doBindService();
277        getWindow().setBackgroundDrawable(new ColorDrawable(Color.GRAY));
278        setContentView(R.layout.filtershow_splashscreen);
279        UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_EDITOR, "Main");
280        UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
281                UsageStatistics.CATEGORY_LIFECYCLE, UsageStatistics.LIFECYCLE_START);
282    }
283
284    public boolean isShowingImageStatePanel() {
285        return mShowingImageStatePanel;
286    }
287
288    public void loadMainPanel() {
289        if (findViewById(R.id.main_panel_container) == null) {
290            return;
291        }
292        MainPanel panel = new MainPanel();
293        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
294        transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
295        transaction.commit();
296    }
297
298    public void loadEditorPanel(FilterRepresentation representation,
299                                final Editor currentEditor) {
300        if (representation.getEditorId() == ImageOnlyEditor.ID) {
301            currentEditor.reflectCurrentFilter();
302            return;
303        }
304        final int currentId = currentEditor.getID();
305        Runnable showEditor = new Runnable() {
306            @Override
307            public void run() {
308                EditorPanel panel = new EditorPanel();
309                panel.setEditor(currentId);
310                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
311                transaction.remove(getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG));
312                transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
313                transaction.commit();
314            }
315        };
316        Fragment main = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
317        boolean doAnimation = false;
318        if (mShowingImageStatePanel
319                && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
320            doAnimation = true;
321        }
322        if (doAnimation && main != null && main instanceof MainPanel) {
323            MainPanel mainPanel = (MainPanel) main;
324            View container = mainPanel.getView().findViewById(R.id.category_panel_container);
325            View bottom = mainPanel.getView().findViewById(R.id.bottom_panel);
326            int panelHeight = container.getHeight() + bottom.getHeight();
327            ViewPropertyAnimator anim = mainPanel.getView().animate();
328            anim.translationY(panelHeight).start();
329            final Handler handler = new Handler();
330            handler.postDelayed(showEditor, anim.getDuration());
331        } else {
332            showEditor.run();
333        }
334    }
335
336    public void toggleInformationPanel() {
337        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
338        transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
339
340        InfoPanel panel = new InfoPanel();
341        panel.show(transaction, InfoPanel.FRAGMENT_TAG);
342    }
343
344    private void loadXML() {
345        setContentView(R.layout.filtershow_activity);
346
347        ActionBar actionBar = getActionBar();
348        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
349        actionBar.setCustomView(R.layout.filtershow_actionbar);
350        actionBar.setBackgroundDrawable(new ColorDrawable(
351                getResources().getColor(R.color.background_screen)));
352
353        mSaveButton = actionBar.getCustomView();
354        mSaveButton.setOnClickListener(new OnClickListener() {
355            @Override
356            public void onClick(View view) {
357                saveImage();
358            }
359        });
360
361        mImageShow = (ImageShow) findViewById(R.id.imageShow);
362        mImageViews.add(mImageShow);
363
364        setupEditors();
365
366        mEditorPlaceHolder.hide();
367        mImageShow.attach();
368
369        setupStatePanel();
370    }
371
372    public void fillCategories() {
373        fillLooks();
374        loadUserPresets();
375        fillBorders();
376        fillTools();
377        fillEffects();
378        fillVersions();
379    }
380
381    public void setupStatePanel() {
382        MasterImage.getImage().setHistoryManager(mMasterImage.getHistory());
383    }
384
385    private void fillVersions() {
386        if (mCategoryVersionsAdapter != null) {
387            mCategoryVersionsAdapter.clear();
388        }
389        mCategoryVersionsAdapter = new CategoryAdapter(this);
390        mCategoryVersionsAdapter.setShowAddButton(true);
391    }
392
393    public void registerAction(Action action) {
394        if (mActions.contains(action)) {
395            return;
396        }
397        mActions.add(action);
398    }
399
400    private void loadActions() {
401        for (int i = 0; i < mActions.size(); i++) {
402            Action action = mActions.get(i);
403            action.setImageFrame(new Rect(0, 0, 96, 96), 0);
404        }
405    }
406
407    public void updateVersions() {
408        mCategoryVersionsAdapter.clear();
409        FilterUserPresetRepresentation originalRep = new FilterUserPresetRepresentation(
410                getString(R.string.filtershow_version_original), new ImagePreset(), -1);
411        mCategoryVersionsAdapter.add(
412                new Action(this, originalRep, Action.FULL_VIEW));
413        ImagePreset current = new ImagePreset(MasterImage.getImage().getPreset());
414        FilterUserPresetRepresentation currentRep = new FilterUserPresetRepresentation(
415                getString(R.string.filtershow_version_current), current, -1);
416        mCategoryVersionsAdapter.add(
417                new Action(this, currentRep, Action.FULL_VIEW));
418        if (mVersions.size() > 0) {
419            mCategoryVersionsAdapter.add(new Action(this, Action.SPACER));
420        }
421        for (FilterUserPresetRepresentation rep : mVersions) {
422            mCategoryVersionsAdapter.add(
423                    new Action(this, rep, Action.FULL_VIEW, true));
424        }
425        mCategoryVersionsAdapter.notifyDataSetInvalidated();
426    }
427
428    public void addCurrentVersion() {
429        ImagePreset current = new ImagePreset(MasterImage.getImage().getPreset());
430        mVersionsCounter++;
431        FilterUserPresetRepresentation rep = new FilterUserPresetRepresentation(
432                "" + mVersionsCounter, current, -1);
433        mVersions.add(rep);
434        updateVersions();
435    }
436
437    public void removeVersion(Action action) {
438        mVersions.remove(action.getRepresentation());
439        updateVersions();
440    }
441
442    public void removeLook(Action action) {
443        FilterUserPresetRepresentation rep =
444                (FilterUserPresetRepresentation) action.getRepresentation();
445        if (rep == null) {
446            return;
447        }
448        mUserPresetsManager.delete(rep.getId());
449        updateUserPresetsFromManager();
450    }
451
452    private void fillEffects() {
453        FiltersManager filtersManager = FiltersManager.getManager();
454        ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getEffects();
455        if (mCategoryFiltersAdapter != null) {
456            mCategoryFiltersAdapter.clear();
457        }
458        mCategoryFiltersAdapter = new CategoryAdapter(this);
459        for (FilterRepresentation representation : filtersRepresentations) {
460            if (representation.getTextId() != 0) {
461                representation.setName(getString(representation.getTextId()));
462            }
463            mCategoryFiltersAdapter.add(new Action(this, representation));
464        }
465    }
466
467    private void fillTools() {
468        FiltersManager filtersManager = FiltersManager.getManager();
469        ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getTools();
470        if (mCategoryGeometryAdapter != null) {
471            mCategoryGeometryAdapter.clear();
472        }
473        mCategoryGeometryAdapter = new CategoryAdapter(this);
474        for (FilterRepresentation representation : filtersRepresentations) {
475            mCategoryGeometryAdapter.add(new Action(this, representation));
476        }
477    }
478
479    private void processIntent() {
480        Intent intent = getIntent();
481        if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
482            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
483        }
484
485        mAction = intent.getAction();
486        mSelectedImageUri = intent.getData();
487        Uri loadUri = mSelectedImageUri;
488        if (mOriginalImageUri != null) {
489            loadUri = mOriginalImageUri;
490        }
491        if (loadUri != null) {
492            startLoadBitmap(loadUri);
493        } else {
494            pickImage();
495        }
496    }
497
498    private void setupEditors() {
499        mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
500        EditorManager.addEditors(mEditorPlaceHolder);
501        mEditorPlaceHolder.setOldViews(mImageViews);
502    }
503
504    private void fillEditors() {
505        mEditorPlaceHolder.addEditor(new EditorChanSat());
506        mEditorPlaceHolder.addEditor(new EditorGrad());
507        mEditorPlaceHolder.addEditor(new EditorDraw());
508        mEditorPlaceHolder.addEditor(new EditorColorBorder());
509        mEditorPlaceHolder.addEditor(new BasicEditor());
510        mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
511        mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
512        mEditorPlaceHolder.addEditor(new EditorRedEye());
513        mEditorPlaceHolder.addEditor(new EditorCrop());
514        mEditorPlaceHolder.addEditor(new EditorMirror());
515        mEditorPlaceHolder.addEditor(new EditorRotate());
516        mEditorPlaceHolder.addEditor(new EditorStraighten());
517    }
518
519    private void setDefaultValues() {
520        Resources res = getResources();
521
522        // TODO: get those values from XML.
523        FramedTextButton.setTextSize((int) getPixelsFromDip(14));
524        FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
525        FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
526
527        Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
528        int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
529        Spline.setCurveHandle(curveHandle, curveHandleSize);
530        Spline.setCurveWidth((int) getPixelsFromDip(3));
531
532        mOriginalImageUri = null;
533    }
534
535    private void startLoadBitmap(Uri uri) {
536        final View loading = findViewById(R.id.loading);
537        final View imageShow = findViewById(R.id.imageShow);
538        imageShow.setVisibility(View.INVISIBLE);
539        loading.setVisibility(View.VISIBLE);
540        mShowingTinyPlanet = false;
541        mLoadBitmapTask = new LoadBitmapTask();
542        mLoadBitmapTask.execute(uri);
543    }
544
545    private void fillBorders() {
546        FiltersManager filtersManager = FiltersManager.getManager();
547        ArrayList<FilterRepresentation> borders = filtersManager.getBorders();
548
549        for (int i = 0; i < borders.size(); i++) {
550            FilterRepresentation filter = borders.get(i);
551            filter.setName(getString(R.string.borders));
552            if (i == 0) {
553                filter.setName(getString(R.string.none));
554            }
555        }
556
557        if (mCategoryBordersAdapter != null) {
558            mCategoryBordersAdapter.clear();
559        }
560        mCategoryBordersAdapter = new CategoryAdapter(this);
561        for (FilterRepresentation representation : borders) {
562            if (representation.getTextId() != 0) {
563                representation.setName(getString(representation.getTextId()));
564            }
565            mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
566        }
567    }
568
569    public UserPresetsAdapter getUserPresetsAdapter() {
570        return mUserPresetsAdapter;
571    }
572
573    public CategoryAdapter getCategoryLooksAdapter() {
574        return mCategoryLooksAdapter;
575    }
576
577    public CategoryAdapter getCategoryBordersAdapter() {
578        return mCategoryBordersAdapter;
579    }
580
581    public CategoryAdapter getCategoryGeometryAdapter() {
582        return mCategoryGeometryAdapter;
583    }
584
585    public CategoryAdapter getCategoryFiltersAdapter() {
586        return mCategoryFiltersAdapter;
587    }
588
589    public CategoryAdapter getCategoryVersionsAdapter() {
590        return mCategoryVersionsAdapter;
591    }
592
593    public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
594        if (filterRepresentation == null) {
595            return;
596        }
597        ImagePreset oldPreset = MasterImage.getImage().getPreset();
598        ImagePreset copy = new ImagePreset(oldPreset);
599        copy.removeFilter(filterRepresentation);
600        MasterImage.getImage().setPreset(copy, copy.getLastRepresentation(), true);
601        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
602            FilterRepresentation lastRepresentation = copy.getLastRepresentation();
603            MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
604        }
605    }
606
607    public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
608        if (filterRepresentation == null) {
609            return;
610        }
611        if (!(filterRepresentation instanceof FilterRotateRepresentation)
612            && !(filterRepresentation instanceof FilterMirrorRepresentation)
613            && MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
614            return;
615        }
616        if (filterRepresentation instanceof FilterUserPresetRepresentation
617                || filterRepresentation instanceof FilterRotateRepresentation
618                || filterRepresentation instanceof FilterMirrorRepresentation) {
619            MasterImage.getImage().onNewLook(filterRepresentation);
620        }
621        ImagePreset oldPreset = MasterImage.getImage().getPreset();
622        ImagePreset copy = new ImagePreset(oldPreset);
623        FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
624        if (representation == null) {
625            filterRepresentation = filterRepresentation.copy();
626            copy.addFilter(filterRepresentation);
627        } else {
628            if (filterRepresentation.allowsSingleInstanceOnly()) {
629                // Don't just update the filter representation. Centralize the
630                // logic in the addFilter(), such that we can keep "None" as
631                // null.
632                if (!representation.equals(filterRepresentation)) {
633                    // Only do this if the filter isn't the same
634                    // (state panel clicks can lead us here)
635                    copy.removeFilter(representation);
636                    copy.addFilter(filterRepresentation);
637                }
638            }
639        }
640        MasterImage.getImage().setPreset(copy, filterRepresentation, true);
641        MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
642    }
643
644    public void showRepresentation(FilterRepresentation representation) {
645        if (representation == null) {
646            return;
647        }
648
649        if (representation instanceof FilterRotateRepresentation) {
650            FilterRotateRepresentation r = (FilterRotateRepresentation) representation;
651            r.rotateCW();
652        }
653        if (representation instanceof FilterMirrorRepresentation) {
654            FilterMirrorRepresentation r = (FilterMirrorRepresentation) representation;
655            r.cycle();
656        }
657        useFilterRepresentation(representation);
658
659        // show representation
660        if (mCurrentEditor != null) {
661            mCurrentEditor.detach();
662        }
663        mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
664        loadEditorPanel(representation, mCurrentEditor);
665    }
666
667    public Editor getEditor(int editorID) {
668        return mEditorPlaceHolder.getEditor(editorID);
669    }
670
671    public void setCurrentPanel(int currentPanel) {
672        mCurrentPanel = currentPanel;
673    }
674
675    public int getCurrentPanel() {
676        return mCurrentPanel;
677    }
678
679    public void updateCategories() {
680        ImagePreset preset = mMasterImage.getPreset();
681        mCategoryLooksAdapter.reflectImagePreset(preset);
682        mCategoryBordersAdapter.reflectImagePreset(preset);
683    }
684
685    public View getMainStatePanelContainer(int id) {
686        return findViewById(id);
687    }
688
689    private class LoadHighresBitmapTask extends AsyncTask<Void, Void, Boolean> {
690        @Override
691        protected Boolean doInBackground(Void... params) {
692            MasterImage master = MasterImage.getImage();
693            Rect originalBounds = master.getOriginalBounds();
694            if (master.supportsHighRes()) {
695                int highresPreviewSize = master.getOriginalBitmapLarge().getWidth() * 2;
696                if (highresPreviewSize > originalBounds.width()) {
697                    highresPreviewSize = originalBounds.width();
698                }
699                Rect bounds = new Rect();
700                Bitmap originalHires = ImageLoader.loadOrientedConstrainedBitmap(master.getUri(),
701                        master.getActivity(), highresPreviewSize,
702                        master.getOrientation(), bounds);
703                master.setOriginalBounds(bounds);
704                master.setOriginalBitmapHighres(originalHires);
705                mBoundService.setOriginalBitmapHighres(originalHires);
706                master.warnListeners();
707            }
708            return true;
709        }
710
711        @Override
712        protected void onPostExecute(Boolean result) {
713            Bitmap highresBitmap = MasterImage.getImage().getOriginalBitmapHighres();
714            if (highresBitmap != null) {
715                float highResPreviewScale = (float) highresBitmap.getWidth()
716                        / (float) MasterImage.getImage().getOriginalBounds().width();
717                mBoundService.setHighresPreviewScaleFactor(highResPreviewScale);
718            }
719            MasterImage.getImage().warnListeners();
720        }
721    }
722
723    public void stopLoadingIndicator() {
724        final View loading = findViewById(R.id.loading);
725        loading.setVisibility(View.GONE);
726    }
727
728    private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
729        int mBitmapSize;
730
731        public LoadBitmapTask() {
732            mBitmapSize = getScreenImageSize();
733        }
734
735        @Override
736        protected Boolean doInBackground(Uri... params) {
737            if (!MasterImage.getImage().loadBitmap(params[0], mBitmapSize)) {
738                return false;
739            }
740            publishProgress(ImageLoader.queryLightCycle360(MasterImage.getImage().getActivity()));
741            return true;
742        }
743
744        @Override
745        protected void onProgressUpdate(Boolean... values) {
746            super.onProgressUpdate(values);
747            if (isCancelled()) {
748                return;
749            }
750            if (values[0]) {
751                mShowingTinyPlanet = true;
752            }
753        }
754
755        @Override
756        protected void onPostExecute(Boolean result) {
757            MasterImage.setMaster(mMasterImage);
758            if (isCancelled()) {
759                return;
760            }
761
762            if (!result) {
763                if (mOriginalImageUri != null
764                        && !mOriginalImageUri.equals(mSelectedImageUri)) {
765                    mOriginalImageUri = mSelectedImageUri;
766                    mOriginalPreset = null;
767                    Toast.makeText(FilterShowActivity.this,
768                            R.string.cannot_edit_original, Toast.LENGTH_SHORT).show();
769                    startLoadBitmap(mOriginalImageUri);
770                } else {
771                    cannotLoadImage();
772                }
773                return;
774            }
775
776            if (null == CachingPipeline.getRenderScriptContext()){
777                Log.v(LOGTAG,"RenderScript context destroyed during load");
778                return;
779            }
780            final View imageShow = findViewById(R.id.imageShow);
781            imageShow.setVisibility(View.VISIBLE);
782
783
784            Bitmap largeBitmap = MasterImage.getImage().getOriginalBitmapLarge();
785            mBoundService.setOriginalBitmap(largeBitmap);
786
787            float previewScale = (float) largeBitmap.getWidth()
788                    / (float) MasterImage.getImage().getOriginalBounds().width();
789            mBoundService.setPreviewScaleFactor(previewScale);
790            if (!mShowingTinyPlanet) {
791                mCategoryFiltersAdapter.removeTinyPlanet();
792            }
793            mCategoryLooksAdapter.imageLoaded();
794            mCategoryBordersAdapter.imageLoaded();
795            mCategoryGeometryAdapter.imageLoaded();
796            mCategoryFiltersAdapter.imageLoaded();
797            mLoadBitmapTask = null;
798
799            MasterImage.getImage().warnListeners();
800            loadActions();
801
802            if (mOriginalPreset != null) {
803                MasterImage.getImage().setLoadedPreset(mOriginalPreset);
804                MasterImage.getImage().setPreset(mOriginalPreset,
805                        mOriginalPreset.getLastRepresentation(), true);
806                mOriginalPreset = null;
807            } else {
808                setDefaultPreset();
809            }
810
811            MasterImage.getImage().resetGeometryImages(true);
812
813            if (mAction == TINY_PLANET_ACTION) {
814                showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
815            }
816            LoadHighresBitmapTask highresLoad = new LoadHighresBitmapTask();
817            highresLoad.execute();
818            MasterImage.getImage().warnListeners();
819            super.onPostExecute(result);
820        }
821
822    }
823
824    private void clearGalleryBitmapPool() {
825        (new AsyncTask<Void, Void, Void>() {
826            @Override
827            protected Void doInBackground(Void... params) {
828                // Free memory held in Gallery's Bitmap pool.  May be O(n) for n bitmaps.
829                GalleryBitmapPool.getInstance().clear();
830                return null;
831            }
832        }).execute();
833    }
834
835    @Override
836    protected void onDestroy() {
837        if (mLoadBitmapTask != null) {
838            mLoadBitmapTask.cancel(false);
839        }
840        mUserPresetsManager.close();
841        doUnbindService();
842        super.onDestroy();
843    }
844
845    // TODO: find a more robust way of handling image size selection
846    // for high screen densities.
847    private int getScreenImageSize() {
848        DisplayMetrics outMetrics = new DisplayMetrics();
849        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
850        return Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
851    }
852
853    private void showSavingProgress(String albumName) {
854        ProgressDialog progress;
855        if (mSavingProgressDialog != null) {
856            progress = mSavingProgressDialog.get();
857            if (progress != null) {
858                progress.show();
859                return;
860            }
861        }
862        // TODO: Allow cancellation of the saving process
863        String progressText;
864        if (albumName == null) {
865            progressText = getString(R.string.saving_image);
866        } else {
867            progressText = getString(R.string.filtershow_saving_image, albumName);
868        }
869        progress = ProgressDialog.show(this, "", progressText, true, false);
870        mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
871    }
872
873    private void hideSavingProgress() {
874        if (mSavingProgressDialog != null) {
875            ProgressDialog progress = mSavingProgressDialog.get();
876            if (progress != null)
877                progress.dismiss();
878        }
879    }
880
881    public void completeSaveImage(Uri saveUri) {
882        if (mSharingImage && mSharedOutputFile != null) {
883            // Image saved, we unblock the content provider
884            Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
885                    Uri.encode(mSharedOutputFile.getAbsolutePath()));
886            ContentValues values = new ContentValues();
887            values.put(SharedImageProvider.PREPARE, false);
888            getContentResolver().insert(uri, values);
889        }
890        setResult(RESULT_OK, new Intent().setData(saveUri));
891        hideSavingProgress();
892        finish();
893    }
894
895    @Override
896    public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
897        // First, let's tell the SharedImageProvider that it will need to wait
898        // for the image
899        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
900                Uri.encode(mSharedOutputFile.getAbsolutePath()));
901        ContentValues values = new ContentValues();
902        values.put(SharedImageProvider.PREPARE, true);
903        getContentResolver().insert(uri, values);
904        mSharingImage = true;
905
906        // Process and save the image in the background.
907        showSavingProgress(null);
908        mImageShow.saveImage(this, mSharedOutputFile);
909        return true;
910    }
911
912    private Intent getDefaultShareIntent() {
913        Intent intent = new Intent(Intent.ACTION_SEND);
914        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
915        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
916        intent.setType(SharedImageProvider.MIME_TYPE);
917        mSharedOutputFile = SaveImage.getNewFile(this, MasterImage.getImage().getUri());
918        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
919                Uri.encode(mSharedOutputFile.getAbsolutePath()));
920        intent.putExtra(Intent.EXTRA_STREAM, uri);
921        return intent;
922    }
923
924    @Override
925    public boolean onCreateOptionsMenu(Menu menu) {
926        getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
927        MenuItem showState = menu.findItem(R.id.showImageStateButton);
928        if (mShowingImageStatePanel) {
929            showState.setTitle(R.string.hide_imagestate_panel);
930        } else {
931            showState.setTitle(R.string.show_imagestate_panel);
932        }
933        mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
934                .getActionProvider();
935        mShareActionProvider.setShareIntent(getDefaultShareIntent());
936        mShareActionProvider.setOnShareTargetSelectedListener(this);
937        mMenu = menu;
938        return true;
939    }
940
941    private void setupMenu(){
942        MenuItem undoItem = mMenu.findItem(R.id.undoButton);
943        MenuItem redoItem = mMenu.findItem(R.id.redoButton);
944        MenuItem resetItem = mMenu.findItem(R.id.resetHistoryButton);
945        mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
946    }
947
948    @Override
949    public void onPause() {
950        super.onPause();
951        if (mShareActionProvider != null) {
952            mShareActionProvider.setOnShareTargetSelectedListener(null);
953        }
954    }
955
956    @Override
957    public void onResume() {
958        super.onResume();
959        if (mShareActionProvider != null) {
960            mShareActionProvider.setOnShareTargetSelectedListener(this);
961        }
962    }
963
964    @Override
965    public boolean onOptionsItemSelected(MenuItem item) {
966        switch (item.getItemId()) {
967            case R.id.undoButton: {
968                HistoryManager adapter = mMasterImage.getHistory();
969                int position = adapter.undo();
970                mMasterImage.onHistoryItemClick(position);
971                backToMain();
972                invalidateViews();
973                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
974                        UsageStatistics.CATEGORY_BUTTON_PRESS, "Undo");
975                return true;
976            }
977            case R.id.redoButton: {
978                HistoryManager adapter = mMasterImage.getHistory();
979                int position = adapter.redo();
980                mMasterImage.onHistoryItemClick(position);
981                invalidateViews();
982                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
983                        UsageStatistics.CATEGORY_BUTTON_PRESS, "Redo");
984                return true;
985            }
986            case R.id.resetHistoryButton: {
987                resetHistory();
988                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
989                        UsageStatistics.CATEGORY_BUTTON_PRESS, "ResetHistory");
990                return true;
991            }
992            case R.id.showImageStateButton: {
993                toggleImageStatePanel();
994                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
995                        UsageStatistics.CATEGORY_BUTTON_PRESS,
996                        mShowingImageStatePanel ? "ShowPanel" : "HidePanel");
997                return true;
998            }
999            case R.id.exportFlattenButton: {
1000                showExportOptionsDialog();
1001                return true;
1002            }
1003            case android.R.id.home: {
1004                saveImage();
1005                return true;
1006            }
1007            case R.id.manageUserPresets: {
1008                manageUserPresets();
1009                return true;
1010            }
1011            case R.id.showInfoPanel: {
1012                toggleInformationPanel();
1013                return true;
1014            }
1015            case R.id.printButton: {
1016                print();
1017                return true;
1018            }
1019        }
1020        return false;
1021    }
1022
1023    public void print() {
1024        Bitmap bitmap = MasterImage.getImage().getHighresImage();
1025        PrintJob.printBitmap(this, "ImagePrint", bitmap);
1026    }
1027
1028    public void addNewPreset() {
1029        DialogFragment dialog = new PresetManagementDialog();
1030        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
1031    }
1032
1033    private void manageUserPresets() {
1034        DialogFragment dialog = new PresetManagementDialog();
1035        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
1036    }
1037
1038    private void showExportOptionsDialog() {
1039        DialogFragment dialog = new ExportDialog();
1040        dialog.show(getSupportFragmentManager(), "ExportDialogFragment");
1041    }
1042
1043    public void updateUserPresetsFromAdapter(UserPresetsAdapter adapter) {
1044        ArrayList<FilterUserPresetRepresentation> representations =
1045                adapter.getDeletedRepresentations();
1046        for (FilterUserPresetRepresentation representation : representations) {
1047            deletePreset(representation.getId());
1048        }
1049        ArrayList<FilterUserPresetRepresentation> changedRepresentations =
1050                adapter.getChangedRepresentations();
1051        for (FilterUserPresetRepresentation representation : changedRepresentations) {
1052            updatePreset(representation);
1053        }
1054        adapter.clearDeletedRepresentations();
1055        adapter.clearChangedRepresentations();
1056        loadUserPresets();
1057    }
1058
1059    public void loadUserPresets() {
1060        mUserPresetsManager.load();
1061        updateUserPresetsFromManager();
1062    }
1063
1064    public void updateUserPresetsFromManager() {
1065        ArrayList<FilterUserPresetRepresentation> presets = mUserPresetsManager.getRepresentations();
1066        if (presets == null) {
1067            return;
1068        }
1069        if (mCategoryLooksAdapter != null) {
1070            fillLooks();
1071        }
1072        if (presets.size() > 0) {
1073            mCategoryLooksAdapter.add(new Action(this, Action.SPACER));
1074        }
1075        mUserPresetsAdapter.clear();
1076        for (int i = 0; i < presets.size(); i++) {
1077            FilterUserPresetRepresentation representation = presets.get(i);
1078            mCategoryLooksAdapter.add(
1079                    new Action(this, representation, Action.FULL_VIEW, true));
1080            mUserPresetsAdapter.add(new Action(this, representation, Action.FULL_VIEW));
1081        }
1082        if (presets.size() > 0) {
1083            mCategoryLooksAdapter.add(new Action(this, Action.ADD_ACTION));
1084        }
1085        mCategoryLooksAdapter.notifyDataSetChanged();
1086        mCategoryLooksAdapter.notifyDataSetInvalidated();
1087    }
1088
1089    public void saveCurrentImagePreset(String name) {
1090        mUserPresetsManager.save(MasterImage.getImage().getPreset(), name);
1091    }
1092
1093    private void deletePreset(int id) {
1094        mUserPresetsManager.delete(id);
1095    }
1096
1097    private void updatePreset(FilterUserPresetRepresentation representation) {
1098        mUserPresetsManager.update(representation);
1099    }
1100
1101    public void enableSave(boolean enable) {
1102        if (mSaveButton != null) {
1103            mSaveButton.setEnabled(enable);
1104        }
1105    }
1106
1107    private void fillLooks() {
1108        FiltersManager filtersManager = FiltersManager.getManager();
1109        ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getLooks();
1110
1111        if (mCategoryLooksAdapter != null) {
1112            mCategoryLooksAdapter.clear();
1113        }
1114        mCategoryLooksAdapter = new CategoryAdapter(this);
1115        int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
1116        mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
1117        for (FilterRepresentation representation : filtersRepresentations) {
1118            mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
1119        }
1120        if (mUserPresetsManager.getRepresentations() == null
1121            || mUserPresetsManager.getRepresentations().size() == 0) {
1122            mCategoryLooksAdapter.add(new Action(this, Action.ADD_ACTION));
1123        }
1124
1125        Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1126        if (panel != null) {
1127            if (panel instanceof MainPanel) {
1128                MainPanel mainPanel = (MainPanel) panel;
1129                mainPanel.loadCategoryLookPanel(true);
1130            }
1131        }
1132    }
1133
1134    public void setDefaultPreset() {
1135        // Default preset (original)
1136        ImagePreset preset = new ImagePreset(); // empty
1137        mMasterImage.setPreset(preset, preset.getLastRepresentation(), true);
1138    }
1139
1140    // //////////////////////////////////////////////////////////////////////////////
1141    // Some utility functions
1142    // TODO: finish the cleanup.
1143
1144    public void invalidateViews() {
1145        for (ImageShow views : mImageViews) {
1146            views.updateImage();
1147        }
1148    }
1149
1150    public void hideImageViews() {
1151        for (View view : mImageViews) {
1152            view.setVisibility(View.GONE);
1153        }
1154        mEditorPlaceHolder.hide();
1155    }
1156
1157    // //////////////////////////////////////////////////////////////////////////////
1158    // imageState panel...
1159
1160    public void toggleImageStatePanel() {
1161        invalidateOptionsMenu();
1162        mShowingImageStatePanel = !mShowingImageStatePanel;
1163        Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1164        if (panel != null) {
1165            if (panel instanceof EditorPanel) {
1166                EditorPanel editorPanel = (EditorPanel) panel;
1167                editorPanel.showImageStatePanel(mShowingImageStatePanel);
1168            } else if (panel instanceof MainPanel) {
1169                MainPanel mainPanel = (MainPanel) panel;
1170                mainPanel.showImageStatePanel(mShowingImageStatePanel);
1171            }
1172        }
1173    }
1174
1175    public void toggleVersionsPanel() {
1176        mShowingVersionsPanel = !mShowingVersionsPanel;
1177        Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1178        if (panel != null && panel instanceof MainPanel) {
1179            MainPanel mainPanel = (MainPanel) panel;
1180            mainPanel.loadCategoryVersionsPanel();
1181        }
1182    }
1183
1184    @Override
1185    public void onConfigurationChanged(Configuration newConfig)
1186    {
1187        super.onConfigurationChanged(newConfig);
1188        setDefaultValues();
1189        loadXML();
1190        fillCategories();
1191        loadMainPanel();
1192
1193        // mLoadBitmapTask==null implies you have looked at the intent
1194        if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
1195            mCategoryFiltersAdapter.removeTinyPlanet();
1196        }
1197        stopLoadingIndicator();
1198    }
1199
1200    public void setupMasterImage() {
1201
1202        HistoryManager historyManager = new HistoryManager();
1203        StateAdapter imageStateAdapter = new StateAdapter(this, 0);
1204        MasterImage.reset();
1205        mMasterImage = MasterImage.getImage();
1206        mMasterImage.setHistoryManager(historyManager);
1207        mMasterImage.setStateAdapter(imageStateAdapter);
1208        mMasterImage.setActivity(this);
1209        mMasterImage.setFirstLoad(true);
1210
1211        if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
1212            mMasterImage.setSupportsHighRes(true);
1213        } else {
1214            mMasterImage.setSupportsHighRes(false);
1215        }
1216    }
1217
1218    void resetHistory() {
1219        HistoryManager adapter = mMasterImage.getHistory();
1220        adapter.reset();
1221        HistoryItem historyItem = adapter.getItem(0);
1222        ImagePreset original = new ImagePreset(historyItem.getImagePreset());
1223        mMasterImage.setPreset(original, historyItem.getFilterRepresentation(), true);
1224        invalidateViews();
1225        backToMain();
1226    }
1227
1228    public void showDefaultImageView() {
1229        mEditorPlaceHolder.hide();
1230        mImageShow.setVisibility(View.VISIBLE);
1231        MasterImage.getImage().setCurrentFilter(null);
1232        MasterImage.getImage().setCurrentFilterRepresentation(null);
1233    }
1234
1235    public void backToMain() {
1236        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1237        if (currentPanel instanceof MainPanel) {
1238            return;
1239        }
1240        loadMainPanel();
1241        showDefaultImageView();
1242    }
1243
1244    @Override
1245    public void onBackPressed() {
1246        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1247        if (currentPanel instanceof MainPanel) {
1248            if (!mImageShow.hasModifications()) {
1249                done();
1250            } else {
1251                AlertDialog.Builder builder = new AlertDialog.Builder(this);
1252                builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
1253                builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
1254                    @Override
1255                    public void onClick(DialogInterface dialog, int id) {
1256                        saveImage();
1257                    }
1258                });
1259                builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
1260                    @Override
1261                    public void onClick(DialogInterface dialog, int id) {
1262                        done();
1263                    }
1264                });
1265                builder.show();
1266            }
1267        } else {
1268            backToMain();
1269        }
1270    }
1271
1272    public void cannotLoadImage() {
1273        Toast.makeText(this, R.string.cannot_load_image, Toast.LENGTH_SHORT).show();
1274        finish();
1275    }
1276
1277    // //////////////////////////////////////////////////////////////////////////////
1278
1279    public float getPixelsFromDip(float value) {
1280        Resources r = getResources();
1281        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
1282                r.getDisplayMetrics());
1283    }
1284
1285    @Override
1286    public void onItemClick(AdapterView<?> parent, View view, int position,
1287            long id) {
1288        mMasterImage.onHistoryItemClick(position);
1289        invalidateViews();
1290    }
1291
1292    public void pickImage() {
1293        Intent intent = new Intent();
1294        intent.setType("image/*");
1295        intent.setAction(Intent.ACTION_GET_CONTENT);
1296        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
1297                SELECT_PICTURE);
1298    }
1299
1300    @Override
1301    public void onActivityResult(int requestCode, int resultCode, Intent data) {
1302        if (resultCode == RESULT_OK) {
1303            if (requestCode == SELECT_PICTURE) {
1304                Uri selectedImageUri = data.getData();
1305                startLoadBitmap(selectedImageUri);
1306            }
1307        }
1308    }
1309
1310
1311    public void saveImage() {
1312        if (mImageShow.hasModifications()) {
1313            // Get the name of the album, to which the image will be saved
1314            File saveDir = SaveImage.getFinalSaveDirectory(this, mSelectedImageUri);
1315            int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
1316            String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
1317            showSavingProgress(albumName);
1318            mImageShow.saveImage(this, null);
1319        } else {
1320            done();
1321        }
1322    }
1323
1324
1325    public void done() {
1326        hideSavingProgress();
1327        if (mLoadBitmapTask != null) {
1328            mLoadBitmapTask.cancel(false);
1329        }
1330        finish();
1331    }
1332
1333    private void extractXMPData() {
1334        XMresults res = XmpPresets.extractXMPData(
1335                getBaseContext(), mMasterImage, getIntent().getData());
1336        if (res == null)
1337            return;
1338
1339        mOriginalImageUri = res.originalimage;
1340        mOriginalPreset = res.preset;
1341    }
1342
1343    public Uri getSelectedImageUri() {
1344        return mSelectedImageUri;
1345    }
1346
1347    public void setHandlesSwipeForView(View view, float startX, float startY) {
1348        if (view != null) {
1349            mHandlingSwipeButton = true;
1350        } else {
1351            mHandlingSwipeButton = false;
1352        }
1353        mHandledSwipeView = view;
1354        int[] location = new int[2];
1355        view.getLocationInWindow(location);
1356        mSwipeStartX = location[0] + startX;
1357        mSwipeStartY = location[1] + startY;
1358    }
1359
1360    public boolean dispatchTouchEvent (MotionEvent ev) {
1361        if (mHandlingSwipeButton) {
1362            int direction = CategoryView.HORIZONTAL;
1363            if (mHandledSwipeView instanceof CategoryView) {
1364                direction = ((CategoryView) mHandledSwipeView).getOrientation();
1365            }
1366            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
1367                float delta = ev.getY() - mSwipeStartY;
1368                float distance = mHandledSwipeView.getHeight();
1369                if (direction == CategoryView.VERTICAL) {
1370                    delta = ev.getX() - mSwipeStartX;
1371                    mHandledSwipeView.setTranslationX(delta);
1372                    distance = mHandledSwipeView.getWidth();
1373                } else {
1374                    mHandledSwipeView.setTranslationY(delta);
1375                }
1376                delta = Math.abs(delta);
1377                float transparency = Math.min(1, delta / distance);
1378                mHandledSwipeView.setAlpha(1.f - transparency);
1379                mHandledSwipeViewLastDelta = delta;
1380            }
1381            if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1382                    || ev.getActionMasked() == MotionEvent.ACTION_UP) {
1383                mHandledSwipeView.setTranslationX(0);
1384                mHandledSwipeView.setTranslationY(0);
1385                mHandledSwipeView.setAlpha(1.f);
1386                mHandlingSwipeButton = false;
1387                float distance = mHandledSwipeView.getHeight();
1388                if (direction == CategoryView.VERTICAL) {
1389                    distance = mHandledSwipeView.getWidth();
1390                }
1391                if (mHandledSwipeViewLastDelta > distance) {
1392                    ((SwipableView) mHandledSwipeView).delete();
1393                }
1394            }
1395            return true;
1396        }
1397        return super.dispatchTouchEvent(ev);
1398    }
1399
1400    public Point mHintTouchPoint = new Point();
1401
1402    public Point hintTouchPoint(View view) {
1403        int location[] = new int[2];
1404        view.getLocationOnScreen(location);
1405        int x = mHintTouchPoint.x - location[0];
1406        int y = mHintTouchPoint.y - location[1];
1407        return new Point(x, y);
1408    }
1409
1410    public void startTouchAnimation(View target, float x, float y) {
1411        final CategorySelected hint =
1412                (CategorySelected) findViewById(R.id.categorySelectedIndicator);
1413        int location[] = new int[2];
1414        target.getLocationOnScreen(location);
1415        mHintTouchPoint.x = (int) (location[0] + x);
1416        mHintTouchPoint.y = (int) (location[1] + y);
1417        int locationHint[] = new int[2];
1418        ((View)hint.getParent()).getLocationOnScreen(locationHint);
1419        int dx = (int) (x - (hint.getWidth())/2);
1420        int dy = (int) (y - (hint.getHeight())/2);
1421        hint.setTranslationX(location[0] - locationHint[0] + dx);
1422        hint.setTranslationY(location[1] - locationHint[1] + dy);
1423        hint.setVisibility(View.VISIBLE);
1424        hint.animate().scaleX(2).scaleY(2).alpha(0).withEndAction(new Runnable() {
1425            @Override
1426            public void run() {
1427                hint.setVisibility(View.INVISIBLE);
1428                hint.setScaleX(1);
1429                hint.setScaleY(1);
1430                hint.setAlpha(1);
1431            }
1432        });
1433    }
1434}
1435