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