Merge "DO NOT MERGE Remove dead code" into jb-dev
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 0114a41..23337f0 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -226,17 +226,12 @@
*/
private boolean mIsDoubleTapping;
- private float mLastMotionY;
- private float mLastMotionX;
+ private float mLastFocusX;
+ private float mLastFocusY;
+ private float mDownFocusX;
+ private float mDownFocusY;
private boolean mIsLongpressEnabled;
-
- /**
- * True if we are at a target API level of >= Froyo or the developer can
- * explicitly set it. If true, input events with > 1 pointer will be ignored
- * so we can work side by side with multitouch gesture detectors.
- */
- private boolean mIgnoreMultitouch;
/**
* Determines speed during touch scrolling
@@ -349,8 +344,16 @@
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
- this(context, listener, handler, context != null &&
- context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
+ if (handler != null) {
+ mHandler = new GestureHandler(handler);
+ } else {
+ mHandler = new GestureHandler();
+ }
+ mListener = listener;
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
+ init(context);
}
/**
@@ -362,31 +365,19 @@
* @param listener the listener invoked for all the callbacks, this must
* not be null.
* @param handler the handler to use
- * @param ignoreMultitouch whether events involving more than one pointer should
- * be ignored.
*
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
- boolean ignoreMultitouch) {
- if (handler != null) {
- mHandler = new GestureHandler(handler);
- } else {
- mHandler = new GestureHandler();
- }
- mListener = listener;
- if (listener instanceof OnDoubleTapListener) {
- setOnDoubleTapListener((OnDoubleTapListener) listener);
- }
- init(context, ignoreMultitouch);
+ boolean unused) {
+ this(context, listener, handler);
}
- private void init(Context context, boolean ignoreMultitouch) {
+ private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
- mIgnoreMultitouch = ignoreMultitouch;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
@@ -456,34 +447,40 @@
}
final int action = ev.getAction();
- final float y = ev.getY();
- final float x = ev.getX();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = ev.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += ev.getX(i);
+ sumY += ev.getY(i);
+ }
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX = sumX / div;
+ final float focusY = sumY / div;
+
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
- if (mIgnoreMultitouch) {
- // Multitouch event - abort.
- cancel();
- }
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
+ // Cancel long press and taps
+ cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
- // Ending a multitouch gesture and going back to 1 finger
- if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
- int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
- mLastMotionX = ev.getX(index);
- mLastMotionY = ev.getY(index);
- mVelocityTracker.recycle();
- mVelocityTracker = VelocityTracker.obtain();
- }
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
break;
case MotionEvent.ACTION_DOWN:
@@ -504,8 +501,8 @@
}
}
- mLastMotionX = x;
- mLastMotionY = y;
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
@@ -525,22 +522,22 @@
break;
case MotionEvent.ACTION_MOVE:
- if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) {
+ if (mInLongPress) {
break;
}
- final float scrollX = mLastMotionX - x;
- final float scrollY = mLastMotionY - y;
+ final float scrollX = mLastFocusX - focusX;
+ final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
- final int deltaX = (int) (x - mCurrentDownEvent.getX());
- final int deltaY = (int) (y - mCurrentDownEvent.getY());
+ final int deltaX = (int) (focusX - mDownFocusX);
+ final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- mLastMotionX = x;
- mLastMotionY = y;
+ mLastFocusX = focusX;
+ mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
@@ -551,8 +548,8 @@
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- mLastMotionX = x;
- mLastMotionY = y;
+ mLastFocusX = focusX;
+ mLastFocusY = focusY;
}
break;
@@ -571,9 +568,10 @@
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
+ final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
- final float velocityY = velocityTracker.getYVelocity();
- final float velocityX = velocityTracker.getXVelocity();
+ final float velocityY = velocityTracker.getYVelocity(pointerId);
+ final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)){
@@ -622,6 +620,18 @@
}
}
+ private void cancelTaps() {
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ mIsDoubleTapping = false;
+ mAlwaysInTapRegion = false;
+ mAlwaysInBiggerTapRegion = false;
+ if (mInLongPress) {
+ mInLongPress = false;
+ }
+ }
+
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 73f94bc..dc36088 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -17,14 +17,13 @@
package android.view;
import android.content.Context;
-import android.util.DisplayMetrics;
import android.util.FloatMath;
-import android.util.Log;
/**
- * Detects transformation gestures involving more than one pointer ("multitouch")
- * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
- * callback will notify users when a particular gesture event has occurred.
+ * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
+ * The {@link OnScaleGestureListener} callback will notify users when a particular
+ * gesture event has occurred.
+ *
* This class should only be used with {@link MotionEvent}s reported via touch.
*
* To use this class:
@@ -121,43 +120,21 @@
}
}
- /**
- * This value is the threshold ratio between our previous combined pressure
- * and the current combined pressure. We will only fire an onScale event if
- * the computed ratio between the current and previous event pressures is
- * greater than this value. When pressure decreases rapidly between events
- * the position values can often be imprecise, as it usually indicates
- * that the user is in the process of lifting a pointer off of the device.
- * Its value was tuned experimentally.
- */
- private static final float PRESSURE_THRESHOLD = 0.67f;
-
private final Context mContext;
private final OnScaleGestureListener mListener;
- private boolean mGestureInProgress;
-
- private MotionEvent mPrevEvent;
- private MotionEvent mCurrEvent;
private float mFocusX;
private float mFocusY;
- private float mPrevFingerDiffX;
- private float mPrevFingerDiffY;
- private float mCurrFingerDiffX;
- private float mCurrFingerDiffY;
- private float mCurrLen;
- private float mPrevLen;
- private float mScaleFactor;
- private float mCurrPressure;
- private float mPrevPressure;
- private long mTimeDelta;
- private boolean mInvalidGesture;
-
- // Pointer IDs currently responsible for the two fingers controlling the gesture
- private int mActiveId0;
- private int mActiveId1;
- private boolean mActive0MostRecent;
+ private float mCurrSpan;
+ private float mPrevSpan;
+ private float mCurrSpanX;
+ private float mCurrSpanY;
+ private float mPrevSpanX;
+ private float mPrevSpanY;
+ private long mCurrTime;
+ private long mPrevTime;
+ private boolean mInProgress;
/**
* Consistency verifier for debugging purposes.
@@ -171,6 +148,18 @@
mListener = listener;
}
+ /**
+ * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
+ * when appropriate.
+ *
+ * <p>Applications should pass a complete and consistent event stream to this method.
+ * A complete and consistent event stream involves all MotionEvents from the initial
+ * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+ *
+ * @param event The event to process
+ * @return true if the event was processed and the detector wants to receive the
+ * rest of the MotionEvents in this event stream.
+ */
public boolean onTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
@@ -178,265 +167,110 @@
final int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- reset(); // Start fresh
- }
-
- boolean handled = true;
- if (mInvalidGesture) {
- handled = false;
- } else if (!mGestureInProgress) {
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- mActiveId0 = event.getPointerId(0);
- mActive0MostRecent = true;
- }
- break;
-
- case MotionEvent.ACTION_UP:
- reset();
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN: {
- // We have a new multi-finger gesture
- if (mPrevEvent != null) mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- mTimeDelta = 0;
-
- int index1 = event.getActionIndex();
- int index0 = event.findPointerIndex(mActiveId0);
- mActiveId1 = event.getPointerId(index1);
- if (index0 < 0 || index0 == index1) {
- // Probably someone sending us a broken event stream.
- index0 = findNewActiveIndex(event, mActiveId1, -1);
- mActiveId0 = event.getPointerId(index0);
- }
- mActive0MostRecent = false;
-
- setContext(event);
-
- mGestureInProgress = mListener.onScaleBegin(this);
- break;
- }
- }
- } else {
- // Transform gesture in progress - attempt to handle it
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN: {
- // End the old gesture and begin a new one with the most recent two fingers.
- mListener.onScaleEnd(this);
- final int oldActive0 = mActiveId0;
- final int oldActive1 = mActiveId1;
- reset();
-
- mPrevEvent = MotionEvent.obtain(event);
- mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1;
- mActiveId1 = event.getPointerId(event.getActionIndex());
- mActive0MostRecent = false;
-
- int index0 = event.findPointerIndex(mActiveId0);
- if (index0 < 0 || mActiveId0 == mActiveId1) {
- // Probably someone sending us a broken event stream.
- Log.e(TAG, "Got " + MotionEvent.actionToString(action) +
- " with bad state while a gesture was in progress. " +
- "Did you forget to pass an event to " +
- "ScaleGestureDetector#onTouchEvent?");
- index0 = findNewActiveIndex(event, mActiveId1, -1);
- mActiveId0 = event.getPointerId(index0);
- }
-
- setContext(event);
-
- mGestureInProgress = mListener.onScaleBegin(this);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP: {
- final int pointerCount = event.getPointerCount();
- final int actionIndex = event.getActionIndex();
- final int actionId = event.getPointerId(actionIndex);
-
- boolean gestureEnded = false;
- if (pointerCount > 2) {
- if (actionId == mActiveId0) {
- final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
- if (newIndex >= 0) {
- mListener.onScaleEnd(this);
- mActiveId0 = event.getPointerId(newIndex);
- mActive0MostRecent = true;
- mPrevEvent = MotionEvent.obtain(event);
- setContext(event);
- mGestureInProgress = mListener.onScaleBegin(this);
- } else {
- gestureEnded = true;
- }
- } else if (actionId == mActiveId1) {
- final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
- if (newIndex >= 0) {
- mListener.onScaleEnd(this);
- mActiveId1 = event.getPointerId(newIndex);
- mActive0MostRecent = false;
- mPrevEvent = MotionEvent.obtain(event);
- setContext(event);
- mGestureInProgress = mListener.onScaleBegin(this);
- } else {
- gestureEnded = true;
- }
- }
- mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- setContext(event);
- } else {
- gestureEnded = true;
- }
-
- if (gestureEnded) {
- // Gesture ended
- setContext(event);
-
- // Set focus point to the remaining finger
- final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
- final int index = event.findPointerIndex(activeId);
- mFocusX = event.getX(index);
- mFocusY = event.getY(index);
-
- mListener.onScaleEnd(this);
- reset();
- mActiveId0 = activeId;
- mActive0MostRecent = true;
- }
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mListener.onScaleEnd(this);
- reset();
- break;
-
- case MotionEvent.ACTION_UP:
- reset();
- break;
-
- case MotionEvent.ACTION_MOVE: {
- setContext(event);
-
- // Only accept the event if our relative pressure is within
- // a certain limit - this can help filter shaky data as a
- // finger is lifted.
- if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
- final boolean updatePrevious = mListener.onScale(this);
-
- if (updatePrevious) {
- mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- }
- }
- }
- break;
- }
- }
-
- if (!handled && mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
- }
- return handled;
- }
-
- private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) {
- final int pointerCount = ev.getPointerCount();
-
- // It's ok if this isn't found and returns -1, it simply won't match.
- final int otherActiveIndex = ev.findPointerIndex(otherActiveId);
-
- // Pick a new id and update tracking state.
- for (int i = 0; i < pointerCount; i++) {
- if (i != removedPointerIndex && i != otherActiveIndex) {
- return i;
- }
- }
- return -1;
- }
-
- private void setContext(MotionEvent curr) {
- if (mCurrEvent != null) {
- mCurrEvent.recycle();
- }
- mCurrEvent = MotionEvent.obtain(curr);
-
- mCurrLen = -1;
- mPrevLen = -1;
- mScaleFactor = -1;
-
- final MotionEvent prev = mPrevEvent;
-
- final int prevIndex0 = prev.findPointerIndex(mActiveId0);
- final int prevIndex1 = prev.findPointerIndex(mActiveId1);
- final int currIndex0 = curr.findPointerIndex(mActiveId0);
- final int currIndex1 = curr.findPointerIndex(mActiveId1);
-
- if (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) {
- mInvalidGesture = true;
- Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable());
- if (mGestureInProgress) {
+ final boolean streamComplete = action == MotionEvent.ACTION_UP ||
+ action == MotionEvent.ACTION_CANCEL;
+ if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+ // Reset any scale in progress with the listener.
+ // If it's an ACTION_DOWN we're beginning a new event stream.
+ // This means the app probably didn't give us all the events. Shame on it.
+ if (mInProgress) {
mListener.onScaleEnd(this);
+ mInProgress = false;
}
- return;
+
+ if (streamComplete) {
+ return true;
+ }
}
- final float px0 = prev.getX(prevIndex0);
- final float py0 = prev.getY(prevIndex0);
- final float px1 = prev.getX(prevIndex1);
- final float py1 = prev.getY(prevIndex1);
- final float cx0 = curr.getX(currIndex0);
- final float cy0 = curr.getY(currIndex0);
- final float cx1 = curr.getX(currIndex1);
- final float cy1 = curr.getY(currIndex1);
+ final boolean configChanged =
+ action == MotionEvent.ACTION_POINTER_UP ||
+ action == MotionEvent.ACTION_POINTER_DOWN;
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
- final float pvx = px1 - px0;
- final float pvy = py1 - py0;
- final float cvx = cx1 - cx0;
- final float cvy = cy1 - cy0;
- mPrevFingerDiffX = pvx;
- mPrevFingerDiffY = pvy;
- mCurrFingerDiffX = cvx;
- mCurrFingerDiffY = cvy;
-
- mFocusX = cx0 + cvx * 0.5f;
- mFocusY = cy0 + cvy * 0.5f;
- mTimeDelta = curr.getEventTime() - prev.getEventTime();
- mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1);
- mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1);
- }
-
- private void reset() {
- if (mPrevEvent != null) {
- mPrevEvent.recycle();
- mPrevEvent = null;
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = event.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
}
- if (mCurrEvent != null) {
- mCurrEvent.recycle();
- mCurrEvent = null;
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX = sumX / div;
+ final float focusY = sumY / div;
+
+ // Determine average deviation from focal point
+ float devSumX = 0, devSumY = 0;
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ devSumX += Math.abs(event.getX(i) - focusX);
+ devSumY += Math.abs(event.getY(i) - focusY);
}
- mGestureInProgress = false;
- mActiveId0 = -1;
- mActiveId1 = -1;
- mInvalidGesture = false;
+ final float devX = devSumX / div;
+ final float devY = devSumY / div;
+
+ // Span is the average distance between touch points through the focal point;
+ // i.e. the diameter of the circle with a radius of the average deviation from
+ // the focal point.
+ final float spanX = devX * 2;
+ final float spanY = devY * 2;
+ final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
+
+ // Dispatch begin/end events as needed.
+ // If the configuration changes, notify the app to reset its current state by beginning
+ // a fresh scale event stream.
+ if (mInProgress && (span == 0 || configChanged)) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
+ }
+ if (configChanged) {
+ mPrevSpanX = mCurrSpanX = spanX;
+ mPrevSpanY = mCurrSpanY = spanY;
+ mPrevSpan = mCurrSpan = span;
+ }
+ if (!mInProgress && span != 0) {
+ mFocusX = focusX;
+ mFocusY = focusY;
+ mInProgress = mListener.onScaleBegin(this);
+ }
+
+ // Handle motion; focal point and span/scale factor are changing.
+ if (action == MotionEvent.ACTION_MOVE) {
+ mCurrSpanX = spanX;
+ mCurrSpanY = spanY;
+ mCurrSpan = span;
+ mFocusX = focusX;
+ mFocusY = focusY;
+
+ boolean updatePrev = true;
+ if (mInProgress) {
+ updatePrev = mListener.onScale(this);
+ }
+
+ if (updatePrev) {
+ mPrevSpanX = mCurrSpanX;
+ mPrevSpanY = mCurrSpanY;
+ mPrevSpan = mCurrSpan;
+ }
+ }
+
+ return true;
}
/**
- * Returns {@code true} if a two-finger scale gesture is in progress.
- * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
+ * Returns {@code true} if a scale gesture is in progress.
*/
public boolean isInProgress() {
- return mGestureInProgress;
+ return mInProgress;
}
/**
* Get the X coordinate of the current gesture's focal point.
- * If a gesture is in progress, the focal point is directly between
- * the two pointers forming the gesture.
- * If a gesture is ending, the focal point is the location of the
- * remaining pointer on the screen.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
* If {@link #isInProgress()} would return false, the result of this
* function is undefined.
*
@@ -448,10 +282,9 @@
/**
* Get the Y coordinate of the current gesture's focal point.
- * If a gesture is in progress, the focal point is directly between
- * the two pointers forming the gesture.
- * If a gesture is ending, the focal point is the location of the
- * remaining pointer on the screen.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
* If {@link #isInProgress()} would return false, the result of this
* function is undefined.
*
@@ -462,73 +295,63 @@
}
/**
- * Return the current distance between the two pointers forming the
- * gesture in progress.
+ * Return the average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpan() {
- if (mCurrLen == -1) {
- final float cvx = mCurrFingerDiffX;
- final float cvy = mCurrFingerDiffY;
- mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
- }
- return mCurrLen;
+ return mCurrSpan;
}
/**
- * Return the current x distance between the two pointers forming the
- * gesture in progress.
+ * Return the average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpanX() {
- return mCurrFingerDiffX;
+ return mCurrSpanX;
}
/**
- * Return the current y distance between the two pointers forming the
- * gesture in progress.
+ * Return the average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpanY() {
- return mCurrFingerDiffY;
+ return mCurrSpanY;
}
/**
- * Return the previous distance between the two pointers forming the
- * gesture in progress.
+ * Return the previous average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpan() {
- if (mPrevLen == -1) {
- final float pvx = mPrevFingerDiffX;
- final float pvy = mPrevFingerDiffY;
- mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
- }
- return mPrevLen;
+ return mPrevSpan;
}
/**
- * Return the previous x distance between the two pointers forming the
- * gesture in progress.
+ * Return the previous average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpanX() {
- return mPrevFingerDiffX;
+ return mPrevSpanX;
}
/**
- * Return the previous y distance between the two pointers forming the
- * gesture in progress.
+ * Return the previous average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpanY() {
- return mPrevFingerDiffY;
+ return mPrevSpanY;
}
/**
@@ -539,10 +362,7 @@
* @return The current scaling factor.
*/
public float getScaleFactor() {
- if (mScaleFactor == -1) {
- mScaleFactor = getCurrentSpan() / getPreviousSpan();
- }
- return mScaleFactor;
+ return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
}
/**
@@ -552,7 +372,7 @@
* @return Time difference since the last scaling event in milliseconds.
*/
public long getTimeDelta() {
- return mTimeDelta;
+ return mCurrTime - mPrevTime;
}
/**
@@ -561,6 +381,6 @@
* @return Current event time in milliseconds.
*/
public long getEventTime() {
- return mCurrEvent.getEventTime();
+ return mCurrTime;
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 5cacd23..3d88f50 100755
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -595,7 +595,6 @@
byte[] payload = encodeUtf16(uData.payloadStr);
int udhBytes = udhData.length + 1; // Add length octet.
int udhCodeUnits = (udhBytes + 1) / 2;
- int udhPadding = udhBytes % 2;
int payloadCodeUnits = payload.length / 2;
uData.msgEncoding = UserData.ENCODING_UNICODE_16;
uData.msgEncodingSet = true;
@@ -603,7 +602,7 @@
uData.payload = new byte[uData.numFields * 2];
uData.payload[0] = (byte)udhData.length;
System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
- System.arraycopy(payload, 0, uData.payload, udhBytes + udhPadding, payload.length);
+ System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
}
private static void encodeEmsUserDataPayload(UserData uData)
@@ -997,27 +996,37 @@
private static String decodeUtf8(byte[] data, int offset, int numFields)
throws CodingException
{
- if (numFields < 0 || (numFields + offset) > data.length) {
- throw new CodingException("UTF-8 decode failed: offset or length out of range");
- }
- try {
- return new String(data, offset, numFields, "UTF-8");
- } catch (java.io.UnsupportedEncodingException ex) {
- throw new CodingException("UTF-8 decode failed: " + ex);
- }
+ return decodeCharset(data, offset, numFields, 1, "UTF-8");
}
private static String decodeUtf16(byte[] data, int offset, int numFields)
throws CodingException
{
- int byteCount = numFields * 2;
- if (byteCount < 0 || (byteCount + offset) > data.length) {
- throw new CodingException("UTF-16 decode failed: offset or length out of range");
+ // Subtract header and possible padding byte (at end) from num fields.
+ int padding = offset % 2;
+ numFields -= (offset + padding) / 2;
+ return decodeCharset(data, offset, numFields, 2, "utf-16be");
+ }
+
+ private static String decodeCharset(byte[] data, int offset, int numFields, int width,
+ String charset) throws CodingException
+ {
+ if (numFields < 0 || (numFields * width + offset) > data.length) {
+ // Try to decode the max number of characters in payload
+ int padding = offset % width;
+ int maxNumFields = (data.length - offset - padding) / width;
+ if (maxNumFields < 0) {
+ throw new CodingException(charset + " decode failed: offset out of range");
+ }
+ Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
+ + numFields + " data.length = " + data.length + " maxNumFields = "
+ + maxNumFields);
+ numFields = maxNumFields;
}
try {
- return new String(data, offset, byteCount, "utf-16be");
+ return new String(data, offset, numFields * width, charset);
} catch (java.io.UnsupportedEncodingException ex) {
- throw new CodingException("UTF-16 decode failed: " + ex);
+ throw new CodingException(charset + " decode failed: " + ex);
}
}
@@ -1073,14 +1082,7 @@
private static String decodeLatin(byte[] data, int offset, int numFields)
throws CodingException
{
- if (numFields < 0 || (numFields + offset) > data.length) {
- throw new CodingException("ISO-8859-1 decode failed: offset or length out of range");
- }
- try {
- return new String(data, offset, numFields, "ISO-8859-1");
- } catch (java.io.UnsupportedEncodingException ex) {
- throw new CodingException("ISO-8859-1 decode failed: " + ex);
- }
+ return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
}
private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
index 58e73e0..f1bc268 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
@@ -35,10 +35,21 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
public class CdmaSmsTest extends AndroidTestCase {
private final static String LOG_TAG = "XXX CdmaSmsTest XXX";
+ // CJK ideographs, Hiragana, Katakana, full width letters, Cyrillic, etc.
+ private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" +
+ "\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" +
+ "\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" +
+ "\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8" +
+ "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18" +
+ "\uff70\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78" +
+ "\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" +
+ "\u00a2\u00a9\u00ae\u2122";
+
@SmallTest
public void testCdmaSmsAddrParsing() throws Exception {
CdmaSmsAddress addr = CdmaSmsAddress.parse("6502531000");
@@ -811,23 +822,51 @@
@SmallTest
public void testUserDataHeaderWithEightCharMsg() throws Exception {
+ encodeDecodeAssertEquals("01234567", 2, 2, false);
+ }
+
+ private void encodeDecodeAssertEquals(String payload, int index, int total,
+ boolean oddLengthHeader) throws Exception {
BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
bearerData.messageId = 55;
- SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
- concatRef.refNumber = 0xEE;
- concatRef.msgCount = 2;
- concatRef.seqNumber = 2;
- concatRef.isEightBits = true;
SmsHeader smsHeader = new SmsHeader();
- smsHeader.concatRef = concatRef;
+ if (oddLengthHeader) {
+ // Odd length header to verify correct UTF-16 header padding
+ SmsHeader.MiscElt miscElt = new SmsHeader.MiscElt();
+ miscElt.id = 0x27; // reserved for future use; ignored on decode
+ miscElt.data = new byte[]{0x12, 0x34};
+ smsHeader.miscEltList.add(miscElt);
+ } else {
+ // Even length header normally generated for concatenated SMS.
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 0xEE;
+ concatRef.msgCount = total;
+ concatRef.seqNumber = index;
+ concatRef.isEightBits = true;
+ smsHeader.concatRef = concatRef;
+ }
+ byte[] encodeHeader = SmsHeader.toByteArray(smsHeader);
+ if (oddLengthHeader) {
+ assertEquals(4, encodeHeader.length); // 5 bytes with UDH length
+ } else {
+ assertEquals(5, encodeHeader.length); // 6 bytes with UDH length
+ }
UserData userData = new UserData();
- userData.payloadStr = "01234567";
+ userData.payloadStr = payload;
userData.userDataHeader = smsHeader;
bearerData.userData = userData;
byte[] encodedSms = BearerData.encode(bearerData);
BearerData revBearerData = BearerData.decode(encodedSms);
assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ assertTrue(revBearerData.hasUserDataHeader);
+ byte[] header = SmsHeader.toByteArray(revBearerData.userData.userDataHeader);
+ if (oddLengthHeader) {
+ assertEquals(4, header.length); // 5 bytes with UDH length
+ } else {
+ assertEquals(5, header.length); // 6 bytes with UDH length
+ }
+ assertTrue(Arrays.equals(encodeHeader, header));
}
@SmallTest
@@ -881,7 +920,27 @@
if (isCdmaPhone) {
ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text2);
assertEquals(3, fragments.size());
+
+ for (int i = 0; i < 3; i++) {
+ encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, false);
+ encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, true);
+ }
}
+ // Test case for multi-part UTF-16 message.
+ String text3 = sUnicodeChars + sUnicodeChars + sUnicodeChars;
+ ted = SmsMessage.calculateLength(text3, false);
+ assertEquals(3, ted.msgCount);
+ assertEquals(189, ted.codeUnitCount);
+ assertEquals(3, ted.codeUnitSize);
+ if (isCdmaPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text3);
+ assertEquals(3, fragments.size());
+
+ for (int i = 0; i < 3; i++) {
+ encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, false);
+ encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, true);
+ }
+ }
}
}