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