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.FilterMirrorRepresentation;
94 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
95 import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
96 import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
97 import com.android.gallery3d.filtershow.filters.FiltersManager;
98 import com.android.gallery3d.filtershow.filters.ImageFilter;
99 import com.android.gallery3d.filtershow.history.HistoryItem;
100 import com.android.gallery3d.filtershow.history.HistoryManager;
101 import com.android.gallery3d.filtershow.imageshow.ImageShow;
102 import com.android.gallery3d.filtershow.imageshow.MasterImage;
103 import com.android.gallery3d.filtershow.imageshow.Spline;
104 import com.android.gallery3d.filtershow.info.InfoPanel;
105 import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
106 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
107 import com.android.gallery3d.filtershow.pipeline.ProcessingService;
108 import com.android.gallery3d.filtershow.presets.PresetManagementDialog;
109 import com.android.gallery3d.filtershow.presets.UserPresetsAdapter;
110 import com.android.gallery3d.filtershow.provider.SharedImageProvider;
111 import com.android.gallery3d.filtershow.state.StateAdapter;
112 import com.android.gallery3d.filtershow.tools.SaveImage;
113 import com.android.gallery3d.filtershow.tools.XmpPresets;
114 import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
115 import com.android.gallery3d.filtershow.ui.ExportDialog;
116 import com.android.gallery3d.filtershow.ui.FramedTextButton;
117 import com.android.gallery3d.util.GalleryUtils;
118 import com.android.gallery3d.util.PrintJob;
119 import com.android.gallery3d.util.UsageStatistics;
120 import com.android.photos.data.GalleryBitmapPool;
123 import java.io.FileDescriptor;
124 import java.io.FileOutputStream;
125 import java.lang.ref.WeakReference;
126 import java.util.ArrayList;
127 import java.util.Vector;
129 public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
130 OnShareTargetSelectedListener {
132 private String mAction = "";
133 MasterImage mMasterImage = null;
135 private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
137 public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
138 public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
139 private ImageShow mImageShow = null;
141 private View mSaveButton = null;
143 private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
144 private Editor mCurrentEditor = null;
146 private static final int SELECT_PICTURE = 1;
147 private static final String LOGTAG = "FilterShowActivity";
149 private boolean mShowingTinyPlanet = false;
150 private boolean mShowingImageStatePanel = false;
151 private boolean mShowingVersionsPanel = false;
153 private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
155 private ShareActionProvider mShareActionProvider;
156 private File mSharedOutputFile = null;
158 private boolean mSharingImage = false;
160 private WeakReference<ProgressDialog> mSavingProgressDialog;
162 private LoadBitmapTask mLoadBitmapTask;
164 private Uri mOriginalImageUri = null;
165 private ImagePreset mOriginalPreset = null;
167 private Uri mSelectedImageUri = null;
169 private ArrayList<Action> mActions = new ArrayList<Action>();
170 private UserPresetsManager mUserPresetsManager = null;
171 private UserPresetsAdapter mUserPresetsAdapter = null;
172 private CategoryAdapter mCategoryLooksAdapter = null;
173 private CategoryAdapter mCategoryBordersAdapter = null;
174 private CategoryAdapter mCategoryGeometryAdapter = null;
175 private CategoryAdapter mCategoryFiltersAdapter = null;
176 private CategoryAdapter mCategoryVersionsAdapter = null;
177 private int mCurrentPanel = MainPanel.LOOKS;
178 private Vector<FilterUserPresetRepresentation> mVersions =
179 new Vector<FilterUserPresetRepresentation>();
180 private int mVersionsCounter = 0;
182 private boolean mHandlingSwipeButton = false;
183 private View mHandledSwipeView = null;
184 private float mHandledSwipeViewLastDelta = 0;
185 private float mSwipeStartX = 0;
186 private float mSwipeStartY = 0;
188 private ProcessingService mBoundService;
189 private boolean mIsBound = false;
192 public ProcessingService getProcessingService() {
193 return mBoundService;
196 public boolean isSimpleEditAction() {
197 return !PhotoPage.ACTION_NEXTGEN_EDIT.equalsIgnoreCase(mAction);
200 private ServiceConnection mConnection = new ServiceConnection() {
202 public void onServiceConnected(ComponentName className, IBinder service) {
204 * This is called when the connection with the service has been
205 * established, giving us the service object we can use to
206 * interact with the service. Because we have bound to a explicit
207 * service that we know is running in our own process, we can
208 * cast its IBinder to a concrete class and directly access it.
210 mBoundService = ((ProcessingService.LocalBinder)service).getService();
211 mBoundService.setFiltershowActivity(FilterShowActivity.this);
212 mBoundService.onStart();
216 public void onServiceDisconnected(ComponentName className) {
218 * This is called when the connection with the service has been
219 * unexpectedly disconnected -- that is, its process crashed.
220 * Because it is running in our same process, we should never
223 mBoundService = null;
227 void doBindService() {
229 * Establish a connection with the service. We use an explicit
230 * class name because we want a specific service implementation that
231 * we know will be running in our own process (and thus won't be
232 * supporting component replacement by other applications).
234 bindService(new Intent(FilterShowActivity.this, ProcessingService.class),
235 mConnection, Context.BIND_AUTO_CREATE);
239 void doUnbindService() {
241 // Detach our existing connection.
242 unbindService(mConnection);
247 public void updateUIAfterServiceStarted() {
248 MasterImage.setMaster(mMasterImage);
249 ImageFilter.setActivityForMemoryToasts(this);
250 mUserPresetsManager = new UserPresetsManager(this);
251 mUserPresetsAdapter = new UserPresetsAdapter(this);
257 getWindow().setBackgroundDrawable(new ColorDrawable(0));
267 public void onCreate(Bundle savedInstanceState) {
268 super.onCreate(savedInstanceState);
270 boolean onlyUsePortrait = getResources().getBoolean(R.bool.only_use_portrait);
271 if (onlyUsePortrait) {
272 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
275 clearGalleryBitmapPool();
277 getWindow().setBackgroundDrawable(new ColorDrawable(Color.GRAY));
278 setContentView(R.layout.filtershow_splashscreen);
279 UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_EDITOR, "Main");
280 UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
281 UsageStatistics.CATEGORY_LIFECYCLE, UsageStatistics.LIFECYCLE_START);
284 public boolean isShowingImageStatePanel() {
285 return mShowingImageStatePanel;
288 public void loadMainPanel() {
289 if (findViewById(R.id.main_panel_container) == null) {
292 MainPanel panel = new MainPanel();
293 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
294 transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
295 transaction.commit();
298 public void loadEditorPanel(FilterRepresentation representation,
299 final Editor currentEditor) {
300 if (representation.getEditorId() == ImageOnlyEditor.ID) {
301 currentEditor.reflectCurrentFilter();
304 final int currentId = currentEditor.getID();
305 Runnable showEditor = new Runnable() {
308 EditorPanel panel = new EditorPanel();
309 panel.setEditor(currentId);
310 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
311 transaction.remove(getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG));
312 transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
313 transaction.commit();
316 Fragment main = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
317 boolean doAnimation = false;
318 if (mShowingImageStatePanel
319 && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
322 if (doAnimation && main != null && main instanceof MainPanel) {
323 MainPanel mainPanel = (MainPanel) main;
324 View container = mainPanel.getView().findViewById(R.id.category_panel_container);
325 View bottom = mainPanel.getView().findViewById(R.id.bottom_panel);
326 int panelHeight = container.getHeight() + bottom.getHeight();
327 ViewPropertyAnimator anim = mainPanel.getView().animate();
328 anim.translationY(panelHeight).start();
329 final Handler handler = new Handler();
330 handler.postDelayed(showEditor, anim.getDuration());
336 public void toggleInformationPanel() {
337 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
338 transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
340 InfoPanel panel = new InfoPanel();
341 panel.show(transaction, InfoPanel.FRAGMENT_TAG);
344 private void loadXML() {
345 setContentView(R.layout.filtershow_activity);
347 ActionBar actionBar = getActionBar();
348 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
349 actionBar.setCustomView(R.layout.filtershow_actionbar);
350 actionBar.setBackgroundDrawable(new ColorDrawable(
351 getResources().getColor(R.color.background_screen)));
353 mSaveButton = actionBar.getCustomView();
354 mSaveButton.setOnClickListener(new OnClickListener() {
356 public void onClick(View view) {
361 mImageShow = (ImageShow) findViewById(R.id.imageShow);
362 mImageViews.add(mImageShow);
366 mEditorPlaceHolder.hide();
372 public void fillCategories() {
381 public void setupStatePanel() {
382 MasterImage.getImage().setHistoryManager(mMasterImage.getHistory());
385 private void fillVersions() {
386 if (mCategoryVersionsAdapter != null) {
387 mCategoryVersionsAdapter.clear();
389 mCategoryVersionsAdapter = new CategoryAdapter(this);
390 mCategoryVersionsAdapter.setShowAddButton(true);
393 public void registerAction(Action action) {
394 if (mActions.contains(action)) {
397 mActions.add(action);
400 private void loadActions() {
401 for (int i = 0; i < mActions.size(); i++) {
402 Action action = mActions.get(i);
403 action.setImageFrame(new Rect(0, 0, 96, 96), 0);
407 public void updateVersions() {
408 mCategoryVersionsAdapter.clear();
409 FilterUserPresetRepresentation originalRep = new FilterUserPresetRepresentation(
410 getString(R.string.filtershow_version_original), new ImagePreset(), -1);
411 mCategoryVersionsAdapter.add(
412 new Action(this, originalRep, Action.FULL_VIEW));
413 ImagePreset current = new ImagePreset(MasterImage.getImage().getPreset());
414 FilterUserPresetRepresentation currentRep = new FilterUserPresetRepresentation(
415 getString(R.string.filtershow_version_current), current, -1);
416 mCategoryVersionsAdapter.add(
417 new Action(this, currentRep, Action.FULL_VIEW));
418 if (mVersions.size() > 0) {
419 mCategoryVersionsAdapter.add(new Action(this, Action.SPACER));
421 for (FilterUserPresetRepresentation rep : mVersions) {
422 mCategoryVersionsAdapter.add(
423 new Action(this, rep, Action.FULL_VIEW, true));
425 mCategoryVersionsAdapter.notifyDataSetInvalidated();
428 public void addCurrentVersion() {
429 ImagePreset current = new ImagePreset(MasterImage.getImage().getPreset());
431 FilterUserPresetRepresentation rep = new FilterUserPresetRepresentation(
432 "" + mVersionsCounter, current, -1);
437 public void removeVersion(Action action) {
438 mVersions.remove(action.getRepresentation());
442 public void removeLook(Action action) {
443 FilterUserPresetRepresentation rep =
444 (FilterUserPresetRepresentation) action.getRepresentation();
448 mUserPresetsManager.delete(rep.getId());
449 updateUserPresetsFromManager();
452 private void fillEffects() {
453 FiltersManager filtersManager = FiltersManager.getManager();
454 ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getEffects();
455 if (mCategoryFiltersAdapter != null) {
456 mCategoryFiltersAdapter.clear();
458 mCategoryFiltersAdapter = new CategoryAdapter(this);
459 for (FilterRepresentation representation : filtersRepresentations) {
460 if (representation.getTextId() != 0) {
461 representation.setName(getString(representation.getTextId()));
463 mCategoryFiltersAdapter.add(new Action(this, representation));
467 private void fillTools() {
468 FiltersManager filtersManager = FiltersManager.getManager();
469 ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getTools();
470 if (mCategoryGeometryAdapter != null) {
471 mCategoryGeometryAdapter.clear();
473 mCategoryGeometryAdapter = new CategoryAdapter(this);
474 for (FilterRepresentation representation : filtersRepresentations) {
475 mCategoryGeometryAdapter.add(new Action(this, representation));
479 private void processIntent() {
480 Intent intent = getIntent();
481 if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
482 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
485 mAction = intent.getAction();
486 mSelectedImageUri = intent.getData();
487 Uri loadUri = mSelectedImageUri;
488 if (mOriginalImageUri != null) {
489 loadUri = mOriginalImageUri;
491 if (loadUri != null) {
492 startLoadBitmap(loadUri);
498 private void setupEditors() {
499 mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
500 EditorManager.addEditors(mEditorPlaceHolder);
501 mEditorPlaceHolder.setOldViews(mImageViews);
504 private void fillEditors() {
505 mEditorPlaceHolder.addEditor(new EditorChanSat());
506 mEditorPlaceHolder.addEditor(new EditorGrad());
507 mEditorPlaceHolder.addEditor(new EditorDraw());
508 mEditorPlaceHolder.addEditor(new EditorColorBorder());
509 mEditorPlaceHolder.addEditor(new BasicEditor());
510 mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
511 mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
512 mEditorPlaceHolder.addEditor(new EditorRedEye());
513 mEditorPlaceHolder.addEditor(new EditorCrop());
514 mEditorPlaceHolder.addEditor(new EditorMirror());
515 mEditorPlaceHolder.addEditor(new EditorRotate());
516 mEditorPlaceHolder.addEditor(new EditorStraighten());
519 private void setDefaultValues() {
520 Resources res = getResources();
522 // TODO: get those values from XML.
523 FramedTextButton.setTextSize((int) getPixelsFromDip(14));
524 FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
525 FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
527 Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
528 int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
529 Spline.setCurveHandle(curveHandle, curveHandleSize);
530 Spline.setCurveWidth((int) getPixelsFromDip(3));
532 mOriginalImageUri = null;
535 private void startLoadBitmap(Uri uri) {
536 final View loading = findViewById(R.id.loading);
537 final View imageShow = findViewById(R.id.imageShow);
538 imageShow.setVisibility(View.INVISIBLE);
539 loading.setVisibility(View.VISIBLE);
540 mShowingTinyPlanet = false;
541 mLoadBitmapTask = new LoadBitmapTask();
542 mLoadBitmapTask.execute(uri);
545 private void fillBorders() {
546 FiltersManager filtersManager = FiltersManager.getManager();
547 ArrayList<FilterRepresentation> borders = filtersManager.getBorders();
549 for (int i = 0; i < borders.size(); i++) {
550 FilterRepresentation filter = borders.get(i);
551 filter.setName(getString(R.string.borders));
553 filter.setName(getString(R.string.none));
557 if (mCategoryBordersAdapter != null) {
558 mCategoryBordersAdapter.clear();
560 mCategoryBordersAdapter = new CategoryAdapter(this);
561 for (FilterRepresentation representation : borders) {
562 if (representation.getTextId() != 0) {
563 representation.setName(getString(representation.getTextId()));
565 mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
569 public UserPresetsAdapter getUserPresetsAdapter() {
570 return mUserPresetsAdapter;
573 public CategoryAdapter getCategoryLooksAdapter() {
574 return mCategoryLooksAdapter;
577 public CategoryAdapter getCategoryBordersAdapter() {
578 return mCategoryBordersAdapter;
581 public CategoryAdapter getCategoryGeometryAdapter() {
582 return mCategoryGeometryAdapter;
585 public CategoryAdapter getCategoryFiltersAdapter() {
586 return mCategoryFiltersAdapter;
589 public CategoryAdapter getCategoryVersionsAdapter() {
590 return mCategoryVersionsAdapter;
593 public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
594 if (filterRepresentation == null) {
597 ImagePreset oldPreset = MasterImage.getImage().getPreset();
598 ImagePreset copy = new ImagePreset(oldPreset);
599 copy.removeFilter(filterRepresentation);
600 MasterImage.getImage().setPreset(copy, copy.getLastRepresentation(), true);
601 if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
602 FilterRepresentation lastRepresentation = copy.getLastRepresentation();
603 MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
607 public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
608 if (filterRepresentation == null) {
611 if (!(filterRepresentation instanceof FilterRotateRepresentation)
612 && !(filterRepresentation instanceof FilterMirrorRepresentation)
613 && MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
616 if (filterRepresentation instanceof FilterUserPresetRepresentation
617 || filterRepresentation instanceof FilterRotateRepresentation
618 || filterRepresentation instanceof FilterMirrorRepresentation) {
619 MasterImage.getImage().onNewLook(filterRepresentation);
621 ImagePreset oldPreset = MasterImage.getImage().getPreset();
622 ImagePreset copy = new ImagePreset(oldPreset);
623 FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
624 if (representation == null) {
625 filterRepresentation = filterRepresentation.copy();
626 copy.addFilter(filterRepresentation);
628 if (filterRepresentation.allowsSingleInstanceOnly()) {
629 // Don't just update the filter representation. Centralize the
630 // logic in the addFilter(), such that we can keep "None" as
632 if (!representation.equals(filterRepresentation)) {
633 // Only do this if the filter isn't the same
634 // (state panel clicks can lead us here)
635 copy.removeFilter(representation);
636 copy.addFilter(filterRepresentation);
640 MasterImage.getImage().setPreset(copy, filterRepresentation, true);
641 MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
644 public void showRepresentation(FilterRepresentation representation) {
645 if (representation == null) {
649 if (representation instanceof FilterRotateRepresentation) {
650 FilterRotateRepresentation r = (FilterRotateRepresentation) representation;
653 if (representation instanceof FilterMirrorRepresentation) {
654 FilterMirrorRepresentation r = (FilterMirrorRepresentation) representation;
657 useFilterRepresentation(representation);
659 // show representation
660 if (mCurrentEditor != null) {
661 mCurrentEditor.detach();
663 mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
664 loadEditorPanel(representation, mCurrentEditor);
667 public Editor getEditor(int editorID) {
668 return mEditorPlaceHolder.getEditor(editorID);
671 public void setCurrentPanel(int currentPanel) {
672 mCurrentPanel = currentPanel;
675 public int getCurrentPanel() {
676 return mCurrentPanel;
679 public void updateCategories() {
680 ImagePreset preset = mMasterImage.getPreset();
681 mCategoryLooksAdapter.reflectImagePreset(preset);
682 mCategoryBordersAdapter.reflectImagePreset(preset);
685 public View getMainStatePanelContainer(int id) {
686 return findViewById(id);
689 private class LoadHighresBitmapTask extends AsyncTask<Void, Void, Boolean> {
691 protected Boolean doInBackground(Void... params) {
692 MasterImage master = MasterImage.getImage();
693 Rect originalBounds = master.getOriginalBounds();
694 if (master.supportsHighRes()) {
695 int highresPreviewSize = master.getOriginalBitmapLarge().getWidth() * 2;
696 if (highresPreviewSize > originalBounds.width()) {
697 highresPreviewSize = originalBounds.width();
699 Rect bounds = new Rect();
700 Bitmap originalHires = ImageLoader.loadOrientedConstrainedBitmap(master.getUri(),
701 master.getActivity(), highresPreviewSize,
702 master.getOrientation(), bounds);
703 master.setOriginalBounds(bounds);
704 master.setOriginalBitmapHighres(originalHires);
705 mBoundService.setOriginalBitmapHighres(originalHires);
706 master.warnListeners();
712 protected void onPostExecute(Boolean result) {
713 Bitmap highresBitmap = MasterImage.getImage().getOriginalBitmapHighres();
714 if (highresBitmap != null) {
715 float highResPreviewScale = (float) highresBitmap.getWidth()
716 / (float) MasterImage.getImage().getOriginalBounds().width();
717 mBoundService.setHighresPreviewScaleFactor(highResPreviewScale);
719 MasterImage.getImage().warnListeners();
723 public void stopLoadingIndicator() {
724 final View loading = findViewById(R.id.loading);
725 loading.setVisibility(View.GONE);
728 private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
731 public LoadBitmapTask() {
732 mBitmapSize = getScreenImageSize();
736 protected Boolean doInBackground(Uri... params) {
737 if (!MasterImage.getImage().loadBitmap(params[0], mBitmapSize)) {
740 publishProgress(ImageLoader.queryLightCycle360(MasterImage.getImage().getActivity()));
745 protected void onProgressUpdate(Boolean... values) {
746 super.onProgressUpdate(values);
751 mShowingTinyPlanet = true;
756 protected void onPostExecute(Boolean result) {
757 MasterImage.setMaster(mMasterImage);
763 if (mOriginalImageUri != null
764 && !mOriginalImageUri.equals(mSelectedImageUri)) {
765 mOriginalImageUri = mSelectedImageUri;
766 mOriginalPreset = null;
767 Toast.makeText(FilterShowActivity.this,
768 R.string.cannot_edit_original, Toast.LENGTH_SHORT).show();
769 startLoadBitmap(mOriginalImageUri);
776 if (null == CachingPipeline.getRenderScriptContext()){
777 Log.v(LOGTAG,"RenderScript context destroyed during load");
780 final View imageShow = findViewById(R.id.imageShow);
781 imageShow.setVisibility(View.VISIBLE);
784 Bitmap largeBitmap = MasterImage.getImage().getOriginalBitmapLarge();
785 mBoundService.setOriginalBitmap(largeBitmap);
787 float previewScale = (float) largeBitmap.getWidth()
788 / (float) MasterImage.getImage().getOriginalBounds().width();
789 mBoundService.setPreviewScaleFactor(previewScale);
790 if (!mShowingTinyPlanet) {
791 mCategoryFiltersAdapter.removeTinyPlanet();
793 mCategoryLooksAdapter.imageLoaded();
794 mCategoryBordersAdapter.imageLoaded();
795 mCategoryGeometryAdapter.imageLoaded();
796 mCategoryFiltersAdapter.imageLoaded();
797 mLoadBitmapTask = null;
799 MasterImage.getImage().warnListeners();
802 if (mOriginalPreset != null) {
803 MasterImage.getImage().setLoadedPreset(mOriginalPreset);
804 MasterImage.getImage().setPreset(mOriginalPreset,
805 mOriginalPreset.getLastRepresentation(), true);
806 mOriginalPreset = null;
811 MasterImage.getImage().resetGeometryImages(true);
813 if (mAction == TINY_PLANET_ACTION) {
814 showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
816 LoadHighresBitmapTask highresLoad = new LoadHighresBitmapTask();
817 highresLoad.execute();
818 MasterImage.getImage().warnListeners();
819 super.onPostExecute(result);
824 private void clearGalleryBitmapPool() {
825 (new AsyncTask<Void, Void, Void>() {
827 protected Void doInBackground(Void... params) {
828 // Free memory held in Gallery's Bitmap pool. May be O(n) for n bitmaps.
829 GalleryBitmapPool.getInstance().clear();
836 protected void onDestroy() {
837 if (mLoadBitmapTask != null) {
838 mLoadBitmapTask.cancel(false);
840 mUserPresetsManager.close();
845 // TODO: find a more robust way of handling image size selection
846 // for high screen densities.
847 private int getScreenImageSize() {
848 DisplayMetrics outMetrics = new DisplayMetrics();
849 getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
850 return Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
853 private void showSavingProgress(String albumName) {
854 ProgressDialog progress;
855 if (mSavingProgressDialog != null) {
856 progress = mSavingProgressDialog.get();
857 if (progress != null) {
862 // TODO: Allow cancellation of the saving process
864 if (albumName == null) {
865 progressText = getString(R.string.saving_image);
867 progressText = getString(R.string.filtershow_saving_image, albumName);
869 progress = ProgressDialog.show(this, "", progressText, true, false);
870 mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
873 private void hideSavingProgress() {
874 if (mSavingProgressDialog != null) {
875 ProgressDialog progress = mSavingProgressDialog.get();
876 if (progress != null)
881 public void completeSaveImage(Uri saveUri) {
882 if (mSharingImage && mSharedOutputFile != null) {
883 // Image saved, we unblock the content provider
884 Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
885 Uri.encode(mSharedOutputFile.getAbsolutePath()));
886 ContentValues values = new ContentValues();
887 values.put(SharedImageProvider.PREPARE, false);
888 getContentResolver().insert(uri, values);
890 setResult(RESULT_OK, new Intent().setData(saveUri));
891 hideSavingProgress();
896 public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
897 // First, let's tell the SharedImageProvider that it will need to wait
899 Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
900 Uri.encode(mSharedOutputFile.getAbsolutePath()));
901 ContentValues values = new ContentValues();
902 values.put(SharedImageProvider.PREPARE, true);
903 getContentResolver().insert(uri, values);
904 mSharingImage = true;
906 // Process and save the image in the background.
907 showSavingProgress(null);
908 mImageShow.saveImage(this, mSharedOutputFile);
912 private Intent getDefaultShareIntent() {
913 Intent intent = new Intent(Intent.ACTION_SEND);
914 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
915 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
916 intent.setType(SharedImageProvider.MIME_TYPE);
917 mSharedOutputFile = SaveImage.getNewFile(this, MasterImage.getImage().getUri());
918 Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
919 Uri.encode(mSharedOutputFile.getAbsolutePath()));
920 intent.putExtra(Intent.EXTRA_STREAM, uri);
925 public boolean onCreateOptionsMenu(Menu menu) {
926 getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
927 MenuItem showState = menu.findItem(R.id.showImageStateButton);
928 if (mShowingImageStatePanel) {
929 showState.setTitle(R.string.hide_imagestate_panel);
931 showState.setTitle(R.string.show_imagestate_panel);
933 mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
934 .getActionProvider();
935 mShareActionProvider.setShareIntent(getDefaultShareIntent());
936 mShareActionProvider.setOnShareTargetSelectedListener(this);
942 private void setupMenu(){
943 if (mMenu == null || mMasterImage == null) {
946 MenuItem undoItem = mMenu.findItem(R.id.undoButton);
947 MenuItem redoItem = mMenu.findItem(R.id.redoButton);
948 MenuItem resetItem = mMenu.findItem(R.id.resetHistoryButton);
949 mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
953 public void onPause() {
955 if (mShareActionProvider != null) {
956 mShareActionProvider.setOnShareTargetSelectedListener(null);
961 public void onResume() {
963 if (mShareActionProvider != null) {
964 mShareActionProvider.setOnShareTargetSelectedListener(this);
969 public boolean onOptionsItemSelected(MenuItem item) {
970 switch (item.getItemId()) {
971 case R.id.undoButton: {
972 HistoryManager adapter = mMasterImage.getHistory();
973 int position = adapter.undo();
974 mMasterImage.onHistoryItemClick(position);
977 UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
978 UsageStatistics.CATEGORY_BUTTON_PRESS, "Undo");
981 case R.id.redoButton: {
982 HistoryManager adapter = mMasterImage.getHistory();
983 int position = adapter.redo();
984 mMasterImage.onHistoryItemClick(position);
986 UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
987 UsageStatistics.CATEGORY_BUTTON_PRESS, "Redo");
990 case R.id.resetHistoryButton: {
992 UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
993 UsageStatistics.CATEGORY_BUTTON_PRESS, "ResetHistory");
996 case R.id.showImageStateButton: {
997 toggleImageStatePanel();
998 UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
999 UsageStatistics.CATEGORY_BUTTON_PRESS,
1000 mShowingImageStatePanel ? "ShowPanel" : "HidePanel");
1003 case R.id.exportFlattenButton: {
1004 showExportOptionsDialog();
1007 case android.R.id.home: {
1011 case R.id.manageUserPresets: {
1012 manageUserPresets();
1015 case R.id.showInfoPanel: {
1016 toggleInformationPanel();
1019 case R.id.printButton: {
1027 public void print() {
1028 Bitmap bitmap = MasterImage.getImage().getHighresImage();
1029 PrintJob.printBitmap(this, "ImagePrint", bitmap);
1032 public void addNewPreset() {
1033 DialogFragment dialog = new PresetManagementDialog();
1034 dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
1037 private void manageUserPresets() {
1038 DialogFragment dialog = new PresetManagementDialog();
1039 dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
1042 private void showExportOptionsDialog() {
1043 DialogFragment dialog = new ExportDialog();
1044 dialog.show(getSupportFragmentManager(), "ExportDialogFragment");
1047 public void updateUserPresetsFromAdapter(UserPresetsAdapter adapter) {
1048 ArrayList<FilterUserPresetRepresentation> representations =
1049 adapter.getDeletedRepresentations();
1050 for (FilterUserPresetRepresentation representation : representations) {
1051 deletePreset(representation.getId());
1053 ArrayList<FilterUserPresetRepresentation> changedRepresentations =
1054 adapter.getChangedRepresentations();
1055 for (FilterUserPresetRepresentation representation : changedRepresentations) {
1056 updatePreset(representation);
1058 adapter.clearDeletedRepresentations();
1059 adapter.clearChangedRepresentations();
1063 public void loadUserPresets() {
1064 mUserPresetsManager.load();
1065 updateUserPresetsFromManager();
1068 public void updateUserPresetsFromManager() {
1069 ArrayList<FilterUserPresetRepresentation> presets = mUserPresetsManager.getRepresentations();
1070 if (presets == null) {
1073 if (mCategoryLooksAdapter != null) {
1076 if (presets.size() > 0) {
1077 mCategoryLooksAdapter.add(new Action(this, Action.SPACER));
1079 mUserPresetsAdapter.clear();
1080 for (int i = 0; i < presets.size(); i++) {
1081 FilterUserPresetRepresentation representation = presets.get(i);
1082 mCategoryLooksAdapter.add(
1083 new Action(this, representation, Action.FULL_VIEW, true));
1084 mUserPresetsAdapter.add(new Action(this, representation, Action.FULL_VIEW));
1086 if (presets.size() > 0) {
1087 mCategoryLooksAdapter.add(new Action(this, Action.ADD_ACTION));
1089 mCategoryLooksAdapter.notifyDataSetChanged();
1090 mCategoryLooksAdapter.notifyDataSetInvalidated();
1093 public void saveCurrentImagePreset(String name) {
1094 mUserPresetsManager.save(MasterImage.getImage().getPreset(), name);
1097 private void deletePreset(int id) {
1098 mUserPresetsManager.delete(id);
1101 private void updatePreset(FilterUserPresetRepresentation representation) {
1102 mUserPresetsManager.update(representation);
1105 public void enableSave(boolean enable) {
1106 if (mSaveButton != null) {
1107 mSaveButton.setEnabled(enable);
1111 private void fillLooks() {
1112 FiltersManager filtersManager = FiltersManager.getManager();
1113 ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getLooks();
1115 if (mCategoryLooksAdapter != null) {
1116 mCategoryLooksAdapter.clear();
1118 mCategoryLooksAdapter = new CategoryAdapter(this);
1119 int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
1120 mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
1121 for (FilterRepresentation representation : filtersRepresentations) {
1122 mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
1124 if (mUserPresetsManager.getRepresentations() == null
1125 || mUserPresetsManager.getRepresentations().size() == 0) {
1126 mCategoryLooksAdapter.add(new Action(this, Action.ADD_ACTION));
1129 Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1130 if (panel != null) {
1131 if (panel instanceof MainPanel) {
1132 MainPanel mainPanel = (MainPanel) panel;
1133 mainPanel.loadCategoryLookPanel(true);
1138 public void setDefaultPreset() {
1139 // Default preset (original)
1140 ImagePreset preset = new ImagePreset(); // empty
1141 mMasterImage.setPreset(preset, preset.getLastRepresentation(), true);
1144 // //////////////////////////////////////////////////////////////////////////////
1145 // Some utility functions
1146 // TODO: finish the cleanup.
1148 public void invalidateViews() {
1149 for (ImageShow views : mImageViews) {
1150 views.updateImage();
1154 public void hideImageViews() {
1155 for (View view : mImageViews) {
1156 view.setVisibility(View.GONE);
1158 mEditorPlaceHolder.hide();
1161 // //////////////////////////////////////////////////////////////////////////////
1162 // imageState panel...
1164 public void toggleImageStatePanel() {
1165 invalidateOptionsMenu();
1166 mShowingImageStatePanel = !mShowingImageStatePanel;
1167 Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1168 if (panel != null) {
1169 if (panel instanceof EditorPanel) {
1170 EditorPanel editorPanel = (EditorPanel) panel;
1171 editorPanel.showImageStatePanel(mShowingImageStatePanel);
1172 } else if (panel instanceof MainPanel) {
1173 MainPanel mainPanel = (MainPanel) panel;
1174 mainPanel.showImageStatePanel(mShowingImageStatePanel);
1179 public void toggleVersionsPanel() {
1180 mShowingVersionsPanel = !mShowingVersionsPanel;
1181 Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1182 if (panel != null && panel instanceof MainPanel) {
1183 MainPanel mainPanel = (MainPanel) panel;
1184 mainPanel.loadCategoryVersionsPanel();
1189 public void onConfigurationChanged(Configuration newConfig)
1191 super.onConfigurationChanged(newConfig);
1197 // mLoadBitmapTask==null implies you have looked at the intent
1198 if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
1199 mCategoryFiltersAdapter.removeTinyPlanet();
1201 stopLoadingIndicator();
1204 public void setupMasterImage() {
1206 HistoryManager historyManager = new HistoryManager();
1207 StateAdapter imageStateAdapter = new StateAdapter(this, 0);
1208 MasterImage.reset();
1209 mMasterImage = MasterImage.getImage();
1210 mMasterImage.setHistoryManager(historyManager);
1211 mMasterImage.setStateAdapter(imageStateAdapter);
1212 mMasterImage.setActivity(this);
1213 mMasterImage.setFirstLoad(true);
1215 if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
1216 mMasterImage.setSupportsHighRes(true);
1218 mMasterImage.setSupportsHighRes(false);
1222 void resetHistory() {
1223 HistoryManager adapter = mMasterImage.getHistory();
1225 HistoryItem historyItem = adapter.getItem(0);
1226 ImagePreset original = new ImagePreset(historyItem.getImagePreset());
1227 mMasterImage.setPreset(original, historyItem.getFilterRepresentation(), true);
1232 public void showDefaultImageView() {
1233 mEditorPlaceHolder.hide();
1234 mImageShow.setVisibility(View.VISIBLE);
1235 MasterImage.getImage().setCurrentFilter(null);
1236 MasterImage.getImage().setCurrentFilterRepresentation(null);
1239 public void backToMain() {
1240 Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1241 if (currentPanel instanceof MainPanel) {
1245 showDefaultImageView();
1249 public void onBackPressed() {
1250 Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
1251 if (currentPanel instanceof MainPanel) {
1252 if (!mImageShow.hasModifications()) {
1255 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1256 builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
1257 builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
1259 public void onClick(DialogInterface dialog, int id) {
1263 builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
1265 public void onClick(DialogInterface dialog, int id) {
1276 public void cannotLoadImage() {
1277 Toast.makeText(this, R.string.cannot_load_image, Toast.LENGTH_SHORT).show();
1281 // //////////////////////////////////////////////////////////////////////////////
1283 public float getPixelsFromDip(float value) {
1284 Resources r = getResources();
1285 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
1286 r.getDisplayMetrics());
1290 public void onItemClick(AdapterView<?> parent, View view, int position,
1292 mMasterImage.onHistoryItemClick(position);
1296 public void pickImage() {
1297 Intent intent = new Intent();
1298 intent.setType("image/*");
1299 intent.setAction(Intent.ACTION_GET_CONTENT);
1300 startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
1305 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1306 if (resultCode == RESULT_OK) {
1307 if (requestCode == SELECT_PICTURE) {
1308 Uri selectedImageUri = data.getData();
1309 startLoadBitmap(selectedImageUri);
1315 public void saveImage() {
1316 if (mImageShow.hasModifications()) {
1317 // Get the name of the album, to which the image will be saved
1318 File saveDir = SaveImage.getFinalSaveDirectory(this, mSelectedImageUri);
1319 int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
1320 String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
1321 showSavingProgress(albumName);
1322 mImageShow.saveImage(this, null);
1329 public void done() {
1330 hideSavingProgress();
1331 if (mLoadBitmapTask != null) {
1332 mLoadBitmapTask.cancel(false);
1337 private void extractXMPData() {
1338 XMresults res = XmpPresets.extractXMPData(
1339 getBaseContext(), mMasterImage, getIntent().getData());
1343 mOriginalImageUri = res.originalimage;
1344 mOriginalPreset = res.preset;
1347 public Uri getSelectedImageUri() {
1348 return mSelectedImageUri;
1351 public void setHandlesSwipeForView(View view, float startX, float startY) {
1353 mHandlingSwipeButton = true;
1355 mHandlingSwipeButton = false;
1357 mHandledSwipeView = view;
1358 int[] location = new int[2];
1359 view.getLocationInWindow(location);
1360 mSwipeStartX = location[0] + startX;
1361 mSwipeStartY = location[1] + startY;
1364 public boolean dispatchTouchEvent (MotionEvent ev) {
1365 if (mHandlingSwipeButton) {
1366 int direction = CategoryView.HORIZONTAL;
1367 if (mHandledSwipeView instanceof CategoryView) {
1368 direction = ((CategoryView) mHandledSwipeView).getOrientation();
1370 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
1371 float delta = ev.getY() - mSwipeStartY;
1372 float distance = mHandledSwipeView.getHeight();
1373 if (direction == CategoryView.VERTICAL) {
1374 delta = ev.getX() - mSwipeStartX;
1375 mHandledSwipeView.setTranslationX(delta);
1376 distance = mHandledSwipeView.getWidth();
1378 mHandledSwipeView.setTranslationY(delta);
1380 delta = Math.abs(delta);
1381 float transparency = Math.min(1, delta / distance);
1382 mHandledSwipeView.setAlpha(1.f - transparency);
1383 mHandledSwipeViewLastDelta = delta;
1385 if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1386 || ev.getActionMasked() == MotionEvent.ACTION_UP) {
1387 mHandledSwipeView.setTranslationX(0);
1388 mHandledSwipeView.setTranslationY(0);
1389 mHandledSwipeView.setAlpha(1.f);
1390 mHandlingSwipeButton = false;
1391 float distance = mHandledSwipeView.getHeight();
1392 if (direction == CategoryView.VERTICAL) {
1393 distance = mHandledSwipeView.getWidth();
1395 if (mHandledSwipeViewLastDelta > distance) {
1396 ((SwipableView) mHandledSwipeView).delete();
1401 return super.dispatchTouchEvent(ev);
1404 public Point mHintTouchPoint = new Point();
1406 public Point hintTouchPoint(View view) {
1407 int location[] = new int[2];
1408 view.getLocationOnScreen(location);
1409 int x = mHintTouchPoint.x - location[0];
1410 int y = mHintTouchPoint.y - location[1];
1411 return new Point(x, y);
1414 public void startTouchAnimation(View target, float x, float y) {
1415 final CategorySelected hint =
1416 (CategorySelected) findViewById(R.id.categorySelectedIndicator);
1417 int location[] = new int[2];
1418 target.getLocationOnScreen(location);
1419 mHintTouchPoint.x = (int) (location[0] + x);
1420 mHintTouchPoint.y = (int) (location[1] + y);
1421 int locationHint[] = new int[2];
1422 ((View)hint.getParent()).getLocationOnScreen(locationHint);
1423 int dx = (int) (x - (hint.getWidth())/2);
1424 int dy = (int) (y - (hint.getHeight())/2);
1425 hint.setTranslationX(location[0] - locationHint[0] + dx);
1426 hint.setTranslationY(location[1] - locationHint[1] + dy);
1427 hint.setVisibility(View.VISIBLE);
1428 hint.animate().scaleX(2).scaleY(2).alpha(0).withEndAction(new Runnable() {
1431 hint.setVisibility(View.INVISIBLE);