OSDN Git Service

Give CropActivity minimal touchpad support.
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / filtershow / crop / CropActivity.java
index 3429e0b..9c01308 100644 (file)
@@ -21,11 +21,14 @@ import android.app.Activity;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.net.Uri;
@@ -34,12 +37,21 @@ import android.os.Bundle;
 import android.provider.MediaStore;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.InputDevice;
+import android.view.InputDevice.MotionRange;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.WindowManager;
 import android.widget.Toast;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+import com.google.android.pano.util.TouchNavGestureDetector;
+import com.google.android.pano.util.TouchNavGestureDetector.OnGestureListener;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -51,12 +63,15 @@ import java.io.OutputStream;
 /**
  * Activity for cropping an image.
  */
-public class CropActivity extends Activity {
+public class CropActivity extends Activity implements OnGestureListener {
     private static final String LOGTAG = "CropActivity";
     public static final String CROP_ACTION = "com.android.camera.action.CROP";
+    private static final int MIN_SCROLL_LENGTH = 5;
     private CropExtras mCropExtras = null;
     private LoadBitmapTask mLoadBitmapTask = null;
 
+    private int mOutputX = 0;
+    private int mOutputY = 0;
     private Bitmap mOriginalBitmap = null;
     private RectF mOriginalBounds = null;
     private int mOriginalRotation = 0;
@@ -68,7 +83,14 @@ public class CropActivity extends Activity {
     private static final int SELECT_PICTURE = 1; // request code for picker
 
     private static final int DEFAULT_COMPRESS_QUALITY = 90;
-    public static final int MAX_BMAP_IN_INTENT = 990000;
+    /**
+     * The maximum bitmap size we allow to be returned through the intent.
+     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+     * have some overhead to hit so that we go way below the limit here to make
+     * sure the intent stays below 1MB.We should consider just returning a byte
+     * array instead of a Bitmap instance to avoid overhead.
+     */
+    public static final int MAX_BMAP_IN_INTENT = 750000;
 
     // Flags
     private static final int DO_SET_WALLPAPER = 1;
@@ -77,9 +99,25 @@ public class CropActivity extends Activity {
 
     private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
 
+    private TouchNavGestureDetector mGestureDetector;
+    private float mTouchPadToViewRatioX;
+    private float mTouchPadToViewRatioY;
+    private float mResolutionX;
+    private float mResolutionY;
+
+    /**
+     * Used for click filtering. Any scroll less than MIN_SCROLL_LENGTH will not
+     * be registered. This will make clicking motions not trigger a scroll. Once
+     * you get past MIN_SCROLL_LENGTH, you can go below it again and register
+     * scrolls. This is to allow for starting a scroll and then moving back to
+     * the origin of the scroll.
+     */
+    private boolean mDidStartDrag;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mGestureDetector = new TouchNavGestureDetector(this, this);
         Intent intent = getIntent();
         setResult(RESULT_CANCELED, new Intent());
         mCropExtras = getExtrasFromIntent(intent);
@@ -91,16 +129,18 @@ public class CropActivity extends Activity {
         mCropView = (CropView) findViewById(R.id.cropView);
 
         ActionBar actionBar = getActionBar();
-        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
-        actionBar.setCustomView(R.layout.filtershow_actionbar);
-
-        View mSaveButton = actionBar.getCustomView();
-        mSaveButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                startFinishOutput();
-            }
-        });
+        if (actionBar != null) {
+            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+            actionBar.setCustomView(R.layout.filtershow_actionbar);
+
+            mSaveButton = actionBar.getCustomView();
+            mSaveButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    startFinishOutput();
+                }
+            });
+        }
 
         if (intent.getData() != null) {
             mSourceUri = intent.getData();
@@ -124,6 +164,12 @@ public class CropActivity extends Activity {
         super.onDestroy();
     }
 
+    @Override
+    public void onConfigurationChanged (Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mCropView.configChanged();
+    }
+
     /**
      * Opens a selector in Gallery to chose an image for use when none was given
      * in the CROP intent.
@@ -187,10 +233,16 @@ public class CropActivity extends Activity {
             if (mCropExtras != null) {
                 int aspectX = mCropExtras.getAspectX();
                 int aspectY = mCropExtras.getAspectY();
-                int outputX = mCropExtras.getOutputX();
-                int outputY = mCropExtras.getOutputY();
-                if (outputX > 0 && outputY > 0) {
-                    mCropView.applyAspect(outputX, outputY);
+                mOutputX = mCropExtras.getOutputX();
+                mOutputY = mCropExtras.getOutputY();
+                if (mOutputX > 0 && mOutputY > 0) {
+                    mCropView.applyAspect(mOutputX, mOutputY);
+
+                }
+                float spotX = mCropExtras.getSpotlightX();
+                float spotY = mCropExtras.getSpotlightY();
+                if (spotX > 0 && spotY > 0) {
+                    mCropView.setWallpaperSpotlight(spotX, spotY);
                 }
                 if (aspectX > 0 && aspectY > 0) {
                     mCropView.applyAspect(aspectX, aspectY);
@@ -236,9 +288,9 @@ public class CropActivity extends Activity {
         @Override
         protected Bitmap doInBackground(Uri... params) {
             Uri uri = params[0];
-            Bitmap bmap = CropLoader.getConstrainedBitmap(uri, mContext, mBitmapSize,
-                    mOriginalBounds);
-            mOrientation = CropLoader.getMetadataRotation(uri, mContext);
+            Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
+                    mOriginalBounds, false);
+            mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
             return bmap;
         }
 
@@ -260,7 +312,9 @@ public class CropActivity extends Activity {
         if (mOriginalBitmap != null && mCropExtras != null) {
             if (mCropExtras.getExtraOutput() != null) {
                 destinationUri = mCropExtras.getExtraOutput();
-                flags |= DO_EXTRA_OUTPUT;
+                if (destinationUri != null) {
+                    flags |= DO_EXTRA_OUTPUT;
+                }
             }
             if (mCropExtras.getSetAsWallpaper()) {
                 flags |= DO_SET_WALLPAPER;
@@ -270,12 +324,12 @@ public class CropActivity extends Activity {
             }
         }
         if (flags == 0) {
-            destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri);
+            destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri);
             if (destinationUri != null) {
                 flags |= DO_EXTRA_OUTPUT;
             }
         }
-        if ((flags & FLAG_CHECK) != 0) {
+        if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
             RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
             RectF crop = getBitmapCrop(photo);
             startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
@@ -307,7 +361,7 @@ public class CropActivity extends Activity {
         final View loading = findViewById(R.id.loading);
         loading.setVisibility(View.VISIBLE);
         BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
-                photoBounds, currentBitmapBounds, rotation);
+                photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
         ioTask.execute(currentBitmap);
     }
 
@@ -337,8 +391,23 @@ public class CropActivity extends Activity {
         Intent mResultIntent = null;
         int mRotation = 0;
 
+        // Helper to setup input stream
+        private void regenerateInputStream() {
+            if (mInUri == null) {
+                Log.w(LOGTAG, "cannot read original file, no input URI given");
+            } else {
+                Utils.closeSilently(mInStream);
+                try {
+                    mInStream = getContentResolver().openInputStream(mInUri);
+                } catch (FileNotFoundException e) {
+                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+                }
+            }
+        }
+
         public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
-                RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation) {
+                RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
+                int outputX, int outputY) {
             mOutputFormat = outputFormat;
             mOutStream = null;
             mOutUri = destUri;
@@ -352,6 +421,8 @@ public class CropActivity extends Activity {
             mRotation = (rotation < 0) ? -rotation : rotation;
             mRotation %= 360;
             mRotation = 90 * (int) (mRotation / 90);  // now mRotation is a multiple of 90
+            mOutputX = outputX;
+            mOutputY = outputY;
 
             if ((flags & DO_EXTRA_OUTPUT) != 0) {
                 if (mOutUri == null) {
@@ -366,15 +437,7 @@ public class CropActivity extends Activity {
             }
 
             if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
-                if (mInUri == null) {
-                    Log.w(LOGTAG, "cannot read original file, no input URI given");
-                } else {
-                    try {
-                        mInStream = getContentResolver().openInputStream(mInUri);
-                    } catch (FileNotFoundException e) {
-                        Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
-                    }
-                }
+                regenerateInputStream();
             }
         }
 
@@ -422,17 +485,6 @@ public class CropActivity extends Activity {
 
             // Do the large cropped bitmap and/or set the wallpaper
             if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
-                BitmapRegionDecoder decoder = null;
-                try {
-                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
-                } catch (IOException e) {
-                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
-                }
-                if (decoder == null) {
-                    failure = true;
-                    return false;
-                }
-
                 // Find crop bounds (scaled to original image size)
                 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
                 if (trueCrop == null) {
@@ -448,18 +500,61 @@ public class CropActivity extends Activity {
                     failure = true;
                     return false;
                 }
-                // Do region decoding to get crop bitmap
-                BitmapFactory.Options options = new BitmapFactory.Options();
-                options.inMutable = true;
-                Bitmap crop = decoder.decodeRegion(roundedTrueCrop, options);
-                decoder.recycle();
+
+                // Attempt to open a region decoder
+                BitmapRegionDecoder decoder = null;
+                try {
+                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+                }
+
+                Bitmap crop = null;
+                if (decoder != null) {
+                    // Do region decoding to get crop bitmap
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inMutable = true;
+                    crop = decoder.decodeRegion(roundedTrueCrop, options);
+                    decoder.recycle();
+                }
 
                 if (crop == null) {
-                    Log.w(LOGTAG, "cannot region decode file: " + mInUri.toString());
+                    // BitmapRegionDecoder has failed, try to crop in-memory
+                    regenerateInputStream();
+                    Bitmap fullSize = null;
+                    if (mInStream != null) {
+                        fullSize = BitmapFactory.decodeStream(mInStream);
+                    }
+                    if (fullSize != null) {
+                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+                                roundedTrueCrop.top, roundedTrueCrop.width(),
+                                roundedTrueCrop.height());
+                    }
+                }
+
+                if (crop == null) {
+                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
                     failure = true;
                     return false;
                 }
-                if (mRotation > 0) {
+                if (mOutputX > 0 && mOutputY > 0) {
+                    Matrix m = new Matrix();
+                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
+                    if (mRotation > 0) {
+                        m.setRotate(mRotation);
+                        m.mapRect(cropRect);
+                    }
+                    RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
+                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+                    m.preRotate(mRotation);
+                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+                    if (tmp != null) {
+                        Canvas c = new Canvas(tmp);
+                        c.drawBitmap(crop, m, new Paint());
+                        crop = tmp;
+                    }
+                } else if (mRotation > 0) {
                     Matrix m = new Matrix();
                     m.setRotate(mRotation);
                     Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
@@ -532,6 +627,8 @@ public class CropActivity extends Activity {
 
         @Override
         protected void onPostExecute(Boolean result) {
+            Utils.closeSilently(mOutStream);
+            Utils.closeSilently(mInStream);
             doneBitmapIO(result.booleanValue(), mResultIntent);
         }
 
@@ -622,4 +719,106 @@ public class CropActivity extends Activity {
         RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
         return scaledCrop;
     }
+
+    @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        boolean handled = super.onGenericMotionEvent(event);
+        if (!handled) {
+            handled = mGestureDetector.onGenericMotionEvent(event);
+            if (!handled
+                    && (event.getSource() & TouchNavGestureDetector.SOURCE_TOUCH_NAVIGATION)
+                        == TouchNavGestureDetector.SOURCE_TOUCH_NAVIGATION) {
+                int action = event.getActionMasked();
+                switch (action) {
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        sendScaledMotionEvent(event);
+                        handled = true;
+                        break;
+                }
+            }
+        }
+        return handled;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        boolean handled = super.onKeyDown(keyCode, event);
+        if (!handled) {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
+                startFinishOutput();
+                handled = true;
+            }
+        }
+        return handled;
+    }
+
+    /**
+     * Returns whether a scroll happened.
+     */
+    @Override
+    public boolean onScroll(MotionEvent downEvent, MotionEvent currentEvent, float deltaX,
+            float deltaY) {
+        // If <5mm from the start, consider it a click. Used for smoother
+        // clicks.
+        if (!mDidStartDrag) {
+            float distanceX = Math.abs((currentEvent.getX() - downEvent.getX()) / mResolutionX);
+            float distanceY = Math.abs((currentEvent.getY() - downEvent.getY()) / mResolutionY);
+            if (distanceX < MIN_SCROLL_LENGTH && distanceY < MIN_SCROLL_LENGTH) {
+                // Not moved enough to scroll.
+                return false;
+            }
+        }
+        // Moved enough to scroll.
+        mDidStartDrag = true;
+        sendScaledMotionEvent(currentEvent);
+        return true;
+    }
+
+    @Override
+    public boolean onFling(MotionEvent downEvent, MotionEvent currentEvent, float velX,
+            float velY) {
+        sendScaledMotionEvent(currentEvent);
+        return true;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent downEvent) {
+        mDidStartDrag = false;
+        InputDevice device = downEvent.getDevice();
+        MotionRange motionRangeX = device.getMotionRange(MotionEvent.AXIS_X);
+        MotionRange motionRangeY = device.getMotionRange(MotionEvent.AXIS_Y);
+
+        if (motionRangeX == null || motionRangeY == null) {
+            return false;
+        }
+
+        mResolutionX = motionRangeX.getResolution();
+        mResolutionY = motionRangeY.getResolution();
+
+        if (mResolutionX == 0) {
+            mResolutionX = TouchNavGestureDetector.DEFAULT_TOUCH_RESOLUTION;
+        }
+        if (mResolutionY == 0) {
+            mResolutionY = TouchNavGestureDetector.DEFAULT_TOUCH_RESOLUTION;
+        }
+
+        // This is the conversion between a drag on the device vs. a drag on
+        // the screen.
+        mTouchPadToViewRatioX = mCropView.getWidth() / motionRangeX.getRange();
+        mTouchPadToViewRatioY = mCropView.getHeight() / motionRangeY.getRange();
+
+        sendScaledMotionEvent(downEvent);
+        return true;
+    }
+
+    private void sendScaledMotionEvent(MotionEvent event) {
+        final int scaledX = (int) (mTouchPadToViewRatioX * event.getX());
+        final int scaledY = (int) (mTouchPadToViewRatioY * event.getY());
+        final MotionEvent ev = MotionEvent.obtain(event.getEventTime(), event.getEventTime(),
+                event.getActionMasked(), scaledX, scaledY, 0);
+        mCropView.onTouchEvent(ev);
+        ev.recycle();
+    }
+
 }