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