FilterShowActivity.java revision 63f82ea45bf9f39818a21096cf178eab9aebfa71
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.app.WallpaperManager;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.res.Configuration;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.Color;
31import android.graphics.Point;
32import android.graphics.drawable.Drawable;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Bundle;
36import android.provider.MediaStore;
37import android.support.v4.app.FragmentActivity;
38import android.support.v4.app.FragmentTransaction;
39import android.util.DisplayMetrics;
40import android.util.Log;
41import android.util.TypedValue;
42import android.view.*;
43import android.view.View.OnClickListener;
44import android.widget.AdapterView;
45import android.widget.AdapterView.OnItemClickListener;
46import android.widget.FrameLayout;
47import android.widget.ImageButton;
48import android.widget.LinearLayout;
49import android.widget.ListView;
50import android.widget.ShareActionProvider;
51import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
52import android.widget.Toast;
53
54import com.android.gallery3d.R;
55import com.android.gallery3d.data.LocalAlbum;
56import com.android.gallery3d.filtershow.cache.CachingPipeline;
57import com.android.gallery3d.filtershow.cache.FilteringPipeline;
58import com.android.gallery3d.filtershow.cache.ImageLoader;
59import com.android.gallery3d.filtershow.crop.CropExtras;
60import com.android.gallery3d.filtershow.editors.BasicEditor;
61import com.android.gallery3d.filtershow.editors.EditorCrop;
62import com.android.gallery3d.filtershow.editors.EditorDraw;
63import com.android.gallery3d.filtershow.editors.EditorFlip;
64import com.android.gallery3d.filtershow.editors.EditorInfo;
65import com.android.gallery3d.filtershow.editors.EditorManager;
66import com.android.gallery3d.filtershow.editors.EditorRedEye;
67import com.android.gallery3d.filtershow.editors.EditorRotate;
68import com.android.gallery3d.filtershow.editors.EditorStraighten;
69import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
70import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
71import com.android.gallery3d.filtershow.filters.*;
72import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
73import com.android.gallery3d.filtershow.imageshow.ImageCrop;
74import com.android.gallery3d.filtershow.imageshow.ImageShow;
75import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet;
76import com.android.gallery3d.filtershow.imageshow.MasterImage;
77import com.android.gallery3d.filtershow.presets.ImagePreset;
78import com.android.gallery3d.filtershow.provider.SharedImageProvider;
79import com.android.gallery3d.filtershow.state.StateAdapter;
80import com.android.gallery3d.filtershow.state.StatePanel;
81import com.android.gallery3d.filtershow.tools.BitmapTask;
82import com.android.gallery3d.filtershow.tools.SaveCopyTask;
83import com.android.gallery3d.filtershow.tools.XmpPresets;
84import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
85import com.android.gallery3d.filtershow.ui.FilterIconButton;
86import com.android.gallery3d.filtershow.ui.FramedTextButton;
87import com.android.gallery3d.filtershow.ui.Spline;
88import com.android.gallery3d.util.GalleryUtils;
89import com.android.photos.data.GalleryBitmapPool;
90
91import java.io.File;
92import java.io.IOException;
93import java.lang.ref.WeakReference;
94import java.util.Vector;
95
96public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
97        OnShareTargetSelectedListener {
98
99    private String mPanelFragmentTag = "StatePanel";
100
101    // fields for supporting crop action
102    public static final String CROP_ACTION = "com.android.camera.action.CROP";
103    private CropExtras mCropExtras = null;
104    private String mAction = "";
105    MasterImage mMasterImage = null;
106
107    private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
108
109    public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
110    public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
111    public static final int MAX_BMAP_IN_INTENT = 990000;
112    private final PanelController mPanelController = new PanelController();
113    private ImageLoader mImageLoader = null;
114    private ImageShow mImageShow = null;
115    private ImageTinyPlanet mImageTinyPlanet = null;
116
117    private View mSaveButton = null;
118
119    private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
120
121    private static final int SELECT_PICTURE = 1;
122    private static final String LOGTAG = "FilterShowActivity";
123    protected static final boolean ANIMATE_PANELS = true;
124
125    private boolean mShowingTinyPlanet = false;
126    private boolean mShowingImageStatePanel = false;
127
128    private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
129
130    private ShareActionProvider mShareActionProvider;
131    private File mSharedOutputFile = null;
132
133    private boolean mSharingImage = false;
134
135    private WeakReference<ProgressDialog> mSavingProgressDialog;
136
137    private LoadBitmapTask mLoadBitmapTask;
138    private FilterIconButton mNullFxFilter;
139    private FilterIconButton mNullBorderFilter;
140    private int mIconSeedSize = 140;
141    private Uri mOriginalImageUri = null;
142    private ImagePreset mOriginalPreset = null;
143    private View mImageCategoryPanel = null;
144
145    @Override
146    public void onCreate(Bundle savedInstanceState) {
147        super.onCreate(savedInstanceState);
148
149        clearGalleryBitmapPool();
150
151        CachingPipeline.createRenderscriptContext(this);
152        setupMasterImage();
153        setDefaultValues();
154        fillEditors();
155
156        loadXML();
157
158        if (getResources().getConfiguration().orientation
159                == Configuration.ORIENTATION_LANDSCAPE) {
160            mShowingImageStatePanel = true;
161        }
162
163        if (mShowingImageStatePanel && (savedInstanceState == null)) {
164            loadImageStatePanel();
165        }
166
167        setDefaultPreset();
168
169        extractXMPData();
170        processIntent();
171    }
172
173    private void loadXML() {
174        setContentView(R.layout.filtershow_activity);
175
176        ((ViewStub) findViewById(R.id.stateCategoryStub)).inflate();
177        ((ViewStub) findViewById(R.id.editorPanelStub)).inflate();
178
179        ActionBar actionBar = getActionBar();
180        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
181        actionBar.setCustomView(R.layout.filtershow_actionbar);
182
183        mSaveButton = actionBar.getCustomView();
184        mSaveButton.setOnClickListener(new OnClickListener() {
185            @Override
186            public void onClick(View view) {
187                saveImage();
188            }
189        });
190
191        mImageShow = (ImageShow) findViewById(R.id.imageShow);
192        mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet);
193        mImageViews.add(mImageShow);
194        mImageViews.add(mImageTinyPlanet);
195
196        setupEditors();
197
198        mEditorPlaceHolder.hide();
199
200        mImageShow.setImageLoader(mImageLoader);
201        mImageTinyPlanet.setImageLoader(mImageLoader);
202
203        mPanelController.clear();
204        mPanelController.setActivity(this);
205        mPanelController.setEditorPlaceHolder(mEditorPlaceHolder);
206
207        mPanelController.addImageView(findViewById(R.id.imageShow));
208        mPanelController.addImageView(findViewById(R.id.imageTinyPlanet));
209
210        mPanelController.addPanel(R.id.fxButton, R.id.fxList, 0);
211        mPanelController.addPanel(R.id.borderButton, R.id.bordersList, 1);
212        mPanelController.addPanel(R.id.geometryButton, R.id.geometryList, 2);
213        mPanelController.addPanel(R.id.colorsButton, R.id.colorsFxList, 3);
214
215        fillFx((LinearLayout) findViewById(R.id.listFilters), R.id.fxButton);
216        setupBorders();
217        fillGeometry();
218        fillFilters();
219
220        mPanelController.addView(findViewById(R.id.applyEffect));
221
222        setupStatePanel();
223
224        mImageCategoryPanel = findViewById(R.id.imageCategoryPanel);
225    }
226
227    public void hideCategoryPanel() {
228        mImageCategoryPanel.setVisibility(View.GONE);
229    }
230
231    public void showCategoryPanel() {
232        mImageCategoryPanel.setVisibility(View.VISIBLE);
233    }
234
235    public void setupStatePanel() {
236        mImageLoader.setAdapter(mMasterImage.getHistory());
237        mPanelController.setRowPanel(findViewById(R.id.secondRowPanel));
238        mPanelController.setUtilityPanel(this, findViewById(R.id.filterButtonsList));
239        mPanelController.setCurrentPanel(R.id.fxButton);
240    }
241
242    private void fillPanel(Vector<FilterRepresentation> representations, int layoutId, int buttonId) {
243        ImageButton button = (ImageButton) findViewById(buttonId);
244        LinearLayout layout = (LinearLayout) findViewById(layoutId);
245
246        for (FilterRepresentation representation : representations) {
247            setupFilterRepresentationButton(representation, layout, button);
248        }
249    }
250
251    private void fillFilters() {
252        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
253        FiltersManager filtersManager = FiltersManager.getManager();
254        filtersManager.addEffects(filtersRepresentations);
255        fillPanel(filtersRepresentations, R.id.listColorsFx, R.id.colorsButton);
256    }
257
258    private void fillGeometry() {
259        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
260        FiltersManager filtersManager = FiltersManager.getManager();
261
262        GeometryMetadata geo = new GeometryMetadata();
263        int[] editorsId = geo.getEditorIds();
264        for (int i = 0; i < editorsId.length; i++) {
265            int editorId = editorsId[i];
266            GeometryMetadata geometry = new GeometryMetadata(geo);
267            geometry.setEditorId(editorId);
268            EditorInfo editorInfo = (EditorInfo) mEditorPlaceHolder.getEditor(editorId);
269            geometry.setTextId(editorInfo.getTextId());
270            geometry.setOverlayId(editorInfo.getOverlayId());
271            geometry.setOverlayOnly(editorInfo.getOverlayOnly());
272            filtersRepresentations.add(geometry);
273        }
274
275        filtersManager.addTools(filtersRepresentations);
276        fillPanel(filtersRepresentations, R.id.listGeometry, R.id.geometryButton);
277    }
278
279    private void processIntent() {
280        Intent intent = getIntent();
281        if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
282            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
283        }
284
285        mAction = intent.getAction();
286        Uri srcUri = intent.getData();
287        if (mOriginalImageUri != null) {
288            srcUri = mOriginalImageUri;
289        }
290        if (srcUri != null) {
291            startLoadBitmap(srcUri);
292        } else {
293            pickImage();
294        }
295
296        // Handle behavior for various actions
297        if (mAction.equalsIgnoreCase(CROP_ACTION)) {
298            Bundle extras = intent.getExtras();
299            if (extras != null) {
300                mCropExtras = new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
301                        extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
302                        extras.getBoolean(CropExtras.KEY_SCALE, true) &&
303                                extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
304                        extras.getInt(CropExtras.KEY_ASPECT_X, 0),
305                        extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
306                        extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
307                        extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
308                        (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
309                        extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
310                        extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
311                        extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
312                        extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
313
314                if (mCropExtras.getShowWhenLocked()) {
315                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
316                }
317                mImageShow.getImagePreset().mGeoData.setCropExtras(mCropExtras);
318
319                // FIXME: moving to editors breaks the crop action
320                EditorCrop crop = (EditorCrop) mEditorPlaceHolder.getEditor(EditorCrop.ID);
321
322                crop.setExtras(mCropExtras);
323                String s = getString(R.string.Fixed);
324                crop.setAspectString(s);
325                crop.setCropActionFlag(true);
326                mPanelController.setFixedAspect(mCropExtras.getAspectX() > 0
327                        && mCropExtras.getAspectY() > 0);
328            }
329        }
330    }
331
332    private void setupEditors() {
333        mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
334        EditorManager.addEditors(mEditorPlaceHolder);
335        mEditorPlaceHolder.setOldViews(mImageViews);
336        mEditorPlaceHolder.setImageLoader(mImageLoader);
337    }
338
339    private void fillEditors() {
340        mEditorPlaceHolder.addEditor(new EditorDraw());
341        mEditorPlaceHolder.addEditor(new BasicEditor());
342        mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
343        mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
344        mEditorPlaceHolder.addEditor(new EditorRedEye());
345        mEditorPlaceHolder.addEditor(new EditorCrop());
346        mEditorPlaceHolder.addEditor(new EditorFlip());
347        mEditorPlaceHolder.addEditor(new EditorRotate());
348        mEditorPlaceHolder.addEditor(new EditorStraighten());
349    }
350
351    private void setDefaultValues() {
352        ImageFilter.setActivityForMemoryToasts(this);
353
354        Resources res = getResources();
355        FiltersManager.setResources(res);
356
357        ImageShow.setDefaultBackgroundColor(res.getColor(R.color.background_screen));
358        // TODO: get those values from XML.
359        FramedTextButton.setTextSize((int) getPixelsFromDip(14));
360        FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
361        FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
362        ImageShow.setTextSize((int) getPixelsFromDip(12));
363        ImageShow.setTextPadding((int) getPixelsFromDip(10));
364        ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4));
365        ImageShow.setOriginalTextSize((int) getPixelsFromDip(18));
366        ImageShow.setOriginalText(res.getString(R.string.original_picture_text));
367        mIconSeedSize = res.getDimensionPixelSize(R.dimen.thumbnail_size);
368        // TODO: pick correct value
369        // MasterImage.setIconSeedSize(mIconSeedSize);
370
371        Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
372        int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
373        Spline.setCurveHandle(curveHandle, curveHandleSize);
374        Spline.setCurveWidth((int) getPixelsFromDip(3));
375
376        ImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
377        ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
378        ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
379    }
380
381    private void startLoadBitmap(Uri uri) {
382        final View filters = findViewById(R.id.filtersPanel);
383        final View loading = findViewById(R.id.loading);
384        final View imageShow = findViewById(R.id.imageShow);
385        imageShow.setVisibility(View.INVISIBLE);
386        filters.setVisibility(View.INVISIBLE);
387        loading.setVisibility(View.VISIBLE);
388
389        View tinyPlanetView = findViewById(EditorTinyPlanet.ID);
390        if (tinyPlanetView != null) {
391            mShowingTinyPlanet = false;
392            tinyPlanetView.setVisibility(View.GONE);
393        }
394        mLoadBitmapTask = new LoadBitmapTask(tinyPlanetView);
395        mLoadBitmapTask.execute(uri);
396    }
397
398    private void setupBorders() {
399        LinearLayout list = (LinearLayout) findViewById(R.id.listBorders);
400        Vector<FilterRepresentation> borders = new Vector<FilterRepresentation>();
401        ImageButton borderButton = (ImageButton) findViewById(R.id.borderButton);
402
403        // The "no border" implementation
404        borders.add(new FilterImageBorderRepresentation(0));
405
406        // Google-build borders
407        FiltersManager.getManager().addBorders(this, borders);
408
409        for (int i = 0; i < borders.size(); i++) {
410            FilterRepresentation filter = borders.elementAt(i);
411            filter.setScrName(getString(R.string.borders));
412            if (i == 0) {
413                filter.setScrName(getString(R.string.none));
414            }
415            FilterIconButton b = setupFilterRepresentationButton(filter, list, borderButton);
416            if (i == 0) {
417                mNullBorderFilter = b;
418                mNullBorderFilter.setSelected(true);
419            }
420        }
421    }
422
423    private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
424        View mTinyPlanetButton;
425        int mBitmapSize;
426
427        public LoadBitmapTask(View button) {
428            mTinyPlanetButton = button;
429            mBitmapSize = getScreenImageSize();
430        }
431
432        @Override
433        protected Boolean doInBackground(Uri... params) {
434            if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) {
435                return false;
436            }
437            publishProgress(mImageLoader.queryLightCycle360());
438            return true;
439        }
440
441        @Override
442        protected void onProgressUpdate(Boolean... values) {
443            super.onProgressUpdate(values);
444            if (isCancelled()) {
445                return;
446            }
447            if (values[0]) {
448                mShowingTinyPlanet = true;
449                mTinyPlanetButton.setVisibility(View.VISIBLE);
450            }
451        }
452
453        @Override
454        protected void onPostExecute(Boolean result) {
455
456            if (isCancelled()) {
457                return;
458            }
459
460            if (!result) {
461                cannotLoadImage();
462            }
463
464            final View loading = findViewById(R.id.loading);
465            loading.setVisibility(View.GONE);
466            final View filters = findViewById(R.id.filtersPanel);
467            filters.setVisibility(View.VISIBLE);
468            if (PanelController.useAnimationsLayer()) {
469                float y = filters.getY();
470                filters.setY(y + filters.getHeight());
471                filters.animate().setDuration(600).y(y).withLayer().start();
472            }
473            final View imageShow = findViewById(R.id.imageShow);
474            imageShow.setVisibility(View.VISIBLE);
475
476            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
477            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
478            pipeline.setOriginal(largeBitmap);
479            float previewScale = (float) largeBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
480            pipeline.setPreviewScaleFactor(previewScale);
481            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
482            if (highresBitmap != null) {
483                float highResPreviewScale = (float) highresBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
484                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
485            }
486            pipeline.turnOnPipeline(true);
487            MasterImage.getImage().setOriginalGeometry(largeBitmap);
488            mLoadBitmapTask = null;
489            if (mOriginalPreset != null) {
490                MasterImage.getImage().setPreset(mOriginalPreset, true);
491                mOriginalPreset = null;
492            }
493            if (mAction == CROP_ACTION) {
494                mPanelController.showComponent(findViewById(EditorCrop.ID));
495            } else if (mAction == TINY_PLANET_ACTION) {
496                mPanelController.showComponent(findViewById(EditorTinyPlanet.ID));
497            }
498            super.onPostExecute(result);
499        }
500
501    }
502
503    private void clearGalleryBitmapPool() {
504        (new AsyncTask<Void, Void, Void>() {
505            @Override
506            protected Void doInBackground(Void... params) {
507                // Free memory held in Gallery's Bitmap pool.  May be O(n) for n bitmaps.
508                GalleryBitmapPool.getInstance().clear();
509                return null;
510            }
511        }).execute();
512    }
513
514    @Override
515    protected void onDestroy() {
516        if (mLoadBitmapTask != null) {
517            mLoadBitmapTask.cancel(false);
518        }
519        // TODO:  Using singletons is a bad design choice for many of these
520        // due static reference leaks and in general.  Please refactor.
521        FilteringPipeline.getPipeline().turnOnPipeline(false);
522        MasterImage.reset();
523        FilteringPipeline.reset();
524        ImageFilter.resetStatics();
525        FiltersManager.getPreviewManager().freeRSFilterScripts();
526        FiltersManager.getManager().freeRSFilterScripts();
527        FiltersManager.getHighresManager().freeRSFilterScripts();
528        FiltersManager.reset();
529        CachingPipeline.destroyRenderScriptContext();
530        super.onDestroy();
531    }
532
533    private int translateMainPanel(View viewPanel) {
534        int accessoryPanelWidth = viewPanel.getWidth();
535        if (accessoryPanelWidth == 0) {
536            // TODO: fixes this by using a fragment. Currently,
537            // the first time we get called the panel hasn't been
538            // layed out yet, so we get a size zero.
539            accessoryPanelWidth = (int) getPixelsFromDip(200);
540        }
541        int mainViewWidth = findViewById(R.id.mainView).getWidth();
542        int mainPanelWidth = mImageShow.getDisplayedImageBounds().width();
543        if (mainPanelWidth == 0) {
544            mainPanelWidth = mainViewWidth;
545        }
546        int filtersPanelWidth = findViewById(R.id.filtersPanel).getWidth();
547        if (mainPanelWidth < filtersPanelWidth) {
548            mainPanelWidth = filtersPanelWidth;
549        }
550        int leftOver = mainViewWidth - mainPanelWidth - accessoryPanelWidth;
551        if (leftOver < 0) {
552            return -accessoryPanelWidth;
553        }
554        return 0;
555    }
556
557    private int getScreenImageSize() {
558        DisplayMetrics metrics = new DisplayMetrics();
559        Display display = getWindowManager().getDefaultDisplay();
560        Point size = new Point();
561        display.getSize(size);
562        display.getMetrics(metrics);
563        int msize = Math.min(size.x, size.y);
564        return (133 * msize) / metrics.densityDpi;
565    }
566
567    private void showSavingProgress(String albumName) {
568        ProgressDialog progress;
569        if (mSavingProgressDialog != null) {
570            progress = mSavingProgressDialog.get();
571            if (progress != null) {
572                progress.show();
573                return;
574            }
575        }
576        // TODO: Allow cancellation of the saving process
577        String progressText;
578        if (albumName == null) {
579            progressText = getString(R.string.saving_image);
580        } else {
581            progressText = getString(R.string.filtershow_saving_image, albumName);
582        }
583        progress = ProgressDialog.show(this, "", progressText, true, false);
584        mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
585    }
586
587    private void hideSavingProgress() {
588        if (mSavingProgressDialog != null) {
589            ProgressDialog progress = mSavingProgressDialog.get();
590            if (progress != null)
591                progress.dismiss();
592        }
593    }
594
595    public void completeSaveImage(Uri saveUri) {
596        if (mSharingImage && mSharedOutputFile != null) {
597            // Image saved, we unblock the content provider
598            Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
599                    Uri.encode(mSharedOutputFile.getAbsolutePath()));
600            ContentValues values = new ContentValues();
601            values.put(SharedImageProvider.PREPARE, false);
602            getContentResolver().insert(uri, values);
603        }
604        setResult(RESULT_OK, new Intent().setData(saveUri));
605        hideSavingProgress();
606        finish();
607    }
608
609    @Override
610    public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
611        // First, let's tell the SharedImageProvider that it will need to wait
612        // for the image
613        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
614                Uri.encode(mSharedOutputFile.getAbsolutePath()));
615        ContentValues values = new ContentValues();
616        values.put(SharedImageProvider.PREPARE, true);
617        getContentResolver().insert(uri, values);
618        mSharingImage = true;
619
620        // Process and save the image in the background.
621        showSavingProgress(null);
622        mImageShow.saveImage(this, mSharedOutputFile);
623        return true;
624    }
625
626    private Intent getDefaultShareIntent() {
627        Intent intent = new Intent(Intent.ACTION_SEND);
628        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
629        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
630        intent.setType(SharedImageProvider.MIME_TYPE);
631        mSharedOutputFile = SaveCopyTask.getNewFile(this, mImageLoader.getUri());
632        Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
633                Uri.encode(mSharedOutputFile.getAbsolutePath()));
634        intent.putExtra(Intent.EXTRA_STREAM, uri);
635        return intent;
636    }
637
638    @Override
639    public boolean onCreateOptionsMenu(Menu menu) {
640        getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
641        MenuItem showState = menu.findItem(R.id.showImageStateButton);
642        if (mShowingImageStatePanel) {
643            showState.setTitle(R.string.hide_imagestate_panel);
644        } else {
645            showState.setTitle(R.string.show_imagestate_panel);
646        }
647        mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
648                .getActionProvider();
649        mShareActionProvider.setShareIntent(getDefaultShareIntent());
650        mShareActionProvider.setOnShareTargetSelectedListener(this);
651
652        MenuItem undoItem = menu.findItem(R.id.undoButton);
653        MenuItem redoItem = menu.findItem(R.id.redoButton);
654        MenuItem resetItem = menu.findItem(R.id.resetHistoryButton);
655        mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
656        return true;
657    }
658
659    @Override
660    public void onPause() {
661        super.onPause();
662        if (mShareActionProvider != null) {
663            mShareActionProvider.setOnShareTargetSelectedListener(null);
664        }
665    }
666
667    @Override
668    public void onResume() {
669        super.onResume();
670        if (mShareActionProvider != null) {
671            mShareActionProvider.setOnShareTargetSelectedListener(this);
672        }
673    }
674
675    @Override
676    public boolean onOptionsItemSelected(MenuItem item) {
677        switch (item.getItemId()) {
678            case R.id.undoButton: {
679                mPanelController.resetParameters();
680                HistoryAdapter adapter = mMasterImage.getHistory();
681                int position = adapter.undo();
682                mMasterImage.onHistoryItemClick(position);
683                mImageShow.showToast("Undo");
684                invalidateViews();
685                return true;
686            }
687            case R.id.redoButton: {
688                HistoryAdapter adapter = mMasterImage.getHistory();
689                int position = adapter.redo();
690                mMasterImage.onHistoryItemClick(position);
691                mImageShow.showToast("Redo");
692                invalidateViews();
693                return true;
694            }
695            case R.id.resetHistoryButton: {
696                resetHistory();
697                return true;
698            }
699            case R.id.showImageStateButton: {
700                toggleImageStatePanel();
701                return true;
702            }
703            case android.R.id.home: {
704                saveImage();
705                return true;
706            }
707        }
708        return false;
709    }
710
711    public void enableSave(boolean enable) {
712        if (mSaveButton != null)
713            mSaveButton.setEnabled(enable);
714    }
715
716    public FilterIconButton setupFilterRepresentationButton(FilterRepresentation representation, LinearLayout panel, View button) {
717        LayoutInflater inflater =
718                (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
719        FilterIconButton icon = (FilterIconButton) inflater.inflate(R.layout.filtericonbutton,
720                panel, false);
721        if (representation.getTextId() != 0) {
722            representation.setScrName(getString(representation.getTextId()));
723        }
724        String text = representation.getName();
725        icon.setup(text, mPanelController, panel);
726        icon.setFilterRepresentation(representation);
727        icon.setId(representation.getEditorId());
728        mPanelController.addComponent(button, icon);
729        panel.addView(icon);
730        return icon;
731    }
732
733    private void fillFx(LinearLayout listFilters, int buttonId) {
734        // TODO: use listview
735        // TODO: load the filters straight from the filesystem
736
737        FilterFxRepresentation[] fxArray = new FilterFxRepresentation[18];
738        int p = 0;
739
740        int[] drawid = {
741                R.drawable.filtershow_fx_0005_punch,
742//                R.drawable.filtershow_fx_0000_vintage,
743//                R.drawable.filtershow_fx_0004_bw_contrast,
744                R.drawable.filtershow_fx_0002_bleach,
745                R.drawable.filtershow_fx_0001_instant,
746                R.drawable.filtershow_fx_0007_washout,
747                R.drawable.filtershow_fx_0003_blue_crush,
748                R.drawable.filtershow_fx_0008_washout_color,
749                R.drawable.filtershow_fx_0006_x_process
750        };
751
752        int[] fxNameid = {
753                R.string.ffx_punch,
754//                R.string.ffx_vintage,
755//                R.string.ffx_bw_contrast,
756                R.string.ffx_bleach,
757                R.string.ffx_instant,
758                R.string.ffx_washout,
759                R.string.ffx_blue_crush,
760                R.string.ffx_washout_color,
761                R.string.ffx_x_process
762        };
763        FiltersManager filtersManager = FiltersManager.getManager();
764
765        for (int i = 0; i < drawid.length; i++) {
766            FilterFxRepresentation fx = new FilterFxRepresentation(getString(fxNameid[i]), drawid[i], fxNameid[i]);
767            filtersManager.addRepresentation(fx);
768            fxArray[p++] = fx;
769        }
770
771        ImageButton button = (ImageButton) findViewById(buttonId);
772
773        FilterFxRepresentation nullFx = new FilterFxRepresentation(getString(R.string.none), 0, R.string.none);
774        mNullFxFilter = setupFilterRepresentationButton(nullFx, listFilters, button);
775        mNullFxFilter.setSelected(true);
776
777        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
778        FiltersManager.getManager().addLooks(this, filtersRepresentations);
779        for (FilterRepresentation representation : filtersRepresentations) {
780            setupFilterRepresentationButton(representation, listFilters, button);
781        }
782
783    }
784
785    public void setDefaultPreset() {
786        // Default preset (original)
787        ImagePreset preset = new ImagePreset(getString(R.string.history_original)); // empty
788        preset.setImageLoader(mImageLoader);
789
790        mMasterImage.setPreset(preset, true);
791    }
792
793    // //////////////////////////////////////////////////////////////////////////////
794    // Some utility functions
795    // TODO: finish the cleanup.
796
797    public void invalidateViews() {
798        for (ImageShow views : mImageViews) {
799            views.invalidate();
800            views.updateImage();
801        }
802    }
803
804    public void hideImageViews() {
805        for (View view : mImageViews) {
806            view.setVisibility(View.GONE);
807        }
808        mEditorPlaceHolder.hide();
809    }
810
811    // //////////////////////////////////////////////////////////////////////////////
812    // imageState panel...
813
814    private void toggleImageStatePanel() {
815        invalidateOptionsMenu();
816    }
817
818    private void loadImageStatePanel() {
819        StatePanel statePanel = new StatePanel();
820        if (findViewById(R.id.state_panel_container) != null) {
821            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
822            transaction.replace(R.id.state_panel_container, statePanel, mPanelFragmentTag);
823            transaction.commit();
824        }
825    }
826
827    @Override
828    public void onConfigurationChanged(Configuration newConfig)
829    {
830        super.onConfigurationChanged(newConfig);
831        setDefaultValues();
832        loadXML();
833        if (getResources().getConfiguration().orientation
834                == Configuration.ORIENTATION_LANDSCAPE) {
835            mShowingImageStatePanel = true;
836        } else if (mShowingImageStatePanel) {
837            toggleImageStatePanel();
838        }
839        if (mShowingImageStatePanel) {
840            loadImageStatePanel();
841        }
842        if (mShowingTinyPlanet == false) {
843            View tinyPlanetView = findViewById(EditorTinyPlanet.ID);
844            if (tinyPlanetView != null) {
845                tinyPlanetView.setVisibility(View.GONE);
846            }
847        }
848        final View loading = findViewById(R.id.loading);
849        loading.setVisibility(View.GONE);
850    }
851
852    public void setupMasterImage() {
853        mImageLoader = new ImageLoader(this, getApplicationContext());
854
855        HistoryAdapter mHistoryAdapter = new HistoryAdapter(
856                this, R.layout.filtershow_history_operation_row,
857                R.id.rowTextView);
858
859        StateAdapter mImageStateAdapter = new StateAdapter(this, 0);
860        MasterImage.reset();
861        mMasterImage = MasterImage.getImage();
862        mMasterImage.setHistoryAdapter(mHistoryAdapter);
863        mMasterImage.setStateAdapter(mImageStateAdapter);
864        mMasterImage.setActivity(this);
865        mMasterImage.setImageLoader(mImageLoader);
866
867        if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
868            mMasterImage.setSupportsHighRes(true);
869        } else {
870            mMasterImage.setSupportsHighRes(false);
871        }
872    }
873
874    public void dispatchNullFilterClick() {
875        mNullFxFilter.onClick(mNullFxFilter);
876        mNullBorderFilter.onClick(mNullBorderFilter);
877    }
878
879    void resetHistory() {
880        dispatchNullFilterClick();
881        HistoryAdapter adapter = mMasterImage.getHistory();
882        adapter.reset();
883        ImagePreset original = new ImagePreset(adapter.getItem(0));
884        mMasterImage.setPreset(original, true);
885        mPanelController.resetParameters();
886        invalidateViews();
887    }
888
889    @Override
890    public void onBackPressed() {
891        if (mPanelController.onBackPressed()) {
892            if (detectSpecialExitCases()) {
893                saveImage();
894            } else if(!mImageShow.hasModifications()) {
895                done();
896            } else {
897                AlertDialog.Builder builder = new AlertDialog.Builder(this);
898                builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
899                builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
900                    public void onClick(DialogInterface dialog, int id) {
901                        saveImage();
902                    }
903                });
904                builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
905                    public void onClick(DialogInterface dialog, int id) {
906                        done();
907                    }
908                });
909                builder.show();
910            }
911        }
912    }
913
914    public PanelController getPanelController() {
915        return mPanelController;
916    }
917
918    public void cannotLoadImage() {
919        CharSequence text = getString(R.string.cannot_load_image);
920        Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
921        toast.show();
922        finish();
923    }
924
925    // //////////////////////////////////////////////////////////////////////////////
926
927    public float getPixelsFromDip(float value) {
928        Resources r = getResources();
929        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
930                r.getDisplayMetrics());
931    }
932
933    @Override
934    public void onItemClick(AdapterView<?> parent, View view, int position,
935            long id) {
936        mMasterImage.onHistoryItemClick(position);
937        invalidateViews();
938    }
939
940    public void pickImage() {
941        Intent intent = new Intent();
942        intent.setType("image/*");
943        intent.setAction(Intent.ACTION_GET_CONTENT);
944        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
945                SELECT_PICTURE);
946    }
947
948    @Override
949    public void onActivityResult(int requestCode, int resultCode, Intent data) {
950        if (resultCode == RESULT_OK) {
951            if (requestCode == SELECT_PICTURE) {
952                Uri selectedImageUri = data.getData();
953                startLoadBitmap(selectedImageUri);
954            }
955        }
956    }
957
958    private boolean mSaveToExtraUri = false;
959    private boolean mSaveAsWallpaper = false;
960    private boolean mReturnAsExtra = false;
961    private boolean mOutputted = false;
962
963    public void saveImage() {
964        handleSpecialExitCases();
965        if (!mOutputted) {
966            if (mImageShow.hasModifications()) {
967                // Get the name of the album, to which the image will be saved
968                File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri());
969                int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
970                String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
971                showSavingProgress(albumName);
972                mImageShow.saveImage(this, null);
973            } else {
974                done();
975            }
976        }
977    }
978
979    public boolean detectSpecialExitCases() {
980        return mCropExtras != null && (mCropExtras.getExtraOutput() != null
981                || mCropExtras.getSetAsWallpaper() || mCropExtras.getReturnData());
982    }
983
984    public void handleSpecialExitCases() {
985        if (mCropExtras != null) {
986            if (mCropExtras.getExtraOutput() != null) {
987                mSaveToExtraUri = true;
988                mOutputted = true;
989            }
990            if (mCropExtras.getSetAsWallpaper()) {
991                mSaveAsWallpaper = true;
992                mOutputted = true;
993            }
994            if (mCropExtras.getReturnData()) {
995                mReturnAsExtra = true;
996                mOutputted = true;
997            }
998            if (mOutputted) {
999                mImageShow.getImagePreset().mGeoData.setUseCropExtrasFlag(true);
1000                showSavingProgress(null);
1001                mImageShow.returnFilteredResult(this);
1002            }
1003        }
1004    }
1005
1006    public void onFilteredResult(Bitmap filtered) {
1007        Intent intent = new Intent();
1008        intent.putExtra(CropExtras.KEY_CROPPED_RECT, mImageShow.getImageCropBounds());
1009        if (mSaveToExtraUri) {
1010            mImageShow.saveToUri(filtered, mCropExtras.getExtraOutput(),
1011                    mCropExtras.getOutputFormat(), this);
1012        }
1013        if (mSaveAsWallpaper) {
1014            setWallpaperInBackground(filtered);
1015        }
1016        if (mReturnAsExtra) {
1017            if (filtered != null) {
1018                int bmapSize = filtered.getRowBytes() * filtered.getHeight();
1019                /*
1020                 * Max size of Binder transaction buffer is 1Mb, so constrain
1021                 * Bitmap to be somewhat less than this, otherwise we get
1022                 * TransactionTooLargeExceptions.
1023                 */
1024                if (bmapSize > MAX_BMAP_IN_INTENT) {
1025                    Log.w(LOGTAG, "Bitmap too large to be returned via intent");
1026                } else {
1027                    intent.putExtra(CropExtras.KEY_DATA, filtered);
1028                }
1029            }
1030        }
1031        setResult(RESULT_OK, intent);
1032        if (!mSaveToExtraUri) {
1033            done();
1034        }
1035    }
1036
1037    void setWallpaperInBackground(final Bitmap bmap) {
1038        Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
1039        BitmapTask.Callbacks<FilterShowActivity> cb = new BitmapTask.Callbacks<FilterShowActivity>() {
1040            @Override
1041            public void onComplete(Bitmap result) {}
1042
1043            @Override
1044            public void onCancel() {}
1045
1046            @Override
1047            public Bitmap onExecute(FilterShowActivity param) {
1048                try {
1049                    WallpaperManager.getInstance(param).setBitmap(bmap);
1050                } catch (IOException e) {
1051                    Log.w(LOGTAG, "fail to set wall paper", e);
1052                }
1053                return null;
1054            }
1055        };
1056        (new BitmapTask<FilterShowActivity>(cb)).execute(this);
1057    }
1058
1059    public void done() {
1060        if (mOutputted) {
1061            hideSavingProgress();
1062        }
1063        finish();
1064    }
1065
1066    static {
1067        System.loadLibrary("jni_filtershow_filters");
1068    }
1069
1070    private void extractXMPData() {
1071        XMresults res = XmpPresets.extractXMPData(
1072                getBaseContext(), mMasterImage, getIntent().getData());
1073        if (res == null)
1074            return;
1075
1076        mOriginalImageUri = res.originalimage;
1077        mOriginalPreset = res.preset;
1078    }
1079}
1080