2 * Copyright (C) 2013 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.crop;
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.WallpaperManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.graphics.Bitmap;
26 import android.graphics.Bitmap.CompressFormat;
27 import android.graphics.BitmapFactory;
28 import android.graphics.BitmapRegionDecoder;
29 import android.graphics.Canvas;
30 import android.graphics.Matrix;
31 import android.graphics.Paint;
32 import android.graphics.Rect;
33 import android.graphics.RectF;
34 import android.net.Uri;
35 import android.os.AsyncTask;
36 import android.os.Bundle;
37 import android.provider.MediaStore;
38 import android.util.DisplayMetrics;
39 import android.util.Log;
40 import android.view.InputDevice;
41 import android.view.InputDevice.MotionRange;
42 import android.view.KeyEvent;
43 import android.view.MotionEvent;
44 import android.view.View;
45 import android.view.View.OnClickListener;
46 import android.view.WindowManager;
47 import android.widget.Toast;
49 import com.android.gallery3d.R;
50 import com.android.gallery3d.common.Utils;
51 import com.android.gallery3d.filtershow.cache.ImageLoader;
52 import com.android.gallery3d.filtershow.tools.SaveImage;
53 import com.google.android.pano.util.TouchNavGestureDetector;
54 import com.google.android.pano.util.TouchNavGestureDetector.OnGestureListener;
56 import java.io.ByteArrayInputStream;
57 import java.io.ByteArrayOutputStream;
58 import java.io.FileNotFoundException;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.OutputStream;
64 * Activity for cropping an image.
66 public class CropActivity extends Activity implements OnGestureListener {
67 private static final String LOGTAG = "CropActivity";
68 public static final String CROP_ACTION = "com.android.camera.action.CROP";
69 private static final int MIN_SCROLL_LENGTH = 5;
70 private CropExtras mCropExtras = null;
71 private LoadBitmapTask mLoadBitmapTask = null;
73 private int mOutputX = 0;
74 private int mOutputY = 0;
75 private Bitmap mOriginalBitmap = null;
76 private RectF mOriginalBounds = null;
77 private int mOriginalRotation = 0;
78 private Uri mSourceUri = null;
79 private CropView mCropView = null;
80 private View mSaveButton = null;
81 private boolean finalIOGuard = false;
83 private static final int SELECT_PICTURE = 1; // request code for picker
85 private static final int DEFAULT_COMPRESS_QUALITY = 90;
87 * The maximum bitmap size we allow to be returned through the intent.
88 * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
89 * have some overhead to hit so that we go way below the limit here to make
90 * sure the intent stays below 1MB.We should consider just returning a byte
91 * array instead of a Bitmap instance to avoid overhead.
93 public static final int MAX_BMAP_IN_INTENT = 750000;
96 private static final int DO_SET_WALLPAPER = 1;
97 private static final int DO_RETURN_DATA = 1 << 1;
98 private static final int DO_EXTRA_OUTPUT = 1 << 2;
100 private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
102 private TouchNavGestureDetector mGestureDetector;
103 private float mTouchPadToViewRatioX;
104 private float mTouchPadToViewRatioY;
105 private float mResolutionX;
106 private float mResolutionY;
109 * Used for click filtering. Any scroll less than MIN_SCROLL_LENGTH will not
110 * be registered. This will make clicking motions not trigger a scroll. Once
111 * you get past MIN_SCROLL_LENGTH, you can go below it again and register
112 * scrolls. This is to allow for starting a scroll and then moving back to
113 * the origin of the scroll.
115 private boolean mDidStartDrag;
118 public void onCreate(Bundle savedInstanceState) {
119 super.onCreate(savedInstanceState);
120 mGestureDetector = new TouchNavGestureDetector(this, this);
121 Intent intent = getIntent();
122 setResult(RESULT_CANCELED, new Intent());
123 mCropExtras = getExtrasFromIntent(intent);
124 if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
125 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
128 setContentView(R.layout.crop_activity);
129 mCropView = (CropView) findViewById(R.id.cropView);
131 ActionBar actionBar = getActionBar();
132 if (actionBar != null) {
133 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
134 actionBar.setCustomView(R.layout.filtershow_actionbar);
136 mSaveButton = actionBar.getCustomView();
137 mSaveButton.setOnClickListener(new OnClickListener() {
139 public void onClick(View view) {
145 if (intent.getData() != null) {
146 mSourceUri = intent.getData();
147 startLoadBitmap(mSourceUri);
153 private void enableSave(boolean enable) {
154 if (mSaveButton != null) {
155 mSaveButton.setEnabled(enable);
160 protected void onDestroy() {
161 if (mLoadBitmapTask != null) {
162 mLoadBitmapTask.cancel(false);
168 public void onConfigurationChanged (Configuration newConfig) {
169 super.onConfigurationChanged(newConfig);
170 mCropView.configChanged();
174 * Opens a selector in Gallery to chose an image for use when none was given
175 * in the CROP intent.
177 private void pickImage() {
178 Intent intent = new Intent();
179 intent.setType("image/*");
180 intent.setAction(Intent.ACTION_GET_CONTENT);
181 startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
186 * Callback for pickImage().
189 public void onActivityResult(int requestCode, int resultCode, Intent data) {
190 if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
191 mSourceUri = data.getData();
192 startLoadBitmap(mSourceUri);
197 * Gets screen size metric.
199 private int getScreenImageSize() {
200 DisplayMetrics outMetrics = new DisplayMetrics();
201 getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
202 return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
206 * Method that loads a bitmap in an async task.
208 private void startLoadBitmap(Uri uri) {
211 final View loading = findViewById(R.id.loading);
212 loading.setVisibility(View.VISIBLE);
213 mLoadBitmapTask = new LoadBitmapTask();
214 mLoadBitmapTask.execute(uri);
222 * Method called on UI thread with loaded bitmap.
224 private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
225 final View loading = findViewById(R.id.loading);
226 loading.setVisibility(View.GONE);
227 mOriginalBitmap = bitmap;
228 mOriginalBounds = bounds;
229 mOriginalRotation = orientation;
230 if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
231 RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
232 mCropView.initialize(bitmap, imgBounds, imgBounds, orientation);
233 if (mCropExtras != null) {
234 int aspectX = mCropExtras.getAspectX();
235 int aspectY = mCropExtras.getAspectY();
236 mOutputX = mCropExtras.getOutputX();
237 mOutputY = mCropExtras.getOutputY();
238 if (mOutputX > 0 && mOutputY > 0) {
239 mCropView.applyAspect(mOutputX, mOutputY);
242 float spotX = mCropExtras.getSpotlightX();
243 float spotY = mCropExtras.getSpotlightY();
244 if (spotX > 0 && spotY > 0) {
245 mCropView.setWallpaperSpotlight(spotX, spotY);
247 if (aspectX > 0 && aspectY > 0) {
248 mCropView.applyAspect(aspectX, aspectY);
253 Log.w(LOGTAG, "could not load image for cropping");
255 setResult(RESULT_CANCELED, new Intent());
261 * Display toast for image loading failure.
263 private void cannotLoadImage() {
264 CharSequence text = getString(R.string.cannot_load_image);
265 Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
270 * AsyncTask for loading a bitmap into memory.
272 * @see #startLoadBitmap(Uri)
273 * @see #doneLoadBitmap(Bitmap)
275 private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
278 Rect mOriginalBounds;
281 public LoadBitmapTask() {
282 mBitmapSize = getScreenImageSize();
283 mContext = getApplicationContext();
284 mOriginalBounds = new Rect();
289 protected Bitmap doInBackground(Uri... params) {
291 Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
292 mOriginalBounds, false);
293 mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
298 protected void onPostExecute(Bitmap result) {
299 doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
303 private void startFinishOutput() {
310 Uri destinationUri = null;
312 if (mOriginalBitmap != null && mCropExtras != null) {
313 if (mCropExtras.getExtraOutput() != null) {
314 destinationUri = mCropExtras.getExtraOutput();
315 if (destinationUri != null) {
316 flags |= DO_EXTRA_OUTPUT;
319 if (mCropExtras.getSetAsWallpaper()) {
320 flags |= DO_SET_WALLPAPER;
322 if (mCropExtras.getReturnData()) {
323 flags |= DO_RETURN_DATA;
327 destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri);
328 if (destinationUri != null) {
329 flags |= DO_EXTRA_OUTPUT;
332 if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
333 RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
334 RectF crop = getBitmapCrop(photo);
335 startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
336 photo, mOriginalBounds,
337 (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
340 setResult(RESULT_CANCELED, new Intent());
345 private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
346 RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
348 if (cropBounds == null || photoBounds == null || currentBitmap == null
349 || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
350 || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
351 || photoBounds.height() == 0) {
354 if ((flags & FLAG_CHECK) == 0) {
355 return; // no output options
357 if ((flags & DO_SET_WALLPAPER) != 0) {
358 Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
361 final View loading = findViewById(R.id.loading);
362 loading.setVisibility(View.VISIBLE);
363 BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
364 photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
365 ioTask.execute(currentBitmap);
368 private void doneBitmapIO(boolean success, Intent intent) {
369 final View loading = findViewById(R.id.loading);
370 loading.setVisibility(View.GONE);
372 setResult(RESULT_OK, intent);
374 setResult(RESULT_CANCELED, intent);
379 private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
381 private final WallpaperManager mWPManager;
382 InputStream mInStream = null;
383 OutputStream mOutStream = null;
384 String mOutputFormat = null;
391 Intent mResultIntent = null;
394 // Helper to setup input stream
395 private void regenerateInputStream() {
396 if (mInUri == null) {
397 Log.w(LOGTAG, "cannot read original file, no input URI given");
399 Utils.closeSilently(mInStream);
401 mInStream = getContentResolver().openInputStream(mInUri);
402 } catch (FileNotFoundException e) {
403 Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
408 public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
409 RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
410 int outputX, int outputY) {
411 mOutputFormat = outputFormat;
417 mPhoto = photoBounds;
418 mOrig = originalBitmapBounds;
419 mWPManager = WallpaperManager.getInstance(getApplicationContext());
420 mResultIntent = new Intent();
421 mRotation = (rotation < 0) ? -rotation : rotation;
423 mRotation = 90 * (int) (mRotation / 90); // now mRotation is a multiple of 90
427 if ((flags & DO_EXTRA_OUTPUT) != 0) {
428 if (mOutUri == null) {
429 Log.w(LOGTAG, "cannot write file, no output URI given");
432 mOutStream = getContentResolver().openOutputStream(mOutUri);
433 } catch (FileNotFoundException e) {
434 Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
439 if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
440 regenerateInputStream();
445 protected Boolean doInBackground(Bitmap... params) {
446 boolean failure = false;
447 Bitmap img = params[0];
449 // Set extra for crop bounds
450 if (mCrop != null && mPhoto != null && mOrig != null) {
451 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
452 Matrix m = new Matrix();
453 m.setRotate(mRotation);
455 if (trueCrop != null) {
456 Rect rounded = new Rect();
457 trueCrop.roundOut(rounded);
458 mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
462 // Find the small cropped bitmap that is returned in the intent
463 if ((mFlags & DO_RETURN_DATA) != 0) {
464 assert (img != null);
465 Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
467 ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
470 Log.w(LOGTAG, "could not downsample bitmap to return in data");
474 Matrix m = new Matrix();
475 m.setRotate(mRotation);
476 Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
477 ret.getHeight(), m, true);
482 mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
486 // Do the large cropped bitmap and/or set the wallpaper
487 if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
488 // Find crop bounds (scaled to original image size)
489 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
490 if (trueCrop == null) {
491 Log.w(LOGTAG, "cannot find crop for full size image");
495 Rect roundedTrueCrop = new Rect();
496 trueCrop.roundOut(roundedTrueCrop);
498 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
499 Log.w(LOGTAG, "crop has bad values for full size image");
504 // Attempt to open a region decoder
505 BitmapRegionDecoder decoder = null;
507 decoder = BitmapRegionDecoder.newInstance(mInStream, true);
508 } catch (IOException e) {
509 Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
513 if (decoder != null) {
514 // Do region decoding to get crop bitmap
515 BitmapFactory.Options options = new BitmapFactory.Options();
516 options.inMutable = true;
517 crop = decoder.decodeRegion(roundedTrueCrop, options);
522 // BitmapRegionDecoder has failed, try to crop in-memory
523 regenerateInputStream();
524 Bitmap fullSize = null;
525 if (mInStream != null) {
526 fullSize = BitmapFactory.decodeStream(mInStream);
528 if (fullSize != null) {
529 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
530 roundedTrueCrop.top, roundedTrueCrop.width(),
531 roundedTrueCrop.height());
536 Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
540 if (mOutputX > 0 && mOutputY > 0) {
541 Matrix m = new Matrix();
542 RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
544 m.setRotate(mRotation);
547 RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
548 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
549 m.preRotate(mRotation);
550 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
551 (int) returnRect.height(), Bitmap.Config.ARGB_8888);
553 Canvas c = new Canvas(tmp);
554 c.drawBitmap(crop, m, new Paint());
557 } else if (mRotation > 0) {
558 Matrix m = new Matrix();
559 m.setRotate(mRotation);
560 Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
561 crop.getHeight(), m, true);
566 // Get output compression format
568 convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
570 // If we only need to output to a URI, compress straight to file
571 if (mFlags == DO_EXTRA_OUTPUT) {
572 if (mOutStream == null
573 || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
574 Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
577 mResultIntent.setData(mOutUri);
580 // Compress to byte array
581 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
582 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
584 // If we need to output to a Uri, write compressed
586 if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
587 if (mOutStream == null) {
589 "failed to compress bitmap to file: " + mOutUri.toString());
593 mOutStream.write(tmpOut.toByteArray());
594 mResultIntent.setData(mOutUri);
595 } catch (IOException e) {
597 "failed to compress bitmap to file: "
598 + mOutUri.toString(), e);
604 // If we need to set to the wallpaper, set it
605 if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
606 if (mWPManager == null) {
607 Log.w(LOGTAG, "no wallpaper manager");
611 mWPManager.setStream(new ByteArrayInputStream(tmpOut
613 } catch (IOException e) {
614 Log.w(LOGTAG, "cannot write stream to wallpaper", e);
620 Log.w(LOGTAG, "cannot compress bitmap");
625 return !failure; // True if any of the operations failed
629 protected void onPostExecute(Boolean result) {
630 Utils.closeSilently(mOutStream);
631 Utils.closeSilently(mInStream);
632 doneBitmapIO(result.booleanValue(), mResultIntent);
637 private void done() {
641 protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
642 RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
643 RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
647 Rect intCrop = new Rect();
648 crop.roundOut(intCrop);
649 return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
653 protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
654 if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
655 throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
658 int size = CropMath.getBitmapSize(image);
659 while (size > max_size) {
663 Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
664 image.getHeight() >> shifts, true);
668 // Handle edge case for rounding.
669 if (CropMath.getBitmapSize(ret) > max_size) {
670 return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
676 * Gets the crop extras from the intent, or null if none exist.
678 protected static CropExtras getExtrasFromIntent(Intent intent) {
679 Bundle extras = intent.getExtras();
680 if (extras != null) {
681 return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
682 extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
683 extras.getBoolean(CropExtras.KEY_SCALE, true) &&
684 extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
685 extras.getInt(CropExtras.KEY_ASPECT_X, 0),
686 extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
687 extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
688 extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
689 (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
690 extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
691 extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
692 extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
693 extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
698 protected static CompressFormat convertExtensionToCompressFormat(String extension) {
699 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
702 protected static String getFileExtension(String requestFormat) {
703 String outputFormat = (requestFormat == null)
706 outputFormat = outputFormat.toLowerCase();
707 return (outputFormat.equals("png") || outputFormat.equals("gif"))
708 ? "png" // We don't support gif compression.
712 private RectF getBitmapCrop(RectF imageBounds) {
713 RectF crop = mCropView.getCrop();
714 RectF photo = mCropView.getPhoto();
715 if (crop == null || photo == null) {
716 Log.w(LOGTAG, "could not get crop");
719 RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
724 public boolean onGenericMotionEvent(MotionEvent event) {
725 boolean handled = super.onGenericMotionEvent(event);
727 handled = mGestureDetector.onGenericMotionEvent(event);
729 && (event.getSource() & TouchNavGestureDetector.SOURCE_TOUCH_NAVIGATION)
730 == TouchNavGestureDetector.SOURCE_TOUCH_NAVIGATION) {
731 int action = event.getActionMasked();
733 case MotionEvent.ACTION_UP:
734 case MotionEvent.ACTION_CANCEL:
735 sendScaledMotionEvent(event);
745 public boolean onKeyDown(int keyCode, KeyEvent event) {
746 boolean handled = super.onKeyDown(keyCode, event);
748 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
757 * Returns whether a scroll happened.
760 public boolean onScroll(MotionEvent downEvent, MotionEvent currentEvent, float deltaX,
762 // If <5mm from the start, consider it a click. Used for smoother
764 if (!mDidStartDrag) {
765 float distanceX = Math.abs((currentEvent.getX() - downEvent.getX()) / mResolutionX);
766 float distanceY = Math.abs((currentEvent.getY() - downEvent.getY()) / mResolutionY);
767 if (distanceX < MIN_SCROLL_LENGTH && distanceY < MIN_SCROLL_LENGTH) {
768 // Not moved enough to scroll.
772 // Moved enough to scroll.
773 mDidStartDrag = true;
774 sendScaledMotionEvent(currentEvent);
779 public boolean onFling(MotionEvent downEvent, MotionEvent currentEvent, float velX,
781 sendScaledMotionEvent(currentEvent);
786 public boolean onDown(MotionEvent downEvent) {
787 mDidStartDrag = false;
788 InputDevice device = downEvent.getDevice();
789 MotionRange motionRangeX = device.getMotionRange(MotionEvent.AXIS_X);
790 MotionRange motionRangeY = device.getMotionRange(MotionEvent.AXIS_Y);
792 if (motionRangeX == null || motionRangeY == null) {
796 mResolutionX = motionRangeX.getResolution();
797 mResolutionY = motionRangeY.getResolution();
799 if (mResolutionX == 0) {
800 mResolutionX = TouchNavGestureDetector.DEFAULT_TOUCH_RESOLUTION;
802 if (mResolutionY == 0) {
803 mResolutionY = TouchNavGestureDetector.DEFAULT_TOUCH_RESOLUTION;
806 // This is the conversion between a drag on the device vs. a drag on
808 mTouchPadToViewRatioX = mCropView.getWidth() / motionRangeX.getRange();
809 mTouchPadToViewRatioY = mCropView.getHeight() / motionRangeY.getRange();
811 sendScaledMotionEvent(downEvent);
815 private void sendScaledMotionEvent(MotionEvent event) {
816 final int scaledX = (int) (mTouchPadToViewRatioX * event.getX());
817 final int scaledY = (int) (mTouchPadToViewRatioY * event.getY());
818 final MotionEvent ev = MotionEvent.obtain(event.getEventTime(), event.getEventTime(),
819 event.getActionMasked(), scaledX, scaledY, 0);
820 mCropView.onTouchEvent(ev);