OSDN Git Service

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