Merge "Usage statistics for photo and video capture" into gb-ub-photos-bryce
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/CameraManager.java
index e89c2eb..be82ea6 100644
--- a/src/com/android/camera/CameraManager.java
+++ b/src/com/android/camera/CameraManager.java
@@ -29,7 +29,6 @@
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.ShutterCallback;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -40,7 +39,6 @@
import com.android.gallery3d.common.ApiHelper;
import java.io.IOException;
-import java.util.concurrent.SynchronousQueue;
public class CameraManager {
private static final String TAG = "CameraManager";
@@ -48,10 +46,7 @@
private Parameters mParameters;
private boolean mParametersIsDirty;
- private SynchronousQueue<Parameters> mParametersQueue =
- new SynchronousQueue<Parameters>();
- private SynchronousQueue<IOExceptionHolder> mReconnectExceptionQueue =
- new SynchronousQueue<IOExceptionHolder>();
+ private IOException mReconnectIOException;
private static final int RELEASE = 1;
private static final int RECONNECT = 2;
@@ -148,16 +143,11 @@
return;
case RECONNECT:
- IOExceptionHolder holder = new IOExceptionHolder();
- holder.ex = null;
+ mReconnectIOException = null;
try {
mCamera.reconnect();
} catch (IOException ex) {
- holder.ex = ex;
- }
- try {
- mReconnectExceptionQueue.put(holder);
- } catch (InterruptedException ex) {
+ mReconnectIOException = ex;
}
return;
@@ -240,11 +230,7 @@
return;
case GET_PARAMETERS:
- try {
- mParametersQueue.put(mCamera.getParameters());
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ mParameters = mCamera.getParameters();
return;
case SET_PARAMETERS_ASYNC:
@@ -303,14 +289,6 @@
}
public class CameraProxy {
- private ConditionVariable mWaitDoneLock = new ConditionVariable();
- private Runnable mUnlockRunnable = new Runnable() {
- @Override
- public void run() {
- mWaitDoneLock.open();
- }
- };
-
private CameraProxy() {
Assert(mCamera != null);
@@ -329,13 +307,9 @@
public void reconnect() throws IOException {
mCameraHandler.sendEmptyMessage(RECONNECT);
- IOExceptionHolder holder = null;
- try {
- holder = mReconnectExceptionQueue.take();
- } catch (InterruptedException ex) {
- }
- if (holder != null && holder.ex != null) {
- throw holder.ex;
+ waitDone();
+ if (mReconnectIOException != null) {
+ throw mReconnectIOException;
}
}
@@ -446,12 +420,20 @@
}
public void setParameters(Parameters params) {
- // TODO: check if this synchronous version is necessary
+ if (params == null) {
+ Log.v(TAG, "null parameters in setParameters()");
+ return;
+ }
mParametersIsDirty = true;
mCameraHandler.obtainMessage(SET_PARAMETERS, params).sendToTarget();
}
public void setParametersAsync(Parameters params) {
+ // TODO: remove this.
+ if (params == null) {
+ Log.v(TAG, "null parameters in setParameters()");
+ return;
+ }
mParametersIsDirty = true;
mCameraHandler.removeMessages(SET_PARAMETERS_ASYNC);
mCameraHandler.obtainMessage(SET_PARAMETERS_ASYNC, params).sendToTarget();
@@ -460,11 +442,7 @@
public Parameters getParameters() {
if (mParametersIsDirty || mParameters == null) {
mCameraHandler.sendEmptyMessage(GET_PARAMETERS);
- try {
- mParameters = mParametersQueue.take();
- mParametersIsDirty = false;
- } catch (InterruptedException ex) {
- }
+ if (waitDone()) mParametersIsDirty = false;
}
return mParameters;
}
@@ -474,10 +452,28 @@
ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0).sendToTarget();
}
- public void waitDone() {
- mWaitDoneLock.close();
- mCameraHandler.post(mUnlockRunnable);
- mWaitDoneLock.block();
+ // return false if cancelled.
+ public boolean waitDone() {
+ final Object waitDoneLock = new Object();
+ final Runnable unlockRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (waitDoneLock) {
+ waitDoneLock.notifyAll();
+ }
+ }
+ };
+
+ synchronized (waitDoneLock) {
+ mCameraHandler.post(unlockRunnable);
+ try {
+ waitDoneLock.wait();
+ } catch (InterruptedException ex) {
+ Log.v(TAG, "waitDone interrupted");
+ return false;
+ }
+ }
+ return true;
}
}
}
diff --git a/src/com/android/camera/ComboPreferences.java b/src/com/android/camera/ComboPreferences.java
index c561e6b..e17e47a 100644
--- a/src/com/android/camera/ComboPreferences.java
+++ b/src/com/android/camera/ComboPreferences.java
@@ -22,6 +22,8 @@
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.preference.PreferenceManager;
+import com.android.gallery3d.util.UsageStatistics;
+
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
@@ -205,9 +207,7 @@
@Override
public boolean contains(String key) {
- if (mPrefLocal.contains(key)) return true;
- if (mPrefGlobal.contains(key)) return true;
- return false;
+ return mPrefLocal.contains(key) || mPrefGlobal.contains(key);
}
private class MyEditor implements Editor {
@@ -330,5 +330,6 @@
listener.onSharedPreferenceChanged(this, key);
}
BackupManager.dataChanged(mPackageName);
+ UsageStatistics.onEvent("CameraSettingsChange", null, key);
}
}
diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java
index 2535a0c..280ad44 100644
--- a/src/com/android/camera/PhotoMenu.java
+++ b/src/com/android/camera/PhotoMenu.java
@@ -34,7 +34,6 @@
TimerSettingPopup.Listener,
ListPrefSettingPopup.Listener {
private static String TAG = "CAM_photomenu";
- private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2;
private final String mSettingOff;
private PhotoUI mUI;
@@ -54,13 +53,20 @@
super.initialize(group);
mPopup = null;
mSecondPopup = null;
- float sweep = FLOAT_PI_DIVIDED_BY_TWO / 2;
- addItem(CameraSettings.KEY_FLASH_MODE, FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep);
- addItem(CameraSettings.KEY_EXPOSURE, 3 * FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep);
- addItem(CameraSettings.KEY_WHITE_BALANCE, 3 * FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep);
+ float sweep = (float) (SWEEP * Math.PI);
+ PieItem item = null;
+ // flash
+ if (group.findPreference(CameraSettings.KEY_FLASH_MODE) != null) {
+ item = makeItem(CameraSettings.KEY_FLASH_MODE, CENTER - sweep, sweep);
+ mRenderer.addItem(item);
+ }
+ // exposure compensation
+ item = makeItem(CameraSettings.KEY_EXPOSURE, CENTER + sweep, sweep);
+ mRenderer.addItem(item);
+ // camera switcher
if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) {
- PieItem item = makeItem(R.drawable.ic_switch_photo_facing_holo_light);
- item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep);
+ item = makeItem(R.drawable.ic_switch_photo_facing_holo_light);
+ item.setFixedSlice(CENTER - 2 * sweep, sweep);
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(PieItem item) {
@@ -79,10 +85,11 @@
});
mRenderer.addItem(item);
}
+ // hdr
if (group.findPreference(CameraSettings.KEY_CAMERA_HDR) != null) {
- PieItem hdr = makeItem(R.drawable.ic_hdr);
- hdr.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO, sweep);
- hdr.setOnClickListener(new OnClickListener() {
+ item = makeItem(R.drawable.ic_hdr);
+ item.setFixedSlice(CENTER + 2 * sweep, sweep);
+ item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(PieItem item) {
// Find the index of next camera.
@@ -96,8 +103,18 @@
}
}
});
- mRenderer.addItem(hdr);
+ mRenderer.addItem(item);
}
+
+ // more settings
+ PieItem more = makeItem(R.drawable.ic_settings_holo_light);
+ more.setFixedSlice(CENTER, sweep);
+ mRenderer.addItem(more);
+ // white balance
+ item = makeItem(CameraSettings.KEY_WHITE_BALANCE,
+ CENTER + sweep, sweep);
+ more.addItem(item);
+ // settings popup
mOtherKeys = new String[] {
CameraSettings.KEY_SCENE_MODE,
CameraSettings.KEY_RECORD_LOCATION,
@@ -106,8 +123,8 @@
CameraSettings.KEY_TIMER,
CameraSettings.KEY_TIMER_SOUND_EFFECTS,
};
- PieItem item = makeItem(R.drawable.ic_settings_holo_light);
- item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO * 3, sweep);
+ item = makeItem(R.drawable.ic_settings_holo_light);
+ item.setFixedSlice(CENTER, sweep);
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(PieItem item) {
@@ -117,7 +134,7 @@
mUI.showPopup(mPopup);
}
});
- mRenderer.addItem(item);
+ more.addItem(item);
}
@Override
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index de0468b..1af870a 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -56,7 +56,8 @@
PreviewGestures.SingleTapListener,
FocusUI,
LocationManager.Listener,
- FaceDetectionListener {
+ FaceDetectionListener,
+ PreviewGestures.SwipeListener {
private static final String TAG = "CAM_UI";
@@ -175,7 +176,8 @@
}
if (mGestures == null) {
// this will handle gesture disambiguation and dispatching
- mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
+ mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
+ this);
}
mGestures.clearTouchReceivers();
mGestures.setRenderOverlay(mRenderOverlay);
@@ -196,20 +198,24 @@
updateOnScreenIndicators(params, prefs);
}
+ private void openMenu() {
+ if (mPieRenderer != null) {
+ // If autofocus is not finished, cancel autofocus so that the
+ // subsequent touch can be handled by PreviewGestures
+ if (mController.getCameraState() == PhotoController.FOCUSING) {
+ mController.cancelAutoFocus();
+ }
+ mPieRenderer.showInCenter();
+ }
+ }
+
public void initializeControlByIntent() {
mBlocker = mActivity.findViewById(R.id.blocker);
mMenuButton = mActivity.findViewById(R.id.menu);
mMenuButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- if (mPieRenderer != null) {
- // If autofocus is not finished, cancel autofocus so that the
- // subsequent touch can be handled by PreviewGestures
- if (mController.getCameraState() == PhotoController.FOCUSING) {
- mController.cancelAutoFocus();
- }
- mPieRenderer.showInCenter();
- }
+ openMenu();
}
});
if (mController.isImageCaptureIntent()) {
@@ -723,4 +729,11 @@
mFaceView.setFaces(faces);
}
+ @Override
+ public void onSwipe(int direction) {
+ if (direction == PreviewGestures.DIR_UP) {
+ openMenu();
+ }
+ }
+
}
diff --git a/src/com/android/camera/PieController.java b/src/com/android/camera/PieController.java
index 8202fca..2145fd8 100644
--- a/src/com/android/camera/PieController.java
+++ b/src/com/android/camera/PieController.java
@@ -37,6 +37,10 @@
protected static final int MODE_PHOTO = 0;
protected static final int MODE_VIDEO = 1;
+ protected static float CENTER = (float) Math.PI / 2;
+ protected static final float SWEEP = 0.06f;
+
+
protected CameraActivity mActivity;
protected PreferenceGroup mPreferenceGroup;
protected OnPreferenceChangedListener mListener;
@@ -84,10 +88,10 @@
return new PieItem(drawable, 0);
}
- public void addItem(String prefKey, float center, float sweep) {
+ public PieItem makeItem(String prefKey, float center, float sweep) {
final IconListPreference pref =
(IconListPreference) mPreferenceGroup.findPreference(prefKey);
- if (pref == null) return;
+ if (pref == null) return null;
int[] iconIds = pref.getLargeIconIds();
int resid = -1;
if (!pref.getUseSingleIcon() && iconIds != null) {
@@ -101,7 +105,6 @@
PieItem item = makeItem(resid);
// use center and sweep to determine layout
item.setFixedSlice(center, sweep);
- mRenderer.addItem(item);
mPreferences.add(pref);
mPreferenceMap.put(pref, item);
int nOfEntries = pref.getEntries().length;
@@ -113,6 +116,7 @@
} else {
inner = makeItem(pref.getEntries()[i]);
}
+ layoutInner(inner, i, nOfEntries);
item.addItem(inner);
final int index = i;
inner.setOnClickListener(new OnClickListener() {
@@ -125,6 +129,23 @@
});
}
}
+ return item;
+ }
+
+ public PieItem makeDialItem(ListPreference pref, int iconId, float center, float sweep) {
+ PieItem item = makeItem(iconId);
+ return item;
+ }
+
+ protected void layoutInner(PieItem item, int ix, int n) {
+ float sweep = (float) (SWEEP * Math.PI);//FLOAT_PI_DIVIDED_BY_TWO / Math.max(n, 5);
+ float start = CENTER + (n - 1) * (sweep / 2f);
+ item.setFixedSlice(start - ix * sweep, sweep);
+ }
+
+ public void addItem(String prefKey, float center, float sweep) {
+ PieItem item = makeItem(prefKey, center, sweep);
+ mRenderer.addItem(item);
}
public void setPreferenceGroup(PreferenceGroup group) {
diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java
index f6b6228..351da0a 100644
--- a/src/com/android/camera/PreviewGestures.java
+++ b/src/com/android/camera/PreviewGestures.java
@@ -18,7 +18,6 @@
import android.os.Handler;
import android.os.Message;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
@@ -44,6 +43,12 @@
private static final int MODE_ZOOM = 2;
private static final int MODE_MODULE = 3;
private static final int MODE_ALL = 4;
+ private static final int MODE_SWIPE = 5;
+
+ public static final int DIR_UP = 0;
+ public static final int DIR_DOWN = 1;
+ public static final int DIR_LEFT = 2;
+ public static final int DIR_RIGHT = 3;
private CameraActivity mActivity;
private SingleTapListener mTapListener;
@@ -61,6 +66,7 @@
private boolean mZoomOnly;
private int mOrientation;
private int[] mLocation;
+ private SwipeListener mSwipeListener;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
@@ -76,8 +82,12 @@
public void onSingleTapUp(View v, int x, int y);
}
+ interface SwipeListener {
+ public void onSwipe(int direction);
+ }
+
public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener,
- ZoomRenderer zoom, PieRenderer pie) {
+ ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) {
mActivity = ctx;
mTapListener = tapListener;
mPie = pie;
@@ -88,6 +98,7 @@
mTapTimeout = ViewConfiguration.getTapTimeout();
mEnabled = true;
mLocation = new int[2];
+ mSwipeListener = swipe;
}
public void setRenderOverlay(RenderOverlay overlay) {
@@ -149,6 +160,11 @@
}
} else if (mMode == MODE_NONE) {
return false;
+ } else if (mMode == MODE_SWIPE) {
+ if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+ mSwipeListener.onSwipe(getSwipeDirection(m));
+ }
+ return true;
} else if (mMode == MODE_PIE) {
if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
sendToPie(makeCancelEvent(m));
@@ -215,18 +231,13 @@
|| Math.abs(m.getY() - mDown.getY()) > mSlop) {
// moved too far and no timeout yet, no focus or pie
cancelPie();
- if (isSwipe(m, true)) {
+ int dir = getSwipeDirection(m);
+ if (dir == DIR_LEFT) {
mMode = MODE_MODULE;
return mActivity.superDispatchTouchEvent(m);
} else {
cancelActivityTouchHandling(m);
- if (isSwipe(m , false)) {
- mMode = MODE_NONE;
- } else if (!mZoomOnly) {
- mMode = MODE_PIE;
- openPie();
- sendToPie(m);
- }
+ mMode = MODE_NONE;
}
}
}
@@ -246,32 +257,31 @@
}
// left tests for finger moving right to left
- private boolean isSwipe(MotionEvent m, boolean left) {
+ private int getSwipeDirection(MotionEvent m) {
float dx = 0;
float dy = 0;
switch (mOrientation) {
case 0:
dx = m.getX() - mDown.getX();
- dy = Math.abs(m.getY() - mDown.getY());
+ dy = m.getY() - mDown.getY();
break;
case 90:
dx = - (m.getY() - mDown.getY());
- dy = Math.abs(m.getX() - mDown.getX());
+ dy = m.getX() - mDown.getX();
break;
case 180:
dx = -(m.getX() - mDown.getX());
- dy = Math.abs(m.getY() - mDown.getY());
+ dy = m.getY() - mDown.getY();
break;
case 270:
dx = m.getY() - mDown.getY();
- dy = Math.abs(m.getX() - mDown.getX());
+ dy = m.getX() - mDown.getX();
break;
}
- if (left) {
- return (dx < 0 && dy / -dx < 0.6f);
- } else {
- return (dx > 0 && dy / dx < 0.6f);
- }
+ if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT;
+ if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT;
+ if (dy > 0) return DIR_DOWN;
+ return DIR_UP;
}
private boolean isInside(MotionEvent evt, View v) {
diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java
index aa3a807..0f987aa 100644
--- a/src/com/android/camera/VideoMenu.java
+++ b/src/com/android/camera/VideoMenu.java
@@ -34,7 +34,6 @@
TimeIntervalPopup.Listener {
private static String TAG = "CAM_VideoMenu";
- private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2;
private VideoUI mUI;
private String[] mOtherKeys;
@@ -54,12 +53,13 @@
super.initialize(group);
mPopup = null;
mPopupStatus = POPUP_NONE;
- float sweep = FLOAT_PI_DIVIDED_BY_TWO / 2;
+ float sweep = (float)(SWEEP * Math.PI);
- addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep);
- addItem(CameraSettings.KEY_WHITE_BALANCE, 3 * FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep);
+ addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, CENTER - sweep, sweep);
+ addItem(CameraSettings.KEY_WHITE_BALANCE, CENTER + sweep, sweep);
+ // camera switcher
PieItem item = makeItem(R.drawable.ic_switch_video_facing_holo_light);
- item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep);
+ item.setFixedSlice(CENTER - 2 * sweep, sweep);
item.setOnClickListener(new OnClickListener() {
@Override
@@ -76,15 +76,15 @@
}
});
mRenderer.addItem(item);
+ // settings popup
mOtherKeys = new String[] {
CameraSettings.KEY_VIDEO_EFFECT,
CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
CameraSettings.KEY_VIDEO_QUALITY,
CameraSettings.KEY_RECORD_LOCATION
};
-
item = makeItem(R.drawable.ic_settings_holo_light);
- item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO * 3, sweep);
+ item.setFixedSlice(CENTER, sweep);
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(PieItem item) {
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index 0eac49d..bb615e9 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -44,7 +44,8 @@
import java.util.List;
public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener,
- PreviewGestures.SingleTapListener {
+ PreviewGestures.SingleTapListener,
+ PreviewGestures.SwipeListener {
private final static String TAG = "CAM_VideoUI";
// module fields
private CameraActivity mActivity;
@@ -210,7 +211,7 @@
}
mRenderOverlay.addRenderer(mZoomRenderer);
if (mGestures == null) {
- mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
+ mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this);
}
mGestures.setRenderOverlay(mRenderOverlay);
mGestures.clearTouchReceivers();
@@ -279,6 +280,12 @@
}
}
+ private void openMenu() {
+ if (mPieRenderer != null) {
+ mPieRenderer.showInCenter();
+ }
+ }
+
public void showPopup(AbstractSettingPopup popup) {
mActivity.hideUI();
mBlocker.setVisibility(View.INVISIBLE);
@@ -513,4 +520,12 @@
public void onZoomEnd() {
}
}
+
+ @Override
+ public void onSwipe(int direction) {
+ if (direction == PreviewGestures.DIR_UP) {
+ openMenu();
+ }
+ }
+
}
diff --git a/src/com/android/camera/ui/PieItem.java b/src/com/android/camera/ui/PieItem.java
index 677e5ac..bbfa1dc 100644
--- a/src/com/android/camera/ui/PieItem.java
+++ b/src/com/android/camera/ui/PieItem.java
@@ -57,7 +57,9 @@
public PieItem(Drawable drawable, int level) {
mDrawable = drawable;
this.level = level;
- setAlpha(1f);
+ if (drawable != null) {
+ setAlpha(1f);
+ }
mEnabled = true;
setAnimationAngle(getAnimationAngle());
start = -1;
@@ -79,6 +81,10 @@
mItems.add(item);
}
+ public void clearItems() {
+ mItems = null;
+ }
+
public void setPath(Path p) {
mPath = p;
}
diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java
index b60d9f6..dffa47e 100644
--- a/src/com/android/camera/ui/PieRenderer.java
+++ b/src/com/android/camera/ui/PieRenderer.java
@@ -27,6 +27,7 @@
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
@@ -54,6 +55,8 @@
private static final int STATE_FINISHING = 2;
private static final int STATE_PIE = 8;
+ private static final float MATH_PI_2 = (float)(Math.PI / 2);
+
private Runnable mDisappear = new Disappear();
private Animation.AnimationListener mEndAction = new EndAction();
private static final int SCALING_UP_TIME = 600;
@@ -66,26 +69,26 @@
private static final long PIE_FADE_IN_DURATION = 200;
private static final long PIE_XFADE_DURATION = 200;
private static final long PIE_SELECT_FADE_DURATION = 300;
+ private static final long PIE_OPEN_SUB_DELAY = 400;
private static final int MSG_OPEN = 0;
private static final int MSG_CLOSE = 1;
- private static final float PIE_SWEEP = (float)(Math.PI * 2 / 3);
+ private static final int MSG_OPENSUBMENU = 2;
+ private static final float PIE_SWEEP = (float)(Math.PI / 2);
// geometry
- private Point mCenter;
+ private Point mSliceCenter;
private int mRadius;
- private int mRadiusInc;
// the detection if touch is inside a slice is offset
// inbounds by this amount to allow the selection to show before the
// finger covers it
private int mTouchOffset;
- private List<PieItem> mItems;
-
- private PieItem mOpenItem;
+ private List<PieItem> mOpen;
private Paint mSelectedPaint;
private Paint mSubPaint;
+ private Paint mMenuArcPaint;
// touch handling
private PieItem mCurrentItem;
@@ -98,6 +101,11 @@
private int mFocusY;
private int mCenterX;
private int mCenterY;
+ private int mPieCenterX;
+ private int mPieCenterY;
+ private int mIconRadius;
+ private int mArcRadius;
+ private int mArcOffset;
private int mDialAngle;
private RectF mCircle;
@@ -124,7 +132,7 @@
switch(msg.what) {
case MSG_OPEN:
if (mListener != null) {
- mListener.onPieOpened(mCenter.x, mCenter.y);
+ mListener.onPieOpened(mPieCenterX, mPieCenterY);
}
break;
case MSG_CLOSE:
@@ -132,7 +140,11 @@
mListener.onPieClosed();
}
break;
+ case MSG_OPENSUBMENU:
+ onEnterOpen();
+ break;
}
+
}
};
@@ -153,13 +165,13 @@
private void init(Context ctx) {
setVisible(false);
- mItems = new ArrayList<PieItem>();
+ mOpen = new ArrayList<PieItem>();
+ mOpen.add(new PieItem(null, 0));
Resources res = ctx.getResources();
mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
- mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
- mCenter = new Point(0,0);
+ mSliceCenter = new Point(0,0);
mSelectedPaint = new Paint();
mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
mSelectedPaint.setAntiAlias(true);
@@ -184,6 +196,18 @@
mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
mDown = new Point();
+ mMenuArcPaint = new Paint();
+ mMenuArcPaint.setAntiAlias(true);
+ mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255));
+ mMenuArcPaint.setStrokeWidth(10);
+ mMenuArcPaint.setStyle(Paint.Style.STROKE);
+ mIconRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
+ mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius);
+ mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset);
+ }
+
+ private PieItem getRoot() {
+ return mOpen.get(0);
}
public boolean showsItems() {
@@ -192,15 +216,11 @@
public void addItem(PieItem item) {
// add the item to the pie itself
- mItems.add(item);
- }
-
- public void removeItem(PieItem item) {
- mItems.remove(item);
+ getRoot().addItem(item);
}
public void clearItems() {
- mItems.clear();
+ getRoot().clearItems();
}
public void showInCenter() {
@@ -212,7 +232,8 @@
cancelFocus();
}
mState = STATE_PIE;
- setCenter(mCenterX, mCenterY);
+ resetPieCenter();
+ setCenter(mPieCenterX, mPieCenterY);
mTapMode = true;
show(true);
}
@@ -231,10 +252,16 @@
mState = STATE_PIE;
// ensure clean state
mCurrentItem = null;
- mOpenItem = null;
- for (PieItem item : mItems) {
- item.setSelected(false);
+ PieItem root = getRoot();
+ for (PieItem openItem : mOpen) {
+ if (openItem.hasItems()) {
+ for (PieItem item : openItem.getItems()) {
+ item.setSelected(false);
+ }
+ }
}
+ mOpen.clear();
+ mOpen.add(root);
layoutPie();
fadeIn();
} else {
@@ -270,22 +297,41 @@
}
public void setCenter(int x, int y) {
- mCenter.x = x;
- mCenter.y = y;
- // when using the pie menu, align the focus ring
- alignFocus(x, y);
+ mPieCenterX = x;
+ mPieCenterY = y;
+ mSliceCenter.x = x;
+ mSliceCenter.y = y - mArcOffset + mIconRadius;
+ }
+
+ @Override
+ public void layout(int l, int t, int r, int b) {
+ super.layout(l, t, r, b);
+ mCenterX = (r - l) / 2;
+ mCenterY = (b - t) / 2;
+
+ mFocusX = mCenterX;
+ mFocusY = mCenterY;
+ resetPieCenter();
+ setCircle(mFocusX, mFocusY);
+ if (isVisible() && mState == STATE_PIE) {
+ setCenter(mPieCenterX, mPieCenterY);
+ layoutPie();
+ }
+ }
+
+ private void resetPieCenter() {
+ mPieCenterX = mCenterX;
+ mPieCenterY = mCenterY + mCenterY / 3;
}
private void layoutPie() {
- int rgap = 2;
- int inner = mRadius + rgap;
- int outer = mRadius + mRadiusInc - rgap;
+ int inner = mIconRadius;
+ int outer = inner + mTouchOffset;
int gap = 1;
- layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap);
+ layoutItems(0, getRoot().getItems(), (float) (Math.PI / 2), inner, outer, gap);
}
- private void layoutItems(List<PieItem> items, float centerAngle, int inner,
- int outer, int gap) {
+ private void layoutItems(int level, List<PieItem> items, float centerAngle, int inner, int outer, int gap) {
float emptyangle = PIE_SWEEP / 16;
float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size();
float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2;
@@ -298,8 +344,10 @@
break;
}
}
+ Point p = new Point(mSliceCenter);
+ p.y -= level * mTouchOffset;
Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap,
- outer, inner, mCenter);
+ outer, inner, p);
for (PieItem item : items) {
// shared between items
item.setPath(path);
@@ -311,14 +359,14 @@
// move views to outer border
int r = inner + (outer - inner) * 2 / 3;
int x = (int) (r * Math.cos(angle));
- int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2;
- x = mCenter.x + x - w / 2;
+ int y = mSliceCenter.y - (level * mTouchOffset) - (int) (r * Math.sin(angle)) - h / 2;
+ x = mSliceCenter.x + x - w / 2;
item.setBounds(x, y, x + w, y + h);
float itemstart = angle - sweep / 2;
item.setGeometry(itemstart, sweep, inner, outer);
if (item.hasItems()) {
- layoutItems(item.getItems(), angle, inner,
- outer + mRadiusInc / 2, gap);
+ layoutItems(level + 1, item.getItems(), MATH_PI_2, inner,
+ outer, gap);
}
angle += sweep;
}
@@ -378,6 +426,31 @@
mOverlay.startAnimation(mFadeOut);
}
+ // root does not count
+ private boolean hasOpenItem() {
+ return mOpen.size() > 1;
+ }
+
+ // pop an item of the open item stack
+ private PieItem closeOpenItem() {
+ PieItem item = getOpenItem();
+ mOpen.remove(mOpen.size() -1);
+ return item;
+ }
+
+ private PieItem getOpenItem() {
+ return mOpen.get(mOpen.size() - 1);
+ }
+
+ // return the children either the root or parent of the current open item
+ private PieItem getParent() {
+ return mOpen.get(Math.max(0, mOpen.size() - 2));
+ }
+
+ private int getLevel() {
+ return mOpen.size() - 1;
+ }
+
@Override
public void onDraw(Canvas canvas) {
float alpha = 1;
@@ -391,39 +464,55 @@
int state = canvas.save();
if (mFadeIn != null) {
float sf = 0.9f + alpha * 0.1f;
- canvas.scale(sf, sf, mCenter.x, mCenter.y);
+ canvas.scale(sf, sf, mPieCenterX, mPieCenterY);
}
- drawFocus(canvas);
+ if (mState != STATE_PIE) {
+ drawFocus(canvas);
+ }
if (mState == STATE_FINISHING) {
canvas.restoreToCount(state);
return;
}
- if ((mOpenItem == null) || (mXFade != null)) {
+ if (!hasOpenItem() || (mXFade != null)) {
// draw base menu
- for (PieItem item : mItems) {
- drawItem(canvas, item, alpha);
+ drawArc(canvas, getLevel());
+ for (PieItem item : getParent().getItems()) {
+ drawItem(Math.max(0, mOpen.size() - 2), canvas, item, alpha);
}
}
- if (mOpenItem != null) {
- for (PieItem inner : mOpenItem.getItems()) {
+ if (hasOpenItem()) {
+ int level = getLevel();
+ drawArc(canvas, level);
+ for (PieItem inner : getOpenItem().getItems()) {
if (mFadeOut != null) {
- drawItem(canvas, inner, alpha);
+ drawItem(level, canvas, inner, alpha);
} else {
- drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
+ drawItem(level, canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
}
}
}
canvas.restoreToCount(state);
}
- private void drawItem(Canvas canvas, PieItem item, float alpha) {
+ private void drawArc(Canvas canvas, int level) {
+ // arc
+ if (mState == STATE_PIE) {
+ int nr = mArcRadius;
+ int cy = mPieCenterY - mArcOffset + mArcRadius - level * mTouchOffset;
+ canvas.drawArc(new RectF(mPieCenterX - nr, cy - mArcRadius,
+ mPieCenterX + nr, cy + mArcRadius),
+ 252, 36, false, mMenuArcPaint);
+ }
+ }
+ private void drawItem(int level, Canvas canvas, PieItem item, float alpha) {
if (mState == STATE_PIE) {
if (item.getPath() != null) {
+ int y = mSliceCenter.y - level * mTouchOffset;
if (item.isSelected()) {
Paint p = mSelectedPaint;
int state = canvas.save();
float r = getDegrees(item.getStartAngle());
- canvas.rotate(r, mCenter.x, mCenter.y);
+ canvas.rotate(r, mSliceCenter.x, y);
if (mFadeOut != null) {
p.setAlpha((int)(255 * alpha));
}
@@ -448,7 +537,7 @@
float x = evt.getX();
float y = evt.getY();
int action = evt.getActionMasked();
- PointF polar = getPolar(x, y, !(mTapMode));
+ PointF polar = getPolar(x, y, !mTapMode);
if (MotionEvent.ACTION_DOWN == action) {
mDown.x = (int) evt.getX();
mDown.y = (int) evt.getY();
@@ -469,7 +558,7 @@
PieItem item = mCurrentItem;
if (mTapMode) {
item = findItem(polar);
- if (item != null && mOpening) {
+ if (mOpening) {
mOpening = false;
return true;
}
@@ -477,10 +566,11 @@
if (item == null) {
mTapMode = false;
show(false);
- } else if (!mOpening
- && !item.hasItems()) {
- startFadeOut(item);
- mTapMode = false;
+ } else if (!mOpening && !item.hasItems()) {
+ startFadeOut(item);
+ mTapMode = false;
+ } else {
+ mTapMode = true;
}
return true;
}
@@ -489,11 +579,17 @@
show(false);
}
deselect();
+ mHandler.removeMessages(MSG_OPENSUBMENU);
return false;
} else if (MotionEvent.ACTION_MOVE == action) {
- if (polar.y < mRadius) {
- if (mOpenItem != null) {
- mOpenItem = null;
+ if (pulledToCenter(polar)) {
+ mHandler.removeMessages(MSG_OPENSUBMENU);
+ if (hasOpenItem()) {
+ if (mCurrentItem != null) {
+ mCurrentItem.setSelected(false);
+ }
+ closeOpenItem();
+ mCurrentItem = null;
} else {
deselect();
}
@@ -502,23 +598,63 @@
PieItem item = findItem(polar);
boolean moved = hasMoved(evt);
if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
+ mHandler.removeMessages(MSG_OPENSUBMENU);
// only select if we didn't just open or have moved past slop
- mOpening = false;
if (moved) {
// switch back to swipe mode
mTapMode = false;
}
- onEnter(item);
+ onEnterSelect(item);
+ mHandler.sendEmptyMessageDelayed(MSG_OPENSUBMENU, PIE_OPEN_SUB_DELAY);
}
}
return false;
}
+ private boolean pulledToCenter(PointF polarCoords) {
+ return polarCoords.y < mIconRadius - mArcOffset;
+ }
+
+ private PointF getPolar(float x, float y, boolean useOffset) {
+ PointF res = new PointF();
+ // get angle and radius from x/y
+ res.x = (float) Math.PI / 2;
+ x = x - mSliceCenter.x;
+ y = mSliceCenter.y - getLevel() * mTouchOffset - y;
+ res.y = (float) Math.sqrt(x * x + y * y);
+ if (x != 0) {
+ res.x = (float) Math.atan2(y, x);
+ if (res.x < 0) {
+ res.x = (float) (2 * Math.PI + res.x);
+ }
+ }
+ res.y = res.y + (useOffset ? mTouchOffset : 0);
+ return res;
+ }
+
private boolean hasMoved(MotionEvent e) {
return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
+ (e.getY() - mDown.y) * (e.getY() - mDown.y);
}
+ private void onEnterSelect(PieItem item) {
+ if (mCurrentItem != null) {
+ mCurrentItem.setSelected(false);
+ }
+ if (item != null && item.isEnabled()) {
+ item.setSelected(true);
+ mCurrentItem = item;
+ } else {
+ mCurrentItem = null;
+ }
+ }
+
+ private void onEnterOpen() {
+ if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
+ openCurrentItem();
+ }
+ }
+
/**
* enter a slice for a view
* updates model only
@@ -531,7 +667,7 @@
if (item != null && item.isEnabled()) {
item.setSelected(true);
mCurrentItem = item;
- if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) {
+ if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
openCurrentItem();
}
} else {
@@ -543,22 +679,24 @@
if (mCurrentItem != null) {
mCurrentItem.setSelected(false);
}
- if (mOpenItem != null) {
- mOpenItem = null;
+ if (hasOpenItem()) {
+ PieItem item = closeOpenItem();
+ onEnter(item);
+ } else {
+ mCurrentItem = null;
}
- mCurrentItem = null;
}
private void openCurrentItem() {
if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
- mCurrentItem.setSelected(false);
- mOpenItem = mCurrentItem;
+ mOpen.add(mCurrentItem);
mOpening = true;
if (mFadeIn != null) {
mFadeIn.cancel();
}
mXFade = new LinearAnimation(1, 0);
mXFade.setDuration(PIE_XFADE_DURATION);
+ final PieItem ci = mCurrentItem;
mXFade.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
@@ -567,6 +705,8 @@
@Override
public void onAnimationEnd(Animation animation) {
mXFade = null;
+ ci.setSelected(false);
+ mOpening = false;
}
@Override
@@ -578,30 +718,13 @@
}
}
- private PointF getPolar(float x, float y, boolean useOffset) {
- PointF res = new PointF();
- // get angle and radius from x/y
- res.x = (float) Math.PI / 2;
- x = x - mCenter.x;
- y = mCenter.y - y;
- res.y = (float) Math.sqrt(x * x + y * y);
- if (x != 0) {
- res.x = (float) Math.atan2(y, x);
- if (res.x < 0) {
- res.x = (float) (2 * Math.PI + res.x);
- }
- }
- res.y = res.y + (useOffset ? mTouchOffset : 0);
- return res;
- }
-
/**
* @param polar x: angle, y: dist
* @return the item at angle/dist or null
*/
private PieItem findItem(PointF polar) {
// find the matching item:
- List<PieItem> items = (mOpenItem != null) ? mOpenItem.getItems() : mItems;
+ List<PieItem> items = getOpenItem().getItems();
for (PieItem item : items) {
if (inside(polar, item)) {
return item;
@@ -656,20 +779,6 @@
return (int)(-60 + 120 * Math.random());
}
- @Override
- public void layout(int l, int t, int r, int b) {
- super.layout(l, t, r, b);
- mCenterX = (r - l) / 2;
- mCenterY = (b - t) / 2;
- mFocusX = mCenterX;
- mFocusY = mCenterY;
- setCircle(mFocusX, mFocusY);
- if (isVisible() && mState == STATE_PIE) {
- setCenter(mCenterX, mCenterY);
- layoutPie();
- }
- }
-
private void setCircle(int cx, int cy) {
mCircle.set(cx - mCircleSize, cy - mCircleSize,
cx + mCircleSize, cy + mCircleSize);
@@ -849,7 +958,6 @@
}
}
-
private class LinearAnimation extends Animation {
private float mFrom;
private float mTo;
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 8c5d7a4..dbf26e4 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -402,7 +402,15 @@
private void setupBorders() {
LinearLayout list = (LinearLayout) findViewById(R.id.listBorders);
Vector<FilterRepresentation> borders = new Vector<FilterRepresentation>();
+ ImageButton borderButton = (ImageButton) findViewById(R.id.borderButton);
+
+ // The "no border" implementation
borders.add(new FilterImageBorderRepresentation(0));
+
+ // Google-build borders
+ FiltersManager.getManager().addBorders(borders);
+
+ // Regular borders
borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_4x5));
borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_brush));
borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_grunge));
@@ -424,7 +432,6 @@
if (i == 0) {
filter.setName(getString(R.string.none));
}
- ImageButton borderButton = (ImageButton) findViewById(R.id.borderButton);
FilterIconButton b = setupFilterRepresentationButton(filter, list, borderButton);
if (i == 0) {
mNullBorderFilter = b;
@@ -998,17 +1005,12 @@
saveImage();
}
});
- builder.setNeutralButton(R.string.exit, new DialogInterface.OnClickListener() {
+ builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
done();
}
});
- builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- }
- });
-
- AlertDialog dialog = builder.show();
+ builder.show();
}
}
}
diff --git a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
index ecfdaba..8c312a9 100644
--- a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
@@ -23,6 +23,7 @@
import android.support.v8.renderscript.RenderScript;
import android.util.Log;
import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
import com.android.gallery3d.filtershow.filters.ImageFilterRS;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
@@ -57,6 +58,8 @@
private volatile float mHighResPreviewScaleFactor = 1.0f;
private volatile String mName = "";
+ private ImageFilterGeometry mGeometry = null;
+
public CachingPipeline(FiltersManager filtersManager, String name) {
mFiltersManager = filtersManager;
mName = name;
@@ -302,17 +305,15 @@
}
}
- public synchronized Bitmap renderGeometryIcon(Bitmap bitmap, ImagePreset preset) {
- synchronized (CachingPipeline.class) {
- if (getRenderScriptContext() == null) {
- return bitmap;
- }
- setupEnvironment(preset, false);
- mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
- mFiltersManager.freeFilterResources(preset);
- bitmap = preset.applyGeometry(bitmap, mEnvironment);
- return bitmap;
+ public Bitmap renderGeometryIcon(Bitmap bitmap, ImagePreset preset) {
+ // Called by RenderRequest on the main thread
+ // TODO: change this -- we should reuse a pool of bitmaps instead...
+ if (mGeometry == null) {
+ mGeometry = new ImageFilterGeometry();
}
+ mGeometry.useRepresentation(preset.getGeometry());
+ return mGeometry.apply(bitmap, mPreviewScaleFactor,
+ ImagePreset.QUALITY_PREVIEW);
}
public synchronized void compute(TripleBufferBitmap buffer, ImagePreset preset, int type) {
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorFlip.java b/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
index c996dcb..de6240c 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
@@ -17,13 +17,18 @@
package com.android.gallery3d.filtershow.editors;
import android.content.Context;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.imageshow.ImageFlip;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
public class EditorFlip extends Editor implements EditorInfo {
+ public static final String LOGTAG = "EditorFlip";
public static final int ID = R.id.editorFlip;
ImageFlip mImageFlip;
@@ -44,6 +49,18 @@
}
@Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mImageFlip.flip();
+ mImageFlip.saveAndSetPreset();
+ }
+ });
+ }
+
+ @Override
public int getTextId() {
return R.string.mirror;
}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRotate.java b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
index e495558..a637c08 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
@@ -17,13 +17,18 @@
package com.android.gallery3d.filtershow.editors;
import android.content.Context;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.imageshow.ImageRotate;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
public class EditorRotate extends Editor implements EditorInfo {
+ public static final String LOGTAG = "EditorRotate";
public static final int ID = R.id.editorRotate;
ImageRotate mImageRotate;
@@ -44,6 +49,19 @@
}
@Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mImageRotate.rotate();
+ button.setText(mContext.getString(getTextId()) + " " + mImageRotate.getLocalValue());
+ mImageRotate.saveAndSetPreset();
+ }
+ });
+ }
+
+ @Override
public int getTextId() {
return R.string.rotate;
}
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
index b5c4de0..232de64 100644
--- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -108,6 +108,10 @@
filters.add(ImageFilterGeometry.class);
}
+ public void addBorders(Vector<FilterRepresentation> representations) {
+ // Override
+ }
+
public void addLooks(Vector<FilterRepresentation> representations) {
// Override
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
index 70637a3..15197b0 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
@@ -58,6 +58,22 @@
return (rot / 90) % 2 != 0;
}
+ public void flip() {
+ FLIP flip = getLocalFlip();
+ boolean next = true;
+ // Picks next flip in order from enum FLIP (wrapping)
+ for (FLIP f : FLIP.values()) {
+ if (next) {
+ mNextFlip = f;
+ next = false;
+ }
+ if (f.equals(flip)) {
+ next = true;
+ }
+ }
+ setLocalFlip(mNextFlip);
+ }
+
@Override
protected void setActionMove(float x, float y) {
super.setActionMove(x, y);
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
index c4b9aa2..ab8023e 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
@@ -54,6 +54,13 @@
mAngle = (mBaseAngle - angle) % 360;
}
+ public void rotate() {
+ mAngle += 90;
+ mAngle = snappedAngle(mAngle);
+ mAngle %= 360;
+ setLocalRotation(mAngle);
+ }
+
@Override
protected void setActionDown(float x, float y) {
super.setActionDown(x, y);
@@ -76,7 +83,7 @@
}
@Override
- protected int getLocalValue() {
+ public int getLocalValue() {
return constrainedRotation(snappedAngle(getLocalRotation()));
}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index b2dd6b7..a35a18a 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -117,15 +117,24 @@
if (filterRepresentation == null) {
return null;
}
- int position = getPositionForRepresentation(filterRepresentation);
- if (position == -1) {
- return null;
- }
FilterRepresentation representation = null;
- try {
- representation = mFilters.elementAt(position).clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
+ if ((mBorder != null)
+ && (mBorder.getFilterClass() == filterRepresentation.getFilterClass())) {
+ // TODO: instead of special casing for border, we should correctly implements "filters priority set"
+ representation = mBorder;
+ } else {
+ int position = getPositionForRepresentation(filterRepresentation);
+ if (position == -1) {
+ return null;
+ }
+ representation = mFilters.elementAt(position);
+ }
+ if (representation != null) {
+ try {
+ representation = representation.clone();
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
}
return representation;
}
@@ -138,9 +147,17 @@
if (representation instanceof GeometryMetadata) {
setGeometry((GeometryMetadata) representation);
} else {
- int position = getPositionForRepresentation(representation);
- FilterRepresentation old = mFilters.elementAt(position);
- old.updateTempParametersFrom(representation);
+ if ((mBorder != null)
+ && (mBorder.getFilterClass() == representation.getFilterClass())) {
+ mBorder.updateTempParametersFrom(representation);
+ } else {
+ int position = getPositionForRepresentation(representation);
+ if (position == -1) {
+ return;
+ }
+ FilterRepresentation old = mFilters.elementAt(position);
+ old.updateTempParametersFrom(representation);
+ }
}
}
MasterImage.getImage().invalidatePreview();
diff --git a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
index 5e1fb34..30868c2 100644
--- a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
+++ b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
@@ -38,16 +38,28 @@
public static Bitmap getThumbnail(MtpDevice device, MtpObjectInfo info) {
byte[] imageBytes = device.getThumbnail(info.getObjectHandle());
- if (imageBytes == null) return null;
+ if (imageBytes == null) {
+ return null;
+ }
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
- if (o.outWidth == 0 || o.outHeight == 0) return null;
+ if (o.outWidth == 0 || o.outHeight == 0) {
+ return null;
+ }
o.inBitmap = GalleryBitmapPool.getInstance().get(o.outWidth, o.outHeight);
o.inMutable = true;
o.inJustDecodeBounds = false;
o.inSampleSize = 1;
- return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+ try {
+ return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+ } catch (IllegalArgumentException e) {
+ // BitmapFactory throws an exception rather than returning null
+ // when image decoding fails and an existing bitmap was supplied
+ // for recycling, even if the failure was not caused by the use
+ // of that bitmap.
+ return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
+ }
}
public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info) {
@@ -56,7 +68,9 @@
public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info, int maxSide) {
byte[] imageBytes = device.getObject(info.getObjectHandle(), info.getCompressedSize());
- if (imageBytes == null) return null;
+ if (imageBytes == null) {
+ return null;
+ }
Bitmap created;
if (maxSide > 0) {
BitmapFactory.Options o = new BitmapFactory.Options();
@@ -76,7 +90,9 @@
} else {
created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}
- if (created == null) return null;
+ if (created == null) {
+ return null;
+ }
return new BitmapWithMetadata(created, Exif.getOrientation(imageBytes));
}
diff --git a/src/com/android/photos/data/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java
index 4c3279f..a5a17ed 100644
--- a/src/com/android/photos/data/GalleryBitmapPool.java
+++ b/src/com/android/photos/data/GalleryBitmapPool.java
@@ -106,7 +106,7 @@
}
public boolean put(Bitmap b) {
- if (b == null) {
+ if (b == null || b.getConfig() != Bitmap.Config.ARGB_8888) {
return false;
}
SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight());