2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.filtershow;
19 import android.app.ActionBar;
20 import android.app.AlertDialog;
21 import android.app.ProgressDialog;
22 import android.content.ComponentName;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.content.pm.ActivityInfo;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.Color;
33 import android.graphics.Matrix;
34 import android.graphics.Point;
35 import android.graphics.Rect;
36 import android.graphics.RectF;
37 import android.graphics.drawable.ColorDrawable;
38 import android.graphics.drawable.Drawable;
39 import android.net.Uri;
40 import android.os.AsyncTask;
41 import android.os.Bundle;
42 import android.os.CancellationSignal;
43 import android.os.Handler;
44 import android.os.IBinder;
45 import android.support.v4.app.DialogFragment;
46 import android.support.v4.app.Fragment;
47 import android.support.v4.app.FragmentActivity;
48 import android.support.v4.app.FragmentTransaction;
49 import android.util.DisplayMetrics;
50 import android.util.Log;
51 import android.util.TypedValue;
52 import android.view.Menu;
53 import android.view.MenuItem;
54 import android.view.MotionEvent;
55 import android.view.View;
56 import android.view.View.OnClickListener;
57 import android.view.ViewPropertyAnimator;
58 import android.view.WindowManager;
59 import android.widget.AdapterView;
60 import android.widget.AdapterView.OnItemClickListener;
61 import android.widget.FrameLayout;
62 import android.widget.ShareActionProvider;
63 import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
64 import android.widget.Spinner;
65 import android.widget.Toast;
67 import com.android.gallery3d.R;
68 import com.android.gallery3d.app.PhotoPage;
69 import com.android.gallery3d.data.LocalAlbum;
70 import com.android.gallery3d.filtershow.cache.ImageLoader;
71 import com.android.gallery3d.filtershow.category.Action;
72 import com.android.gallery3d.filtershow.category.CategoryAdapter;
73 import com.android.gallery3d.filtershow.category.CategorySelected;
74 import com.android.gallery3d.filtershow.category.CategoryView;
75 import com.android.gallery3d.filtershow.category.MainPanel;
76 import com.android.gallery3d.filtershow.category.SwipableView;
77 import com.android.gallery3d.filtershow.data.UserPresetsManager;
78 import com.android.gallery3d.filtershow.editors.BasicEditor;
79 import com.android.gallery3d.filtershow.editors.Editor;
80 import com.android.gallery3d.filtershow.editors.EditorChanSat;
81 import com.android.gallery3d.filtershow.editors.EditorColorBorder;
82 import com.android.gallery3d.filtershow.editors.EditorCrop;
83 import com.android.gallery3d.filtershow.editors.EditorDraw;
84 import com.android.gallery3d.filtershow.editors.EditorGrad;
85 import com.android.gallery3d.filtershow.editors.EditorManager;
86 import com.android.gallery3d.filtershow.editors.EditorMirror;
87 import com.android.gallery3d.filtershow.editors.EditorPanel;
88 import com.android.gallery3d.filtershow.editors.EditorRedEye;
89 import com.android.gallery3d.filtershow.editors.EditorRotate;
90 import com.android.gallery3d.filtershow.editors.EditorStraighten;
91 import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
92 import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
93 import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation;
94 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
95 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
96 import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
97 import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
98 import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
99 import com.android.gallery3d.filtershow.filters.FiltersManager;
100 import com.android.gallery3d.filtershow.filters.ImageFilter;
101 import com.android.gallery3d.filtershow.history.HistoryItem;
102 import com.android.gallery3d.filtershow.history.HistoryManager;
103 import com.android.gallery3d.filtershow.imageshow.ImageShow;
104 import com.android.gallery3d.filtershow.imageshow.MasterImage;
105 import com.android.gallery3d.filtershow.imageshow.Spline;
106 import com.android.gallery3d.filtershow.info.InfoPanel;
107 import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
108 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
109 import com.android.gallery3d.filtershow.pipeline.ProcessingService;
110 import com.android.gallery3d.filtershow.presets.PresetManagementDialog;
111 import com.android.gallery3d.filtershow.presets.UserPresetsAdapter;
112 import com.android.gallery3d.filtershow.provider.SharedImageProvider;
113 import com.android.gallery3d.filtershow.state.StateAdapter;
114 import com.android.gallery3d.filtershow.tools.SaveImage;
115 import com.android.gallery3d.filtershow.tools.XmpPresets;
116 import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
117 import com.android.gallery3d.filtershow.ui.ExportDialog;
118 import com.android.gallery3d.filtershow.ui.FramedTextButton;
119 import com.android.gallery3d.util.GalleryUtils;
120 import com.android.gallery3d.util.PrintJob;
121 import com.android.photos.data.GalleryBitmapPool;
124 import java.io.FileDescriptor;
125 import java.io.FileOutputStream;
126 import java.lang.ref.WeakReference;
127 import java.util.ArrayList;
128 import java.util.Vector;
130 public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
131 OnShareTargetSelectedListener, DialogInterface.OnShowListener,
132 DialogInterface.OnDismissListener{
134 private String mAction = "";
135 MasterImage mMasterImage = null;
137 private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
139 public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
140 public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
141 private ImageShow mImageShow = null;
143 private View mSaveButton = null;
145 private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
146 private Editor mCurrentEditor = null;
148 private static final int SELECT_PICTURE = 1;
149 private static final String LOGTAG = "FilterShowActivity";
151 private boolean mShowingTinyPlanet = false;
152 private boolean mShowingImageStatePanel = false;
153 private boolean mShowingVersionsPanel = false;
155 private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
157 private ShareActionProvider mShareActionProvider;
158 private File mSharedOutputFile = null;
160 private boolean mSharingImage = false;
162 private WeakReference<ProgressDialog> mSavingProgressDialog;
164 private LoadBitmapTask mLoadBitmapTask;
166 private Uri mOriginalImageUri = null;
167 private ImagePreset mOriginalPreset = null;
169 private Uri mSelectedImageUri = null;
171 private ArrayList<Action> mActions = new ArrayList<Action>();
172 private UserPresetsManager mUserPresetsManager = null;
173 private UserPresetsAdapter mUserPresetsAdapter = null;
174 private CategoryAdapter mCategoryLooksAdapter = null;
175 private CategoryAdapter mCategoryBordersAdapter = null;
176 private CategoryAdapter mCategoryGeometryAdapter = null;
177 private CategoryAdapter mCategoryFiltersAdapter = null;
178 private CategoryAdapter mCategoryVersionsAdapter = null;
179 private int mCurrentPanel = MainPanel.LOOKS;
180 private Vector<FilterUserPresetRepresentation> mVersions =
181 new Vector<FilterUserPresetRepresentation>();
182 private int mVersionsCounter = 0;
184 private boolean mHandlingSwipeButton = false;
185 private View mHandledSwipeView = null;
186 private float mHandledSwipeViewLastDelta = 0;
187 private float mSwipeStartX = 0;
188 private float mSwipeStartY = 0;
190 private ProcessingService mBoundService;
191 private boolean mIsBound = false;
193 private DialogInterface mCurrentDialog = null;
194 private boolean mLoadingVisible = true;
196 public ProcessingService getProcessingService() {
197 return mBoundService;
200 public boolean isSimpleEditAction() {
201 return !PhotoPage.ACTION_NEXTGEN_EDIT.equalsIgnoreCase(mAction);
204 private ServiceConnection mConnection = new ServiceConnection() {
206 public void onServiceConnected(ComponentName className, IBinder service) {
208 * This is called when the connection with the service has been
209 * established, giving us the service object we can use to
210 * interact with the service. Because we have bound to a explicit
211 * service that we know is running in our own process, we can
212 * cast its IBinder to a concrete class and directly access it.
214 mBoundService = ((ProcessingService.LocalBinder)service).getService();
215 mBoundService.setFiltershowActivity(FilterShowActivity.this);
216 mBoundService.onStart();
220 public void onServiceDisconnected(ComponentName className) {
222 * This is called when the connection with the service has been
223 * unexpectedly disconnected -- that is, its process crashed.
224 * Because it is running in our same process, we should never
227 mBoundService = null;
231 void doBindService() {
233 * Establish a connection with the service. We use an explicit
234 * class name because we want a specific service implementation that
235 * we know will be running in our own process (and thus won't be
236 * supporting component replacement by other applications).
238 bindService(new Intent(FilterShowActivity.this, ProcessingService.class),
239 mConnection, Context.BIND_AUTO_CREATE);
243 void doUnbindService() {
245 // Detach our existing connection.
246 unbindService(mConnection);
251 public void updateUIAfterServiceStarted() {
252 MasterImage.setMaster(mMasterImage);
253 ImageFilter.setActivityForMemoryToasts(this);
254 mUserPresetsManager = new UserPresetsManager(this);
255 mUserPresetsAdapter = new UserPresetsAdapter(this);
261 getWindow().setBackgroundDrawable(new ColorDrawable(0));
271 public void onCreate(Bundle savedInstanceState) {
272 super.onCreate(savedInstanceState);
274 boolean onlyUsePortrait = getResources().getBoolean(R.bool.only_use_portrait);
275 if (onlyUsePortrait) {
276 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
279 clearGalleryBitmapPool();
281 getWindow().setBackgroundDrawable(new ColorDrawable(Color.GRAY));
282 setContentView(R.layout.filtershow_splashscreen);
285 public boolean isShowingImageStatePanel() {
286 return mShowingImageStatePanel;
289 public void loadMainPanel() {
290 if (findViewById(R.id.main_panel_container) == null) {
293 MainPanel panel = new MainPanel();
294 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
295 transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
296 transaction.commit();
299 public void loadEditorPanel(FilterRepresentation representation,
300 final Editor currentEditor) {
301 if (representation.getEditorId() == ImageOnlyEditor.ID) {
302 currentEditor.reflectCurrentFilter();
305 final int currentId = currentEditor.getID();
306 Runnable showEditor = new Runnable() {
309 EditorPanel panel = new EditorPanel();
310 panel.setEditor(currentId);
311 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
312 transaction.remove(getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG));
313 transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
314 transaction.commit();
317 Fragment main = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
318 boolean doAnimation = false;
319 if (mShowingImageStatePanel
320 && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
323 if (doAnimation && main != null && main instanceof MainPanel) {
324 MainPanel mainPanel = (MainPanel) main;
325 View container = mainPanel.getView().findViewById(R.id.category_panel_container);
326 View bottom = mainPanel.getView().findViewById(R.id.bottom_panel);
327 int panelHeight = container.getHeight() + bottom.getHeight();
328 ViewPropertyAnimator anim = mainPanel.getView().animate();
329 anim.translationY(panelHeight).start();
330 final Handler handler = new Handler();
331 handler.postDelayed(showEditor, anim.getDuration());
337 public void toggleInformationPanel() {
338 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
339 transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
341 InfoPanel panel = new InfoPanel();
342 panel.show(transaction, InfoPanel.FRAGMENT_TAG);
345 private void loadXML() {
346 setContentView(R.layout.filtershow_activity);
348 ActionBar actionBar = getActionBar();
349 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
350 actionBar.setCustomView(R.layout.filtershow_actionbar);
351 actionBar.setBackgroundDrawable(new ColorDrawable(
352 getResources().getColor(R.color.background_screen)));
354 mSaveButton = actionBar.getCustomView();
355 mSaveButton.setOnClickListener(new OnClickListener() {
357 public void onClick(View view) {
362 mImageShow = (ImageShow) findViewById(R.id.imageShow);
363 mImageViews.add(mImageShow);
367 mEditorPlaceHolder.hide();
373 public void fillCategories() {
382 public void setupStatePanel() {
383 MasterImage.getImage().setHistoryManager(mMasterImage.getHistory());
386 private void fillVersions() {
387 if (mCategoryVersionsAdapter != null) {
388 mCategoryVersionsAdapter.clear();
390 mCategoryVersionsAdapter = new CategoryAdapter(this);
391 mCategoryVersionsAdapter.setShowAddButton(true);
394 public void registerAction(Action action) {
395 if (mActions.contains(action)) {
398 mActions.add(action);
401 private void loadActions() {
402 for (int i = 0; i < mActions.size(); i++) {
403 Action action = mActions.get(i);
404 action.setImageFrame(new Rect(0, 0, 96, 96), 0);
408 public void updateVersions() {
409 mCategoryVersionsAdapter.clear();
410 FilterUserPresetRepresentation originalRep = new FilterUserPresetRepresentation(
411 getString(R.string.filtershow_version_original), new ImagePreset(), -1);
412 mCategoryVersionsAdapter.add(
413 new Action(this, originalRep, Action.FULL_VIEW));
414 ImagePreset current = new ImagePreset(MasterImage.getImage().getPreset());
415 FilterUserPresetRepresentation currentRep = new FilterUserPresetRepresentation(
416 getString(R.string.filtershow_version_current), current, -1);
417 mCategoryVersionsAdapter.add(
418 new Action(this, currentRep, Action.FULL_VIEW));
419 if (mVersions.size() > 0) {
420 mCategoryVersionsAdapter.add(new Action(this, Action.SPACER));
422 for (FilterUserPresetRepresentation rep : mVersions) {
423 mCategoryVersionsAdapter.add(
424 new Action(this, rep, Action.FULL_VIEW, true));
426 mCategoryVersionsAdapter.notifyDataSetInvalidated();
429 public void addCurrentVersion() {
430 ImagePreset current = new ImagePreset(MasterImage.getImage().getPreset());
432 FilterUserPresetRepresentation rep = new FilterUserPresetRepresentation(
433 "" + mVersionsCounter, current, -1);
438 public void removeVersion(Action action) {
439 mVersions.remove(action.getRepresentation());
443 public void removeLook(Action action) {
444 FilterUserPresetRepresentation rep =
445 (FilterUserPresetRepresentation) action.getRepresentation();
449 mUserPresetsManager.delete(rep.getId());
450 updateUserPresetsFromManager();
453 private void fillEffects() {
454 FiltersManager filtersManager = FiltersManager.getManager();
455 ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getEffects();
456 if (mCategoryFiltersAdapter != null) {
457 mCategoryFiltersAdapter.clear();
459 mCategoryFiltersAdapter = new CategoryAdapter(this);
460 for (FilterRepresentation representation : filtersRepresentations) {
461 if (representation.getTextId() != 0) {
462 representation.setName(getString(representation.getTextId()));
464 mCategoryFiltersAdapter.add(new Action(this, representation));
468 private void fillTools() {
469 FiltersManager filtersManager = FiltersManager.getManager();
470 ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getTools();
471 if (mCategoryGeometryAdapter != null) {
472 mCategoryGeometryAdapter.clear();
474 mCategoryGeometryAdapter = new CategoryAdapter(this);
475 boolean found = false;
476 for (FilterRepresentation representation : filtersRepresentations) {
477 mCategoryGeometryAdapter.add(new Action(this, representation));
478 if (representation instanceof FilterDrawRepresentation) {
483 FilterRepresentation representation = new FilterDrawRepresentation();
484 Action action = new Action(this, representation);
485 action.setIsDoubleAction(true);
486 mCategoryGeometryAdapter.add(action);
490 private void processIntent() {
491 Intent intent = getIntent();
492 if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
493 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
496 mAction = intent.getAction();
497 mSelectedImageUri = intent.getData();
498 Uri loadUri = mSelectedImageUri;
499 if (mOriginalImageUri != null) {
500 loadUri = mOriginalImageUri;
502 if (loadUri != null) {
503 startLoadBitmap(loadUri);
509 private void setupEditors() {
510 mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
511 EditorManager.addEditors(mEditorPlaceHolder);
512 mEditorPlaceHolder.setOldViews(mImageViews);
515 private void fillEditors() {
516 mEditorPlaceHolder.addEditor(new EditorChanSat());
517 mEditorPlaceHolder.addEditor(new EditorGrad());
518 mEditorPlaceHolder.addEditor(new EditorDraw());
519 mEditorPlaceHolder.addEditor(new EditorColorBorder());
520 mEditorPlaceHolder.addEditor(new BasicEditor());
521 mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
522 mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
523 mEditorPlaceHolder.addEditor(new EditorRedEye());
524 mEditorPlaceHolder.addEditor(new EditorCrop());
525 mEditorPlaceHolder.addEditor(new EditorMirror());
526 mEditorPlaceHolder.addEditor(new EditorRotate());
527 mEditorPlaceHolder.addEditor(new EditorStraighten());
530 private void setDefaultValues() {
531 Resources res = getResources();
533 // TODO: get those values from XML.
534 FramedTextButton.setTextSize((int) getPixelsFromDip(14));
535 FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
536 FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
538 Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
539 int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
540 Spline.setCurveHandle(curveHandle, curveHandleSize);
541 Spline.setCurveWidth((int) getPixelsFromDip(3));
543 mOriginalImageUri = null;
546 private void startLoadBitmap(Uri uri) {
547 final View imageShow = findViewById(R.id.imageShow);
548 imageShow.setVisibility(View.INVISIBLE);
549 startLoadingIndicator();
550 mShowingTinyPlanet = false;
551 mLoadBitmapTask = new LoadBitmapTask();
552 mLoadBitmapTask.execute(uri);
555 private void fillBorders() {
556 FiltersManager filtersManager = FiltersManager.getManager();
557 ArrayList<FilterRepresentation> borders = filtersManager.getBorders();
559 for (int i = 0; i < borders.size(); i++) {
560 FilterRepresentation filter = borders.get(i);
561 filter.setName(getString(R.string.borders));
563 filter.setName(getString(R.string.none));
567 if (mCategoryBordersAdapter != null) {
568 mCategoryBordersAdapter.clear();
570 mCategoryBordersAdapter = new CategoryAdapter(this);
571 for (FilterRepresentation representation : borders) {
572 if (representation.getTextId() != 0) {
573 representation.setName(getString(representation.getTextId()));
575 mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
579 public UserPresetsAdapter getUserPresetsAdapter() {
580 return mUserPresetsAdapter;
583 public CategoryAdapter getCategoryLooksAdapter() {
584 return mCategoryLooksAdapter;
587 public CategoryAdapter getCategoryBordersAdapter() {
588 return mCategoryBordersAdapter;
591 public CategoryAdapter getCategoryGeometryAdapter() {
592 return mCategoryGeometryAdapter;
595 public CategoryAdapter getCategoryFiltersAdapter() {
596 return mCategoryFiltersAdapter;
599 public CategoryAdapter getCategoryVersionsAdapter() {
600 return mCategoryVersionsAdapter;
603 public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
604 if (filterRepresentation == null) {
607 ImagePreset oldPreset = MasterImage.getImage().getPreset();
608 ImagePreset copy = new ImagePreset(oldPreset);
609 copy.removeFilter(filterRepresentation);
610 MasterImage.getImage().setPreset(copy, copy.getLastRepresentation(), true);
611 if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
612 FilterRepresentation lastRepresentation = copy.getLastRepresentation();
613 MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
617 public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
618 if (filterRepresentation == null) {
621 if (!(filterRepresentation instanceof FilterRotateRepresentation)
622 && !(filterRepresentation instanceof FilterMirrorRepresentation)
623 && MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
626 if (filterRepresentation instanceof FilterUserPresetRepresentation
627 || filterRepresentation instanceof FilterRotateRepresentation
628 || filterRepresentation instanceof FilterMirrorRepresentation) {
629 MasterImage.getImage().onNewLook(filterRepresentation);
631 ImagePreset oldPreset = MasterImage.getImage().getPreset();
632 ImagePreset copy = new ImagePreset(oldPreset);
633 FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
634 if (representation == null) {
635 filterRepresentation = filterRepresentation.copy();
636 copy.addFilter(filterRepresentation);
638 if (filterRepresentation.allowsSingleInstanceOnly()) {
639 // Don't just update the filter representation. Centralize the
640 // logic in the addFilter(), such that we can keep "None" as
642 if (!representation.equals(filterRepresentation)) {
643 // Only do this if the filter isn't the same
644 // (state panel clicks can lead us here)
645 copy.removeFilter(representation);
646 copy.addFilter(filterRepresentation);
650 MasterImage.getImage().setPreset(copy, filterRepresentation, true);
651 MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
654 public void showRepresentation(FilterRepresentation representation) {
655 if (representation == null) {
659 if (representation instanceof FilterRotateRepresentation) {
660 FilterRotateRepresentation r = (FilterRotateRepresentation) representation;
663 if (representation instanceof FilterMirrorRepresentation) {
664 FilterMirrorRepresentation r = (FilterMirrorRepresentation) representation;
667 if (representation.isBooleanFilter()) {
668 ImagePreset preset = MasterImage.getImage().getPreset();
669 if (preset.getRepresentation(representation) != null) {
671 ImagePreset copy = new ImagePreset(preset);
672 copy.removeFilter(representation);
673 FilterRepresentation filterRepresentation = representation.copy();
674 MasterImage.getImage().setPreset(copy, filterRepresentation, true);
675 MasterImage.getImage().setCurrentFilterRepresentation(null);
679 useFilterRepresentation(representation);
681 // show representation
682 if (mCurrentEditor != null) {
683 mCurrentEditor.detach();
685 mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
686 loadEditorPanel(representation, mCurrentEditor);
689 public Editor getEditor(int editorID) {
690 return mEditorPlaceHolder.getEditor(editorID);
693 public void setCurrentPanel(int currentPanel) {
694 mCurrentPanel = currentPanel;
697 public int getCurrentPanel() {
698 return mCurrentPanel;
701 public void updateCategories() {
702 ImagePreset preset = mMasterImage.getPreset();
703 mCategoryLooksAdapter.reflectImagePreset(preset);
704 mCategoryBordersAdapter.reflectImagePreset(preset);
707 public View getMainStatePanelContainer(int id) {
708 return findViewById(id);
712 public void onShow(DialogInterface dialog) {
713 mCurrentDialog = dialog;
717 public void onDismiss(DialogInterface dialogInterface) {
718 mCurrentDialog = null;
721 private class LoadHighresBitmapTask extends AsyncTask<Void, Void, Boolean> {
723 protected Boolean doInBackground(Void... params) {
724 MasterImage master = MasterImage.getImage();
725 Rect originalBounds = master.getOriginalBounds();
726 if (master.supportsHighRes()) {
727 int highresPreviewSize = master.getOriginalBitmapLarge().getWidth() * 2;
728 if (highresPreviewSize > originalBounds.width()) {
729 highresPreviewSize = originalBounds.width();
731 Rect bounds = new Rect();
732 Bitmap originalHires = ImageLoader.loadOrientedConstrainedBitmap(master.getUri(),
733 master.getActivity(), highresPreviewSize,
734 master.getOrientation(), bounds);
735 master.setOriginalBounds(bounds);
736 master.setOriginalBitmapHighres(originalHires);
737 mBoundService.setOriginalBitmapHighres(originalHires);
738 master.warnListeners();
744 protected void onPostExecute(Boolean result) {
745 Bitmap highresBitmap = MasterImage.getImage().getOriginalBitmapHighres();
746 if (highresBitmap != null) {
747 float highResPreviewScale = (float) highresBitmap.getWidth()
748 / (float) MasterImage.getImage().getOriginalBounds().width();
749 mBoundService.setHighresPreviewScaleFactor(highResPreviewScale);
751 MasterImage.getImage().warnListeners();
755 public boolean isLoadingVisible() {
756 return mLoadingVisible;
759 public void startLoadingIndicator() {
760 final View loading = findViewById(R.id.loading);
761 mLoadingVisible = true;
762 loading.setVisibility(View.VISIBLE);
765 public void stopLoadingIndicator() {
766 final View loading = findViewById(R.id.loading);
767 loading.setVisibility(View.GONE);
768 mLoadingVisible = false;
771 private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
774 public LoadBitmapTask() {
775 mBitmapSize = getScreenImageSize();
779 protected Boolean doInBackground(Uri... params) {
780 if (!MasterImage.getImage().loadBitmap(params[0], mBitmapSize)) {
783 publishProgress(ImageLoader.queryLightCycle360(MasterImage.getImage().getActivity()));
788 protected void onProgressUpdate(Boolean... values) {
789 super.onProgressUpdate(values);
794 mShowingTinyPlanet = true;
799 protected void onPostExecute(Boolean result) {
800 MasterImage.setMaster(mMasterImage);
806 if (mOriginalImageUri != null
807 && !mOriginalImageUri.equals(mSelectedImageUri)) {
808 mOriginalImageUri = mSelectedImageUri;
809 mOriginalPreset = null;
810 Toast.makeText(FilterShowActivity.this,
811 R.string.cannot_edit_original, Toast.LENGTH_SHORT).show();
812 startLoadBitmap(mOriginalImageUri);
819 if (null == CachingPipeline.getRenderScriptContext()){
820 Log.v(LOGTAG,"RenderScript context destroyed during load");
823 final View imageShow = findViewById(R.id.imageShow);
824 imageShow.setVisibility(View.VISIBLE);
827 Bitmap largeBitmap = MasterImage.getImage().getOriginalBitmapLarge();
828 mBoundService.setOriginalBitmap(largeBitmap);
830 float previewScale = (float) largeBitmap.getWidth()
831 / (float) MasterImage.getImage().getOriginalBounds().width();
832 mBoundService.setPreviewScaleFactor(previewScale);
833 if (!mShowingTinyPlanet) {
834 mCategoryFiltersAdapter.removeTinyPlanet();
836 mCategoryLooksAdapter.imageLoaded();
837 mCategoryBordersAdapter.imageLoaded();
838 mCategoryGeometryAdapter.imageLoaded();
839 mCategoryFiltersAdapter.imageLoaded();
840 mLoadBitmapTask = null;
842 MasterImage.getImage().warnListeners();
845 if (mOriginalPreset != null) {
846 MasterImage.getImage().setLoadedPreset(mOriginalPreset);
847 MasterImage.getImage().setPreset(mOriginalPreset,
848 mOriginalPreset.getLastRepresentation(), true);
849 mOriginalPreset = null;
854 MasterImage.getImage().resetGeometryImages(true);
856 if (mAction == TINY_PLANET_ACTION) {
857 showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
859 LoadHighresBitmapTask highresLoad = new LoadHighresBitmapTask();
860 highresLoad.execute();
861 MasterImage.getImage().warnListeners();
862 super.onPostExecute(result);
867 private void clearGalleryBitmapPool() {
868 (new AsyncTask<Void, Void, Void>() {
870 protected Void doInBackground(Void... params) {
871 // Free memory held in Gallery's Bitmap pool. May be O(n) for n bitmaps.
872 GalleryBitmapPool.getInstance().clear();
879 protected void onDestroy() {
880 if (mLoadBitmapTask != null) {
881 mLoadBitmapTask.cancel(false);
883 mUserPresetsManager.close();
888 // TODO: find a more robust way of handling image size selection
889 // for high screen densities.
890 private int getScreenImageSize() {
891 DisplayMetrics outMetrics = new DisplayMetrics();
892 getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
893 return Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
896 private void showSavingProgress(String albumName) {
897 ProgressDialog progress;
898 if (mSavingProgressDialog != null) {
899 progress = mSavingProgressDialog.get();
900 if (progress != null) {
905 // TODO: Allow cancellation of the saving process
907 if (albumName == null) {
908 progressText = getString(R.string.saving_image);
910 progressText = getString(R.string.filtershow_saving_image, albumName);
912 progress = ProgressDialog.show(this, "", progressText, true, false);
913 mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
916 private void hideSavingProgress() {
917 if (mSavingProgressDialog != null) {
918 ProgressDialog progress = mSavingProgressDialog.get();
919 if (progress != null)
924 public void completeSaveImage(Uri saveUri) {
925 if (mSharingImage && mSharedOutputFile != null) {
926 // Image saved, we unblock the content provider
927 Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
928 Uri.encode(mSharedOutputFile.getAbsolutePath()));
929 ContentValues values = new ContentValues();
930 values.put(SharedImageProvider.PREPARE, false);
931 getContentResolver().insert(uri, values);
933 setResult(RESULT_OK, new Intent().setData(saveUri));
934 hideSavingProgress();
939 public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
940 // First, let's tell the SharedImageProvider that it will need to wait
942 Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
943 Uri.encode(mSharedOutputFile.getAbsolutePath()));
944 ContentValues values = new ContentValues();
945 values.put(SharedImageProvider.PREPARE, true);
946 getContentResolver().insert(uri, values);
947 mSharingImage = true;
949 // Process and save the image in the background.
950 showSavingProgress(null);
951 mImageShow.saveImage(this, mSharedOutputFile);
955 private Intent getDefaultShareIntent() {
956 Intent intent = new Intent(Intent.ACTION_SEND);
957 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
958 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
959 intent.setType(SharedImageProvider.MIME_TYPE);
960 mSharedOutputFile = SaveImage.getNewFile(this, MasterImage.getImage().getUri());
961 Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
962 Uri.encode(mSharedOutputFile.getAbsolutePath()));
963 intent.putExtra(Intent.EXTRA_STREAM, uri);
968 public boolean onCreateOptionsMenu(Menu menu) {
969 getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
970 MenuItem showState = menu.findItem(R.id.showImageStateButton);
971 if (mShowingImageStatePanel) {
972 showState.setTitle(R.string.hide_imagestate_panel);
974 showState.setTitle(R.string.show_imagestate_panel);
976 mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
977 .getActionProvider();
978 mShareActionProvider.setShareIntent(getDefaultShareIntent());
979 mShareActionProvider.setOnShareTargetSelectedListener(this);
985 private void setupMenu(){
986 if (mMenu == null || mMasterImage == null) {
989 MenuItem undoItem = mMenu.findItem(R.id.undoButton);
990 MenuItem redoItem = mMenu.findItem(R.id.redoButton);
991 MenuItem resetItem = mMenu.findItem(R.id.resetHistoryButton);
992 mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
996 public void onPause() {
998 if (mShareActionProvider != null) {
999 mShareActionProvider.setOnShareTargetSelectedListener(null);
1004 public void onResume() {
1006 if (mShareActionProvider != null) {
1007 mShareActionProvider.setOnShareTargetSelectedListener(this);
1012 public boolean onOptionsItemSelected(MenuItem item) {
1013 switch (item.getItemId()) {
1014 case R.id.undoButton: {
1015 HistoryManager adapter = mMasterImage.getHistory();
1016 int position = adapter.undo();
1017 mMasterImage.onHistoryItemClick(position);
1022 case R.id.redoButton: {
1023 HistoryManager adapter = mMasterImage.getHistory();
1024 int position = adapter.redo();
1025 mMasterImage.onHistoryItemClick(position);
1029 case R.id.resetHistoryButton: {
1033 case R.id.showImageStateButton: {
1034 toggleImageStatePanel();
1037 case R.id.exportFlattenButton: {
1038 showExportOptionsDialog();
1041 case android.R.id.home: {
1045 case R.id.manageUserPresets: {
1046 manageUserPresets();
1049 case R.id.showInfoPanel: {
1050 toggleInformationPanel();
1053 case R.id.printButton: {
1061 public void print() {
1062 Bitmap bitmap = MasterImage.getImage().getHighresImage();
1063 PrintJob.printBitmap(this, "ImagePrint", bitmap);
1066 public void addNewPreset() {
1067 DialogFragment dialog = new PresetManagementDialog();
1068 dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
1071 private void manageUserPresets() {
1072 DialogFragment dialog = new PresetManagementDialog();
1073 dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
1076 private void showExportOptionsDialog() {
1077 DialogFragment dialog = new ExportDialog();
1078 dialog.show(getSupportFragmentManager(), "ExportDialogFragment");
1081 public void updateUserPresetsFromAdapter(UserPresetsAdapter adapter) {
1082 ArrayList<FilterUserPresetRepresentation> representations =
1083 adapter.getDeletedRepresentations();
1084 for (FilterUserPresetRepresentation representation : representations) {
1085 deletePreset(representation.getId());
1087 ArrayList<FilterUserPresetRepresentation> changedRepresentations =
1088 adapter.getChangedRepresentations();
1089 for (FilterUserPresetRepresentation representation : changedRepresentations) {
1090 updatePreset(representation);
1092 adapter.clearDeletedRepresentations();
1093 adapter.clearChangedRepresentations();
1097 public void loadUserPresets() {
1098 mUserPresetsManager.load();
1099 updateUserPresetsFromManager();
1102 public void updateUserPresetsFromManager() {
1103 ArrayList<FilterUserPresetRepresentation> presets = mUserPresetsManager.getRepresentations();
1104 if (presets == null) {
1107 if (mCategoryLooksAdapter != null) {
1110 if (presets.size() > 0) {
1111 mCategoryLooksAdapter.add(new Action(this, Action.SPACER));
1113 mUserPresetsAdapter.clear();
1114 for (int i = 0; i < presets.size(); i++) {
1115 FilterUserPresetRepresentation representation = presets.get(i);
1116 mCategoryLooksAdapter.add(
1117 new Action(this, representation, Action.FULL_VIEW, true));
1118 mUserPresetsAdapter.add(new Action(this, representation, Action.FULL_VIEW));
1120 if (presets.size() > 0) {
1121 mCategoryLooksAdapter.add(new Action(this, Action.ADD_ACTION));
1123 mCategoryLooksAdapter.notifyDataSetChanged();
1124 mCategoryLooksAdapter.notifyDataSetInvalidated();
1127 public void saveCurrentImagePreset(String name) {
1128 mUserPresetsManager.save(MasterImage.getImage().getPreset(), name);
1131 private void deletePreset(int id) {
1132 mUserPresetsManager.delete(id);
1135 private void updatePreset(FilterUserPresetRepresentation representation) {
1136 mUserPresetsManager.update(representation);
1139 public void enableSave(boolean enable) {
1140 if (mSaveButton != null) {
1141 mSaveButton.setEnabled(enable);
1145 private void fillLooks() {
1146 FiltersManager filtersManager = FiltersManager.getManager();
1147 ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getLooks();
1149 if (mCategoryLooksAdapter != null) {
1150 mCategoryLooksAdapter.clear();
1152 mCategoryLooksAdapter = new CategoryAdapter(this);
1153 int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
1154 mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
1155 for (FilterRepresentation representation : filtersRepresentations) {
1156 mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
1158 if (mUserPresetsManager.getRepresentations() == null
1159 || mUserPresetsManager.getRepresentations().size() == 0) {
1160 mCategoryLooksAdapter.add(new Action(this, Action.ADD_ACTION));
1163 Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1164 if (panel != null) {
1165 if (panel instanceof MainPanel) {
1166 MainPanel mainPanel = (MainPanel) panel;
1167 mainPanel.loadCategoryLookPanel(true);
1172 public void setDefaultPreset() {
1173 // Default preset (original)
1174 ImagePreset preset = new ImagePreset(); // empty
1175 mMasterImage.setPreset(preset, preset.getLastRepresentation(), true);
1178 // //////////////////////////////////////////////////////////////////////////////
1179 // Some utility functions
1180 // TODO: finish the cleanup.
1182 public void invalidateViews() {
1183 for (ImageShow views : mImageViews) {
1184 views.updateImage();
1188 public void hideImageViews() {
1189 for (View view : mImageViews) {
1190 view.setVisibility(View.GONE);
1192 mEditorPlaceHolder.hide();
1195 // //////////////////////////////////////////////////////////////////////////////
1196 // imageState panel...
1198 public void toggleImageStatePanel() {
1199 invalidateOptionsMenu();
1200 mShowingImageStatePanel = !mShowingImageStatePanel;
1201 Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1202 if (panel != null) {
1203 if (panel instanceof EditorPanel) {
1204 EditorPanel editorPanel = (EditorPanel) panel;
1205 editorPanel.showImageStatePanel(mShowingImageStatePanel);
1206 } else if (panel instanceof MainPanel) {
1207 MainPanel mainPanel = (MainPanel) panel;
1208 mainPanel.showImageStatePanel(mShowingImageStatePanel);
1213 public void toggleVersionsPanel() {
1214 mShowingVersionsPanel = !mShowingVersionsPanel;
1215 Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1216 if (panel != null && panel instanceof MainPanel) {
1217 MainPanel mainPanel = (MainPanel) panel;
1218 mainPanel.loadCategoryVersionsPanel();
1223 public void onConfigurationChanged(Configuration newConfig)
1225 super.onConfigurationChanged(newConfig);
1228 if (mMasterImage == null) {
1235 if (mCurrentDialog != null) {
1236 mCurrentDialog.dismiss();
1237 mCurrentDialog = null;
1239 // mLoadBitmapTask==null implies you have looked at the intent
1240 if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
1241 mCategoryFiltersAdapter.removeTinyPlanet();
1243 stopLoadingIndicator();
1246 public void setupMasterImage() {
1248 HistoryManager historyManager = new HistoryManager();
1249 StateAdapter imageStateAdapter = new StateAdapter(this, 0);
1250 MasterImage.reset();
1251 mMasterImage = MasterImage.getImage();
1252 mMasterImage.setHistoryManager(historyManager);
1253 mMasterImage.setStateAdapter(imageStateAdapter);
1254 mMasterImage.setActivity(this);
1256 if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
1257 mMasterImage.setSupportsHighRes(true);
1259 mMasterImage.setSupportsHighRes(false);
1263 void resetHistory() {
1264 HistoryManager adapter = mMasterImage.getHistory();
1266 HistoryItem historyItem = adapter.getItem(0);
1267 ImagePreset original = new ImagePreset(historyItem.getImagePreset());
1268 mMasterImage.setPreset(original, historyItem.getFilterRepresentation(), true);
1273 public void showDefaultImageView() {
1274 mEditorPlaceHolder.hide();
1275 mImageShow.setVisibility(View.VISIBLE);
1276 MasterImage.getImage().setCurrentFilter(null);
1277 MasterImage.getImage().setCurrentFilterRepresentation(null);
1280 public void backToMain() {
1281 Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1282 if (currentPanel instanceof MainPanel) {
1286 showDefaultImageView();
1290 public void onBackPressed() {
1291 Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1292 if (currentPanel instanceof MainPanel) {
1293 if (!mImageShow.hasModifications()) {
1296 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1297 builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
1298 builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
1300 public void onClick(DialogInterface dialog, int id) {
1304 builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
1306 public void onClick(DialogInterface dialog, int id) {
1317 public void cannotLoadImage() {
1318 Toast.makeText(this, R.string.cannot_load_image, Toast.LENGTH_SHORT).show();
1322 // //////////////////////////////////////////////////////////////////////////////
1324 public float getPixelsFromDip(float value) {
1325 Resources r = getResources();
1326 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
1327 r.getDisplayMetrics());
1331 public void onItemClick(AdapterView<?> parent, View view, int position,
1333 mMasterImage.onHistoryItemClick(position);
1337 public void pickImage() {
1338 Intent intent = new Intent();
1339 intent.setType("image/*");
1340 intent.setAction(Intent.ACTION_GET_CONTENT);
1341 startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
1346 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1347 if (resultCode == RESULT_OK) {
1348 if (requestCode == SELECT_PICTURE) {
1349 Uri selectedImageUri = data.getData();
1350 startLoadBitmap(selectedImageUri);
1356 public void saveImage() {
1357 if (mImageShow.hasModifications()) {
1358 // Get the name of the album, to which the image will be saved
1359 File saveDir = SaveImage.getFinalSaveDirectory(this, mSelectedImageUri);
1360 int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
1361 String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
1362 showSavingProgress(albumName);
1363 mImageShow.saveImage(this, null);
1370 public void done() {
1371 hideSavingProgress();
1372 if (mLoadBitmapTask != null) {
1373 mLoadBitmapTask.cancel(false);
1378 private void extractXMPData() {
1379 XMresults res = XmpPresets.extractXMPData(
1380 getBaseContext(), mMasterImage, getIntent().getData());
1384 mOriginalImageUri = res.originalimage;
1385 mOriginalPreset = res.preset;
1388 public Uri getSelectedImageUri() {
1389 return mSelectedImageUri;
1392 public void setHandlesSwipeForView(View view, float startX, float startY) {
1394 mHandlingSwipeButton = true;
1396 mHandlingSwipeButton = false;
1398 mHandledSwipeView = view;
1399 int[] location = new int[2];
1400 view.getLocationInWindow(location);
1401 mSwipeStartX = location[0] + startX;
1402 mSwipeStartY = location[1] + startY;
1405 public boolean dispatchTouchEvent (MotionEvent ev) {
1406 if (mHandlingSwipeButton) {
1407 int direction = CategoryView.HORIZONTAL;
1408 if (mHandledSwipeView instanceof CategoryView) {
1409 direction = ((CategoryView) mHandledSwipeView).getOrientation();
1411 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
1412 float delta = ev.getY() - mSwipeStartY;
1413 float distance = mHandledSwipeView.getHeight();
1414 if (direction == CategoryView.VERTICAL) {
1415 delta = ev.getX() - mSwipeStartX;
1416 mHandledSwipeView.setTranslationX(delta);
1417 distance = mHandledSwipeView.getWidth();
1419 mHandledSwipeView.setTranslationY(delta);
1421 delta = Math.abs(delta);
1422 float transparency = Math.min(1, delta / distance);
1423 mHandledSwipeView.setAlpha(1.f - transparency);
1424 mHandledSwipeViewLastDelta = delta;
1426 if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1427 || ev.getActionMasked() == MotionEvent.ACTION_UP) {
1428 mHandledSwipeView.setTranslationX(0);
1429 mHandledSwipeView.setTranslationY(0);
1430 mHandledSwipeView.setAlpha(1.f);
1431 mHandlingSwipeButton = false;
1432 float distance = mHandledSwipeView.getHeight();
1433 if (direction == CategoryView.VERTICAL) {
1434 distance = mHandledSwipeView.getWidth();
1436 if (mHandledSwipeViewLastDelta > distance) {
1437 ((SwipableView) mHandledSwipeView).delete();
1442 return super.dispatchTouchEvent(ev);
1445 public Point mHintTouchPoint = new Point();
1447 public Point hintTouchPoint(View view) {
1448 int location[] = new int[2];
1449 view.getLocationOnScreen(location);
1450 int x = mHintTouchPoint.x - location[0];
1451 int y = mHintTouchPoint.y - location[1];
1452 return new Point(x, y);
1455 public void startTouchAnimation(View target, float x, float y) {
1456 final CategorySelected hint =
1457 (CategorySelected) findViewById(R.id.categorySelectedIndicator);
1458 int location[] = new int[2];
1459 target.getLocationOnScreen(location);
1460 mHintTouchPoint.x = (int) (location[0] + x);
1461 mHintTouchPoint.y = (int) (location[1] + y);
1462 int locationHint[] = new int[2];
1463 ((View)hint.getParent()).getLocationOnScreen(locationHint);
1464 int dx = (int) (x - (hint.getWidth())/2);
1465 int dy = (int) (y - (hint.getHeight())/2);
1466 hint.setTranslationX(location[0] - locationHint[0] + dx);
1467 hint.setTranslationY(location[1] - locationHint[1] + dy);
1468 hint.setVisibility(View.VISIBLE);
1469 hint.animate().scaleX(2).scaleY(2).alpha(0).withEndAction(new Runnable() {
1472 hint.setVisibility(View.INVISIBLE);