diff options
4 files changed, 288 insertions, 397 deletions
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 0114a419f23c..23337f03792c 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -226,17 +226,12 @@ public class GestureDetector { */ 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 @@ public class GestureDetector { * @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 @@ public class GestureDetector { * @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 @@ public class GestureDetector { } 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 @@ public class GestureDetector { } } - mLastMotionX = x; - mLastMotionY = y; + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; if (mCurrentDownEvent != null) { mCurrentDownEvent.recycle(); } @@ -525,22 +522,22 @@ public class GestureDetector { 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 @@ public class GestureDetector { } } 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 @@ public class GestureDetector { // 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 @@ public class GestureDetector { } } + 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 73f94bc2f530..dc3608894115 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 @@ public class ScaleGestureDetector { } } - /** - * 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 @@ public class ScaleGestureDetector { 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 @@ public class ScaleGestureDetector { 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; - } + 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; } - } 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 (streamComplete) { + return true; } } - if (!handled && mInputEventConsistencyVerifier != null) { - mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); + 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; + + // 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); } - 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; - } + 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); } - return -1; - } - - private void setContext(MotionEvent curr) { - if (mCurrEvent != null) { - mCurrEvent.recycle(); + 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); } - 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); + // 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 (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) { - mInvalidGesture = true; - Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable()); - if (mGestureInProgress) { - mListener.onScaleEnd(this); + if (updatePrev) { + mPrevSpanX = mCurrSpanX; + mPrevSpanY = mCurrSpanY; + mPrevSpan = mCurrSpan; } - return; } - 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 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; - } - if (mCurrEvent != null) { - mCurrEvent.recycle(); - mCurrEvent = null; - } - mGestureInProgress = false; - mActiveId0 = -1; - mActiveId1 = -1; - mInvalidGesture = false; + 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 @@ public class ScaleGestureDetector { /** * 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 @@ public class ScaleGestureDetector { } /** - * 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 @@ public class ScaleGestureDetector { * @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 @@ public class ScaleGestureDetector { * @return Time difference since the last scaling event in milliseconds. */ public long getTimeDelta() { - return mTimeDelta; + return mCurrTime - mPrevTime; } /** @@ -561,6 +381,6 @@ public class ScaleGestureDetector { * @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 5cacd23b8bf6..3d88f5088c2a 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 @@ public final class BearerData { 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 @@ public final class BearerData { 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 @@ public final class BearerData { 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 @@ public final class BearerData { 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 58e73e0dabbc..f1bc2688f6f7 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.test.suitebuilder.annotation.SmallTest; 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 @@ public class CdmaSmsTest extends AndroidTestCase { @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 @@ public class CdmaSmsTest extends AndroidTestCase { 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); + } + } } } |