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