Merge "Add instrumentation as a source tag for proguard keep options." into eclair
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
new file mode 100644
index 0000000..2da8f14
--- /dev/null
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+
+/**
+ * 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.
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ * 
+ * To use this class:
+ * <ul>
+ *  <li>Create an instance of the {@code ScaleGestureDetector} for your
+ *      {@link View}
+ *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ *          callback will be executed when the events occur.
+ * </ul>
+ * @hide Pending API approval
+ */
+public class ScaleGestureDetector {
+    /**
+     * The listener for receiving notifications when gestures occur.
+     * If you want to listen for all the different gestures then implement
+     * this interface. If you only want to listen for a subset it might
+     * be easier to extend {@link SimpleOnScaleGestureListener}.
+     * 
+     * An application will receive events in the following order:
+     * <ul>
+     *  <li>One {@link OnScaleGestureListener#onScaleBegin()}
+     *  <li>Zero or more {@link OnScaleGestureListener#onScale()}
+     *  <li>One {@link OnScaleGestureListener#onTransformEnd()}
+     * </ul>
+     */
+    public interface OnScaleGestureListener {
+        /**
+         * Responds to scaling events for a gesture in progress.
+         * Reported by pointer motion.
+         * 
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         * @return Whether or not the detector should consider this event
+         *          as handled. If an event was not handled, the detector
+         *          will continue to accumulate movement until an event is
+         *          handled. This can be useful if an application, for example,
+         *          only wants to update scaling factors if the change is
+         *          greater than 0.01.
+         */
+        public boolean onScale(ScaleGestureDetector detector);
+
+        /**
+         * Responds to the beginning of a scaling gesture. Reported by
+         * new pointers going down.
+         * 
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         * @return Whether or not the detector should continue recognizing
+         *          this gesture. For example, if a gesture is beginning
+         *          with a focal point outside of a region where it makes
+         *          sense, onScaleBegin() may return false to ignore the
+         *          rest of the gesture.
+         */
+        public boolean onScaleBegin(ScaleGestureDetector detector);
+
+        /**
+         * Responds to the end of a scale gesture. Reported by existing
+         * pointers going up. If the end of a gesture would result in a fling,
+         * {@link onTransformFling()} is called instead.
+         * 
+         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+         * and {@link ScaleGestureDetector#getFocusY()} will return the location
+         * of the pointer remaining on the screen.
+         * 
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         */
+        public void onScaleEnd(ScaleGestureDetector detector);
+    }
+    
+    /**
+     * A convenience class to extend when you only want to listen for a subset
+     * of scaling-related events. This implements all methods in
+     * {@link OnScaleGestureListener} but does nothing.
+     * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and
+     * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return
+     * {@code true}. 
+     */
+    public class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+        public boolean onScale(ScaleGestureDetector detector) {
+            return true;
+        }
+
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            return true;
+        }
+
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            // Intentionally empty
+        }
+    }
+
+    private static final float PRESSURE_THRESHOLD = 0.67f;
+
+    private Context mContext;
+    private 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;
+
+    public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+        mContext = context;
+        mListener = listener;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        final int action = event.getAction();
+        boolean handled = true;
+
+        if (!mGestureInProgress) {
+            if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
+                    action == MotionEvent.ACTION_POINTER_2_DOWN) &&
+                    event.getPointerCount() >= 2) {
+                // We have a new multi-finger gesture
+                
+                // Be paranoid in case we missed an event
+                reset();
+                
+                mPrevEvent = MotionEvent.obtain(event);
+                mTimeDelta = 0;
+                
+                setContext(event);
+                mGestureInProgress = mListener.onScaleBegin(this);
+            }
+        } else {
+            // Transform gesture in progress - attempt to handle it
+            switch (action) {
+                case MotionEvent.ACTION_POINTER_1_UP:
+                case MotionEvent.ACTION_POINTER_2_UP:
+                    // Gesture ended
+                    setContext(event);
+                    
+                    // Set focus point to the remaining finger
+                    int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+                            >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+                    mFocusX = event.getX(id);
+                    mFocusY = event.getY(id);
+                    
+                    mListener.onScaleEnd(this);
+                    mGestureInProgress = false;
+
+                    reset();
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                    mListener.onScaleEnd(this);
+                    mGestureInProgress = false;
+
+                    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;
+            }
+        }
+        return handled;
+    }
+
+    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 float px0 = prev.getX(0);
+        final float py0 = prev.getY(0);
+        final float px1 = prev.getX(1);
+        final float py1 = prev.getY(1);
+        final float cx0 = curr.getX(0);
+        final float cy0 = curr.getY(0);
+        final float cx1 = curr.getX(1);
+        final float cy1 = curr.getY(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(0) + curr.getPressure(1);
+        mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
+    }
+
+    private void reset() {
+        if (mPrevEvent != null) {
+            mPrevEvent.recycle();
+            mPrevEvent = null;
+        }
+        if (mCurrEvent != null) {
+            mCurrEvent.recycle();
+            mCurrEvent = null;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public boolean isInProgress() {
+        return mGestureInProgress;
+    }
+
+    /**
+     * 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 {@link isInProgress()} would return false, the result of this
+     * function is undefined.
+     * 
+     * @return X coordinate of the focal point in pixels.
+     */
+    public float getFocusX() {
+        return mFocusX;
+    }
+
+    /**
+     * 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 {@link isInProgress()} would return false, the result of this
+     * function is undefined.
+     * 
+     * @return Y coordinate of the focal point in pixels.
+     */
+    public float getFocusY() {
+        return mFocusY;
+    }
+
+    /**
+     * Return the current distance between the two pointers forming the
+     * gesture in progress.
+     * 
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpan() {
+        if (mCurrLen == -1) {
+            final float cvx = mCurrFingerDiffX;
+            final float cvy = mCurrFingerDiffY;
+            mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+        }
+        return mCurrLen;
+    }
+
+    /**
+     * Return the previous distance between the two pointers forming the
+     * gesture in progress.
+     * 
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpan() {
+        if (mPrevLen == -1) {
+            final float pvx = mPrevFingerDiffX;
+            final float pvy = mPrevFingerDiffY;
+            mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+        }
+        return mPrevLen;
+    }
+
+    /**
+     * Return the scaling factor from the previous scale event to the current
+     * event. This value is defined as
+     * ({@link getCurrentSpan()} / {@link getPreviousSpan()}).
+     * 
+     * @return The current scaling factor.
+     */
+    public float getScaleFactor() {
+        if (mScaleFactor == -1) {
+            mScaleFactor = getCurrentSpan() / getPreviousSpan();
+        }
+        return mScaleFactor;
+    }
+    
+    /**
+     * Return the time difference in milliseconds between the previous
+     * accepted scaling event and the current scaling event.
+     * 
+     * @return Time difference since the last scaling event in milliseconds.
+     */
+    public long getTimeDelta() {
+        return mTimeDelta;
+    }
+    
+    /**
+     * Return the event time of the current event being processed.
+     * 
+     * @return Current event time in milliseconds.
+     */
+    public long getEventTime() {
+        return mCurrEvent.getEventTime();
+    }
+}
diff --git a/core/java/android/view/TransformGestureDetector.java b/core/java/android/view/TransformGestureDetector.java
deleted file mode 100644
index 196716a..0000000
--- a/core/java/android/view/TransformGestureDetector.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.GestureDetector.SimpleOnGestureListener;
-
-/**
- * Detects transformation gestures involving more than one pointer ("multitouch")
- * using the supplied {@link MotionEvent}s. The {@link OnGestureListener} 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:
- * <ul>
- *  <li>Create an instance of the {@code TransformGestureDetector} for your
- *      {@link View}
- *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
- *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
- *          callback will be executed when the events occur.
- * </ul>
- * @hide Pending API approval
- */
-public class TransformGestureDetector {
-    /**
-     * The listener for receiving notifications when gestures occur.
-     * If you want to listen for all the different gestures then implement
-     * this interface. If you only want to listen for a subset it might
-     * be easier to extend {@link SimpleOnGestureListener}.
-     * 
-     * An application will receive events in the following order:
-     * One onTransformBegin()
-     * Zero or more onTransform()
-     * One onTransformEnd() or onTransformFling()
-     */
-    public interface OnTransformGestureListener {
-        /**
-         * Responds to transformation events for a gesture in progress.
-         * Reported by pointer motion.
-         * 
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return true if the event was handled, false otherwise.
-         */
-        public boolean onTransform(TransformGestureDetector detector);
-        
-        /**
-         * Responds to the beginning of a transformation gesture. Reported by
-         * new pointers going down.
-         * 
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return true if the event was handled, false otherwise.
-         */
-        public boolean onTransformBegin(TransformGestureDetector detector);
- 
-        /**
-         * Responds to the end of a transformation gesture. Reported by existing
-         * pointers going up. If the end of a gesture would result in a fling,
-         * onTransformFling is called instead.
-         * 
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return true if the event was handled, false otherwise.
-         */
-        public boolean onTransformEnd(TransformGestureDetector detector);
-
-        /**
-         * Responds to the end of a transformation gesture that begins a fling.
-         * Reported by existing pointers going up. If the end of a gesture 
-         * would not result in a fling, onTransformEnd is called instead.
-         * 
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return true if the event was handled, false otherwise.
-         */
-        public boolean onTransformFling(TransformGestureDetector detector);
-    }
-    
-    private static final boolean DEBUG = false;
-    
-    private static final int INITIAL_EVENT_IGNORES = 2;
-    
-    private Context mContext;
-    private float mTouchSizeScale;
-    private OnTransformGestureListener mListener;
-    private int mVelocityTimeUnits;
-    private MotionEvent mInitialEvent;
-    
-    private MotionEvent mPrevEvent;
-    private MotionEvent mCurrEvent;
-    private VelocityTracker mVelocityTracker;
-
-    private float mCenterX;
-    private float mCenterY;
-    private float mTransX;
-    private float mTransY;
-    private float mPrevFingerDiffX;
-    private float mPrevFingerDiffY;
-    private float mCurrFingerDiffX;
-    private float mCurrFingerDiffY;
-    private float mRotateDegrees;
-    private float mCurrLen;
-    private float mPrevLen;
-    private float mScaleFactor;
-    
-    // Units in pixels. Current value is pulled out of thin air for debugging only.
-    private float mPointerJumpLimit = 30;
-    
-    private int mEventIgnoreCount;
-    
-   public TransformGestureDetector(Context context, OnTransformGestureListener listener,
-            int velocityTimeUnits) {
-        mContext = context;
-        mListener = listener;
-        mTouchSizeScale = context.getResources().getDisplayMetrics().widthPixels/3;
-        mVelocityTimeUnits = velocityTimeUnits;
-        mEventIgnoreCount = INITIAL_EVENT_IGNORES;
-    }
-    
-    public TransformGestureDetector(Context context, OnTransformGestureListener listener) {
-        this(context, listener, 1000);
-    }
-    
-    public boolean onTouchEvent(MotionEvent event) {
-        final int action = event.getAction();
-        boolean handled = true;
-
-        if (mInitialEvent == null) {
-            // No transform gesture in progress
-            if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
-                    action == MotionEvent.ACTION_POINTER_2_DOWN) &&
-                    event.getPointerCount() >= 2) {
-                // We have a new multi-finger gesture
-                mInitialEvent = MotionEvent.obtain(event);
-                mPrevEvent = MotionEvent.obtain(event);
-                mVelocityTracker = VelocityTracker.obtain();
-                handled = mListener.onTransformBegin(this);
-            }
-        } else {
-            // Transform gesture in progress - attempt to handle it
-            switch (action) {
-                case MotionEvent.ACTION_POINTER_1_UP:
-                case MotionEvent.ACTION_POINTER_2_UP:
-                    // Gesture ended
-                    handled = mListener.onTransformEnd(this);
-
-                    reset();
-                    break;
-                    
-                case MotionEvent.ACTION_CANCEL:
-                    handled = mListener.onTransformEnd(this);
-                    
-                    reset();
-                    break;
-                    
-                case MotionEvent.ACTION_MOVE:
-                    setContext(event);
-
-                    // Our first few events can be crazy from some touchscreens - drop them.
-                    if (mEventIgnoreCount == 0) {
-                        mVelocityTracker.addMovement(event);
-                        handled = mListener.onTransform(this);
-                    } else {
-                        mEventIgnoreCount--;
-                    }
-                    
-                    mPrevEvent.recycle();
-                    mPrevEvent = MotionEvent.obtain(event);
-                    break;
-            }
-        }
-        return handled;
-    }
-    
-    private void setContext(MotionEvent curr) {
-        mCurrEvent = MotionEvent.obtain(curr);
-
-        mRotateDegrees = -1;
-        mCurrLen = -1;
-        mPrevLen = -1;
-        mScaleFactor = -1;
-
-        final MotionEvent prev = mPrevEvent;
-        
-        float px0 = prev.getX(0);
-        float py0 = prev.getY(0);
-        float px1 = prev.getX(1);
-        float py1 = prev.getY(1);
-        float cx0 = curr.getX(0);
-        float cy0 = curr.getY(0);
-        float cx1 = curr.getX(1);
-        float cy1 = curr.getY(1);
-
-        // Some touchscreens do weird things with pointer values where points are
-        // too close along one axis. Try to detect this here and smooth things out.
-        // The main indicator is that we get the X or Y value from the other pointer.
-        final float dx0 = cx0 - px0;
-        final float dy0 = cy0 - py0;
-        final float dx1 = cx1 - px1;
-        final float dy1 = cy1 - py1;
-
-        if (cx0 == cx1) {
-            if (Math.abs(dx0) > mPointerJumpLimit) {
-                 cx0 = px0;
-            } else if (Math.abs(dx1) > mPointerJumpLimit) {
-                cx1 = px1;
-            }
-        } else if (cy0 == cy1) {
-            if (Math.abs(dy0) > mPointerJumpLimit) {
-                cy0 = py0;
-            } else if (Math.abs(dy1) > mPointerJumpLimit) {
-                cy1 = py1;
-            }
-        }
-        
-        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;
-
-        final float pmidx = px0 + pvx * 0.5f;
-        final float pmidy = py0 + pvy * 0.5f;
-        final float cmidx = cx0 + cvx * 0.5f;
-        final float cmidy = cy0 + cvy * 0.5f;
-
-        mCenterX = cmidx;
-        mCenterY = cmidy;
-        mTransX = cmidx - pmidx;
-        mTransY = cmidy - pmidy;
-    }
-    
-    private void reset() {
-        if (mInitialEvent != null) {
-            mInitialEvent.recycle();
-            mInitialEvent = null;
-        }
-        if (mPrevEvent != null) {
-            mPrevEvent.recycle();
-            mPrevEvent = null;
-        }
-        if (mCurrEvent != null) {
-            mCurrEvent.recycle();
-            mCurrEvent = null;
-        }
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-        mEventIgnoreCount = INITIAL_EVENT_IGNORES;
-    }
-    
-    public float getCenterX() {
-        return mCenterX;
-    }
-
-    public float getCenterY() {
-        return mCenterY;
-    }
-
-    public float getTranslateX() {
-        return mTransX;
-    }
-
-    public float getTranslateY() {
-        return mTransY;
-    }
-
-    public float getCurrentSpan() {
-        if (mCurrLen == -1) {
-            final float cvx = mCurrFingerDiffX;
-            final float cvy = mCurrFingerDiffY;
-            mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
-        }
-        return mCurrLen;
-    }
-
-    public float getPreviousSpan() {
-        if (mPrevLen == -1) {
-            final float pvx = mPrevFingerDiffX;
-            final float pvy = mPrevFingerDiffY;
-            mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
-        }
-        return mPrevLen;
-    }
-
-    public float getScaleFactor() {
-        if (mScaleFactor == -1) {
-            mScaleFactor = getCurrentSpan() / getPreviousSpan();
-        }
-        return mScaleFactor;
-    }
-
-    public float getRotation() {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 6f3262a..093756d 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -411,6 +411,7 @@
      */
     public void setSupportZoom(boolean support) {
         mSupportZoom = support;
+        mWebView.updateMultiTouchSupport(mContext);
     }
 
     /**
@@ -425,6 +426,7 @@
      */
     public void setBuiltInZoomControls(boolean enabled) {
         mBuiltInZoomControls = enabled;
+        mWebView.updateMultiTouchSupport(mContext);
     }
     
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index f6d6d22..5b4ec60 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -50,6 +50,7 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
 import android.view.SoundEffectConstants;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -430,6 +431,18 @@
 
     private boolean mWrapContent;
 
+    // whether support multi-touch
+    private static boolean mSupportMultiTouch;
+    // use the framework's ScaleGestureDetector to handle multi-touch
+    private ScaleGestureDetector mScaleDetector;
+    // minimum scale change during multi-touch zoom
+    private static float PREVIEW_SCALE_INCREMENT = 0.01f;
+
+    // the anchor point in the document space where VIEW_SIZE_CHANGED should
+    // apply to
+    private int mAnchorX;
+    private int mAnchorY;
+
     /**
      * Private message ids
      */
@@ -747,9 +760,20 @@
                     params;
             frameParams.gravity = Gravity.RIGHT;
         }
+        updateMultiTouchSupport(context);
+    }
 
+    void updateMultiTouchSupport(Context context) {
+        WebSettings settings = getSettings();
         mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
+                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+                && settings.supportZoom() && settings.getBuiltInZoomControls();
+        if (mSupportMultiTouch && (mScaleDetector == null)) {
+            mScaleDetector = new ScaleGestureDetector(context,
+                    new ScaleDetectorListener());
+        } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
+            mScaleDetector = null;
+        }
     }
 
     private void updateZoomButtonsEnabled() {
@@ -3699,23 +3723,15 @@
     private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
     private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
 
-    // MultiTouch handling
-    private static boolean mSupportMultiTouch;
+    private class ScaleDetectorListener implements
+            ScaleGestureDetector.OnScaleGestureListener {
 
-    private double mPinchDistance;
-    private float mLastPressure;
-    private int mAnchorX;
-    private int mAnchorY;
-
-    private static float SCALE_INCREMENT = 0.01f;
-    private static float PRESSURE_THRESHOLD = 0.67f;
-
-    private boolean doMultiTouch(MotionEvent ev) {
-        int action = ev.getAction();
-
-        if ((action & 0xff) == MotionEvent.ACTION_POINTER_DOWN) {
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
             // cancel the single touch handling
             cancelTouch();
+            if (mZoomButtonsController.isVisible()) {
+                mZoomButtonsController.setVisible(false);
+            }
             // reset the zoom overview mode so that the page won't auto grow
             mInZoomOverview = false;
             // If it is in password mode, turn it off so it does not draw
@@ -3723,20 +3739,18 @@
             if (inEditingMode() && nativeFocusCandidateIsPassword()) {
                 mWebTextView.setInPassword(false);
             }
-            // start multi (2-pointer) touch
-            float x0 = ev.getX(0);
-            float y0 = ev.getY(0);
-            float x1 = ev.getX(1);
-            float y1 = ev.getY(1);
-            mPinchDistance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
-                    * (y0 - y1));
-        } else if ((action & 0xff) == MotionEvent.ACTION_POINTER_UP) {
+            return true;
+        }
+
+        public void onScaleEnd(ScaleGestureDetector detector) {
             if (mPreviewZoomOnly) {
                 mPreviewZoomOnly = false;
                 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
                 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-                // for testing only, default don't reflow now
-                boolean reflowNow = !getSettings().getPluginsEnabled();
+                // don't reflow when zoom in; when zoom out, do reflow if the
+                // new scale is almost minimum scale;
+                boolean reflowNow = (mActualScale - mMinZoomScale <= 0.01f)
+                        || ((mActualScale <= 0.8 * mTextWrapScale));
                 // force zoom after mPreviewZoomOnly is set to false so that the
                 // new view size will be passed to the WebKit
                 setNewZoomScale(mActualScale, reflowNow, true);
@@ -3751,24 +3765,14 @@
             // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
             // may trigger the unwanted fling.
             mTouchMode = TOUCH_PINCH_DRAG;
-            // action indicates which pointer is UP. Use the other one as drag's
-            // starting position.
-            int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
-                    >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
-            startTouch(ev.getX(id), ev.getY(id), ev.getEventTime());
-        } else if (action == MotionEvent.ACTION_MOVE) {
-            float x0 = ev.getX(0);
-            float y0 = ev.getY(0);
-            float x1 = ev.getX(1);
-            float y1 = ev.getY(1);
-            double distance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
-                    * (y0 - y1));
-            float scale = (float) (Math.round(distance / mPinchDistance
+            startTouch(detector.getFocusX(), detector.getFocusY(),
+                    mLastTouchTime);
+        }
+
+        public boolean onScale(ScaleGestureDetector detector) {
+            float scale = (float) (Math.round(detector.getScaleFactor()
                     * mActualScale * 100) / 100.0);
-            float pressure = ev.getPressure(0) + ev.getPressure(1);
-            if (Math.abs(scale - mActualScale) >= SCALE_INCREMENT
-                    && (!mPreviewZoomOnly
-                    || (pressure / mLastPressure) > PRESSURE_THRESHOLD)) {
+            if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
                 mPreviewZoomOnly = true;
                 // limit the scale change per step
                 if (scale > mActualScale) {
@@ -3776,18 +3780,14 @@
                 } else {
                     scale = Math.max(scale, mActualScale * 0.8f);
                 }
-                mZoomCenterX = (x0 + x1) / 2;
-                mZoomCenterY = (y0 + y1) / 2;
+                mZoomCenterX = detector.getFocusX();
+                mZoomCenterY = detector.getFocusY();
                 setNewZoomScale(scale, false, false);
                 invalidate();
-                mPinchDistance = distance;
-                mLastPressure = pressure;
+                return true;
             }
-        } else {
-            Log.w(LOGTAG, action + " should not happen during doMultiTouch");
             return false;
         }
-        return true;
     }
 
     @Override
@@ -3801,9 +3801,12 @@
                     + mTouchMode);
         }
 
-        if (mSupportMultiTouch && getSettings().supportZoom()
-                && mMinZoomScale < mMaxZoomScale && ev.getPointerCount() > 1) {
-            return doMultiTouch(ev);
+        // FIXME: we may consider to give WebKit an option to handle multi-touch
+        // events later.
+        if (mSupportMultiTouch && mMinZoomScale < mMaxZoomScale
+                && ev.getPointerCount() > 1) {
+            mLastTouchTime = ev.getEventTime();
+            return mScaleDetector.onTouchEvent(ev);
         }
 
         int action = ev.getAction();
@@ -3939,20 +3942,18 @@
                     if (!mDragFromTextInput) {
                         nativeHideCursor();
                     }
-                    if (!mSupportMultiTouch) {
-                        WebSettings settings = getSettings();
-                        if (settings.supportZoom()
-                                && settings.getBuiltInZoomControls()
-                                && !mZoomButtonsController.isVisible()
-                                && mMinZoomScale < mMaxZoomScale) {
-                            mZoomButtonsController.setVisible(true);
-                            int count = settings.getDoubleTapToastCount();
-                            if (mInZoomOverview && count > 0) {
-                                settings.setDoubleTapToastCount(--count);
-                                Toast.makeText(mContext,
-                                        com.android.internal.R.string.double_tap_toast,
-                                        Toast.LENGTH_LONG).show();
-                            }
+                    WebSettings settings = getSettings();
+                    if (settings.supportZoom()
+                            && settings.getBuiltInZoomControls()
+                            && !mZoomButtonsController.isVisible()
+                            && mMinZoomScale < mMaxZoomScale) {
+                        mZoomButtonsController.setVisible(true);
+                        int count = settings.getDoubleTapToastCount();
+                        if (mInZoomOverview && count > 0) {
+                            settings.setDoubleTapToastCount(--count);
+                            Toast.makeText(mContext,
+                                    com.android.internal.R.string.double_tap_toast,
+                                    Toast.LENGTH_LONG).show();
                         }
                     }
                 }
@@ -4031,8 +4032,7 @@
                     mUserScroll = true;
                 }
 
-                if (!mSupportMultiTouch
-                        && !getSettings().getBuiltInZoomControls()) {
+                if (!getSettings().getBuiltInZoomControls()) {
                     boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
                     if (mZoomControls != null && showPlusMinus) {
                         if (mZoomControls.getVisibility() == View.VISIBLE) {
@@ -4778,22 +4778,21 @@
         mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
         mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
         WebSettings settings = getSettings();
-        if (!mSupportMultiTouch) {
-            // remove the zoom control after double tap
-            if (settings.getBuiltInZoomControls()) {
-                if (mZoomButtonsController.isVisible()) {
-                    mZoomButtonsController.setVisible(false);
-                }
-            } else {
-                if (mZoomControlRunnable != null) {
-                    mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                }
-                if (mZoomControls != null) {
-                    mZoomControls.hide();
-                }
+        // remove the zoom control after double tap
+        if (settings.getBuiltInZoomControls()) {
+            if (mZoomButtonsController.isVisible()) {
+                mZoomButtonsController.setVisible(false);
             }
-            settings.setDoubleTapToastCount(0);
+        } else {
+            if (mZoomControlRunnable != null) {
+                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+            }
+            if (mZoomControls != null) {
+                mZoomControls.hide();
+            }
         }
+        settings.setDoubleTapToastCount(0);
+        boolean zoomToDefault = false;
         if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
                 && (Math.abs(mActualScale - mTextWrapScale) >= 0.01f)) {
             setNewZoomScale(mActualScale, true, true);
@@ -4809,9 +4808,12 @@
                 if (mScrollY < getTitleHeight()) mScrollY = 0;
                 zoomWithPreview(newScale);
             } else if (Math.abs(mActualScale - mDefaultScale) >= 0.01f) {
-                mInZoomOverview = true;
+                zoomToDefault = true;
             }
         } else {
+            zoomToDefault = true;
+        }
+        if (zoomToDefault) {
             mInZoomOverview = false;
             int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
             if (left != NO_LEFTEDGE) {
@@ -5150,7 +5152,8 @@
                         }
                         if (mInitialScaleInPercent > 0) {
                             setNewZoomScale(mInitialScaleInPercent / 100.0f,
-                                    true, false);
+                                    mInitialScaleInPercent != mTextWrapScale * 100,
+                                    false);
                         } else if (restoreState.mViewScale > 0) {
                             mTextWrapScale = restoreState.mTextWrapScale;
                             setNewZoomScale(restoreState.mViewScale, false,
@@ -5158,14 +5161,15 @@
                         } else {
                             mInZoomOverview = useWideViewport
                                     && settings.getLoadWithOverviewMode();
+                            float scale;
                             if (mInZoomOverview) {
-                                setNewZoomScale((float) viewWidth
-                                        / WebViewCore.DEFAULT_VIEWPORT_WIDTH,
-                                        true, false);
+                                scale = (float) viewWidth
+                                        / WebViewCore.DEFAULT_VIEWPORT_WIDTH;
                             } else {
-                                setNewZoomScale(restoreState.mTextWrapScale,
-                                        true, false);
+                                scale = restoreState.mTextWrapScale;
                             }
+                            setNewZoomScale(scale, Math.abs(scale
+                                    - mTextWrapScale) >= 0.01f, false);
                         }
                         setContentScrollTo(restoreState.mScrollX,
                                 restoreState.mScrollY);
@@ -5194,8 +5198,9 @@
                         mPictureListener.onNewPicture(WebView.this, capturePicture());
                     }
                     if (useWideViewport) {
-                        mZoomOverviewWidth = Math.max(draw.mMinPrefWidth,
-                                draw.mViewPoint.x);
+                        mZoomOverviewWidth = Math.max(
+                                (int) (viewWidth / mDefaultScale), Math.max(
+                                        draw.mMinPrefWidth, draw.mViewPoint.x));
                     }
                     if (!mMinZoomScaleFixed) {
                         mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
@@ -5206,7 +5211,8 @@
                         if (Math.abs((viewWidth * mInvActualScale)
                                 - mZoomOverviewWidth) > 1) {
                             setNewZoomScale((float) viewWidth
-                                    / mZoomOverviewWidth, true, false);
+                                    / mZoomOverviewWidth, Math.abs(mActualScale
+                                            - mTextWrapScale) < 0.01f, false);
                         }
                     }
                     break;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index c3817fb..8ac915c 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1518,7 +1518,7 @@
             } else if (mViewportWidth > 0) {
                 width = Math.max(w, mViewportWidth);
             } else {
-                width = Math.max(w, textwrapWidth);
+                width = textwrapWidth;
             }
         }
         nativeSetSize(width, width == w ? h : Math.round((float) width * h / w),
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cdaba8a..876359d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6301,7 +6301,8 @@
             if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
                     + " before=" + before + " after=" + after + ": " + buffer);
 
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (AccessibilityManager.getInstance(mContext).isEnabled()
+                    && !isPasswordInputType(mInputType)) {
                 mBeforeText = buffer.toString();
             }
 
@@ -6972,9 +6973,7 @@
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        boolean isPassword =
-            (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) ==
-            (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
+        final boolean isPassword = isPasswordInputType(mInputType);
 
         if (!isPassword) {
             CharSequence text = getText();
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index e55fbb8..c683e71 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -546,6 +546,11 @@
     public boolean onTouch(View v, MotionEvent event) {
         int action = event.getAction();
 
+        if (event.getPointerCount() > 1) {
+            // ZoomButtonsController doesn't handle mutitouch. Give up control.
+            return false;
+        }
+
         if (mReleaseTouchListenerOnUp) {
             // The controls were dismissed but we need to throw away all events until the up
             if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index f07b2f1..940a2e5 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -148,20 +148,6 @@
         void onGrabbedStateChange(View v, int grabbedState);
     }
 
-    // TODO: For debugging; remove after glitches debugged.
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        int orientation = getResources().getConfiguration().orientation;
-        if (mOrientation == HORIZONTAL && orientation != Configuration.ORIENTATION_PORTRAIT
-                || mOrientation == VERTICAL && orientation != Configuration.ORIENTATION_LANDSCAPE) {
-            // UBER HACK ALERT.  This is a workaround for a configuration race condition between
-            // orientation changed notification and the resize notification. This just prevents
-            // us from drawing under this circumstance, though the view will still be wrong.
-            return;
-        }
-        super.dispatchDraw(canvas);
-    }
-
     /**
      * Simple container class for all things pertinent to a slider.
      * A slider consists of 3 Views:
@@ -436,7 +422,7 @@
         /**
          * Start animating the slider. Note we need two animations since an Animator
          * keeps internal state of the invalidation region which is just the view being animated.
-         * 
+         *
          * @param anim1
          * @param anim2
          */
@@ -674,7 +660,7 @@
                     resetView();
                 }
                 anim.setAnimationListener(mAnimationDoneListener);
-                
+
                 /* Animation can be the same for these since the animation just holds */
                 mLeftSlider.startAnimation(anim, anim);
                 mRightSlider.startAnimation(anim, anim);
diff --git a/core/res/res/values-land/donottranslate.xml b/core/res/res/values-land/donottranslate.xml
new file mode 100644
index 0000000..75a7b06
--- /dev/null
+++ b/core/res/res/values-land/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- @hide DO NOT TRANSLATE. Workaround for resource race condition in lockscreen -->
+    <bool name="lockscreen_isPortrait">false</bool>
+</resources>
diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml
index 6def3bf..78d4d36d 100644
--- a/core/res/res/values/donottranslate.xml
+++ b/core/res/res/values/donottranslate.xml
@@ -20,4 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Default text encoding for WebSettings. -->
     <string name="default_text_encoding">Latin-1</string>
+    <!-- @hide DO NOT TRANSLATE. Workaround for resource race condition in lockscreen. -->
+    <bool name="lockscreen_isPortrait">true</bool>
 </resources>
diff --git a/docs/html/resources/community-groups.jd b/docs/html/resources/community-groups.jd
index 61fbcc8..a36a425 100644
--- a/docs/html/resources/community-groups.jd
+++ b/docs/html/resources/community-groups.jd
@@ -1,43 +1,65 @@
 community=true
-page.title=Android Developer Groups
+page.title=Developer Forums
 @jd:body
 
-<p>Welcome to the Android developers community! We're glad you're here and invite you to participate in these discussions. Before posting, please read the <a href="http://source.android.com/discuss/android-discussion-groups-charter">Groups Charter</a> that covers the community guidelines.</p>
+<p>Welcome to the Android developers community! We're glad you're here and invite you to participate in discussions with other Android application developers on topics that interest you.</p>
 
-<p class="note"><strong>Note:</strong> If you are seeking discussion about Android source code (not application development),
-then please refer to the <a href="http://source.android.com/discuss">Open Source Project Mailing lists</a>.</p>
+<p>The lists on this page are primarily for discussion about Android application development. If you are seeking discussion about Android source code (not application development), then please refer to the <a href="http://source.android.com/discuss">Open Source Project Mailing lists</a>.</p>
 
 <p style="margin-bottom:.5em"><strong>Contents</strong></p>
 <ol class="toc">
-  <li><a href="#BeforeYouPost">Before you post</a></li>
-  <li><a href="#ApplicationDeveloperLists">Application developer mailing lists</a></li>
-  <li><a href="#UsingEmail">Using email with the mailing lists</a></li>
+  <li><a href="#StackOverflow">Stack Overflow</a> <span class="new">new!</span></li>
+  <li><a href="#MailingLists">Mailing lists</a><ol>
+    <li><a href="#BeforeYouPost">Before you post</a></li>
+    <li><a href="#UsingEmail">Using email with the mailing lists</a></li>
+    <li><a href="#ApplicationDeveloperLists">Application developer mailing lists</a></li>
+  </ol></li>
+  <li><a href="#MarketHelp">Android Market Help Forum</a></li>
 </ol>
 
-<h2 id="BeforeYouPost">Before you post</h2>
+
+<h2 id="StackOverflow">Stack Overflow</h2>
+
+<p><a href="http://stackoverflow.com">Stack Overflow</a> is a collaboratively edited question and answer site for programmers. It's a great place to ask technical questions about developing and maintaining Android applications. The site is especially useful for asking questions with definite answers, but can also be used for discussing best practices.</p>
+
+<p>On the site, questions and answers relating to Android use the <a href="http://stackoverflow.com/questions/tagged/android">'android' tag</a>. You can look for Android topics by adding '<code>[android]</code>' to your search query, or by visiting the tag page at:</p>
+
+<p style="margin-left: 2em"><a href="http://stackoverflow.com/questions/tagged/android">http://stackoverflow.com/questions/tagged/android</a></p>
+
+<p>If you want to ask a question on Stack Overflow, you can use <a href="http://stackoverflow.com/questions/ask">this form</a>. Before submitting the form, make sure to add the 'android' tag so that other Android developers will be able to find your question. As always, before submitting a new question, take a look at the existing topics to see whether another developer has already asked or answered the question.</p>
+
+<p>If you are getting started with Android development, Stack Overflow may be a great location to ask questions about general Java programming or setting up the Eclipse development environment. Simply tag your questions with the <a href="http://stackoverflow.com/questions/tagged/java">Java</a> or <a href="http://stackoverflow.com/questions/tagged/eclipse">Eclipse</a> tags in these cases.</p>
+
+
+<h2 id="MailingLists">Mailing lists</h2>
+
+<p>There are a number of mailing lists, powered by <a href="http://groups.google.com">Google Groups</a>, available for discussing Android application development.</p>
+
+
+<h3 id="BeforeYouPost">Before you post</h3>
 <p>Before writing a post, please try the following:</p>
 
 <ol>
-<li><a href="{@docRoot}resources/faq/index.html">Read the FAQs</a> The most common questions about developing Android applications are addressed in this frequently updated list.</li>
-<li><strong>Type in keywords of your questions in the main Android site's search bar</strong> (such as the one above). This search encompasses all previous discussions, across all groups, as well as the full contents of the site, documentation, and blogs. Chances are good that somebody has run into the same issue before.</li>
-<li><b>Search the mailing list archives</b> to see whether your questions have already been discussed.
+
+<li>Look through the support information available in the 'More' section of this tab. You may find the answer to your question in the <a href="{@docRoot}resources/faq/commontasks.html">Common Tasks</a>, <a href="{@docRoot}resources/faq/troubleshooting.html">Troubleshooting Tips</a>, or <a href="{@docRoot}resources/faq/index.html">FAQs</a> sections.</li>
+<li>Type in keywords of your questions in the main Android site's search bar (such as the one above). This search encompasses all previous discussions, across all groups, as well as the full contents of the site, documentation, and blogs. Chances are good that somebody has run into the same issue before.</li>
   </li>
 </ol>
 
 <p>If you can't find your answer, then we encourage you to address the community.
 As you write your post, please do the following:
 <ol>
-<li><b>Read
-the <a href="http://sites.google.com/a/android.com/opensource/discuss/android-discussion-groups-charter">mailing list charter</a></b> that covers the community guidelines. 
+<li><strong>Read
+the <a href="http://source.android.com/discuss/android-discussion-groups-charter">mailing list charter</a></strong> that covers the community guidelines.
 </li>
-<li><b>Select the most appropriate mailing list for your question</b>. There are several different lists for 
+<li><strong>Select the most appropriate mailing list for your question</strong>. There are several different lists for
 developers, described below.</li>
 <li>
-    <b>Be very clear</b> about your question
+    <strong>Be very clear</strong> about your question
 in the subject -- it helps everyone, both those trying to answer your
 question as well as those who may be looking for information in the
 future.</li>
-<li><b>Give plenty of details</b> in your post to
+<li><strong>Give plenty of details</strong> in your post to
 help others understand your problem. Code or log snippets, as well as
 pointers to screenshots, may also be helpful. For a great guide to
 phrasing your questions, read <a href="http://www.catb.org/%7Eesr/faqs/smart-questions.html">How To Ask Questions The Smart Way</a>.
@@ -45,77 +67,48 @@
 </ol>
 
 
-<h2 id="ApplicationDeveloperLists">Application developer mailing lists</h2>
-<ul>
-<li><b>Android beginners</b> - You're new to Android application development. You want to figure out how to get started with the Android SDK and the basic Android APIs? Start here. This list is open to any discussion around beginner-type questions for developers using the SDK; this is a great way to get up and running with your new application on the Android platform. Ask about getting your development environment set up, get help with the first steps of Android development (your first User Interface, your first permission, your first file on the Android filesystem, your first app on the Android Market...). Be sure to check the archives first before asking new questions. Please avoid advanced subjects, which belong on android-developers, and user questions, which will get a better reception on android-discuss.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-beginners">android-beginners</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-beginners-subscribe@googlegroups.com">android-beginners-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android developers</b> - You're now an experienced Android application developer. You've grasped the basics of Android app development, you're comfortable using the SDK, now you want to move to advanced topics. Get help here with troubleshooting applications, advice on implementation, and strategies for improving your application's performance and user experience. This is the not the right place to discuss user issues (use android-discuss for that) or beginner questions with the Android SDK (use android-beginners for that).
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-developers">android-developers</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-developers-subscribe@googlegroups.com">android-developers-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android discuss</b> - The "water cooler" of Android discussion. You can discuss just about anything Android-related here, ideas for the Android platform, announcements about your applications, discussions about Android devices, community resources... As long as your discussion is related to Android, it's on-topic here. However, if you have a discussion here that could belong on another list, you are probably not reaching all of your target audience here and may want to consider shifting to a more targeted list.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-discuss">android-discuss</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-discuss-subscribe@googlegroups.com">android-discuss-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android ndk</b> - A place for discussing the Android NDK and topics related to using native code in Android applications. 
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-ndk">android-ndk</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-ndk-subscribe@googlegroups.com">android-ndk-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android security discuss</b> - A place for open discussion on secure development, emerging security concerns, and best practices for and by android developers. Please don't disclose vulnerabilities directly on this list, you'd be putting all Android users at risk.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-security-discuss">android-security-discuss</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-security-discuss@googlegroups.com">android-security-discuss@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android security announce</b> - A low-volume group for security-related announcements by the Android Security Team.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-security-announce">android-security-announce</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-security-announce-subscribe@googlegroups.com">android-security-announce-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android Market Help Forum</b> - A web-based discussion forum where you can ask questions or report issues relating to Android Market.
-<ul>
-<li>URL:&nbsp;<a href="http://www.google.com/support/forum/p/Android+Market?hl=en">http://www.google.com/support/forum/p/Android+Market?hl=en</a></li>
-</ul>
-</li>
-
-</ul>
-
-
-
-<h2 id="UsingEmail">Using email with the mailing lists</h2>
+<h3 id="UsingEmail">Using email with the mailing lists</h3>
 <p>Instead of using the <a href="http://groups.google.com/">Google Groups</a> site, you can use your email client of choice to participate in the mailing lists.</p>
 <p>To subscribe to a group without using the Google Groups site, use the link under "subscribe via email" in the lists above.</p>
 <p>To set up how you receive mailing list postings by email:</p>
 
 <ol><li>Sign into the group via the Google Groups site. For example, for the android-framework group you would visit <a href="http://groups.google.com/group/android-framework">http://groups.google.com/group/android-framework</a>.</li>
-<li>Click "Edit
-my membership" on the right side.</li>
-<li>Under "How do
-you want to read this group?" select one of the email options. </li>
+<li>Click "Edit my membership" on the right side.</li>
+<li>Under "How do you want to read this group?" select one of the email options.</li>
 </ol>
 
 
+<h3 id="ApplicationDeveloperLists">Application developer mailing lists</h3>
+<ul>
+<li><strong><a href="http://groups.google.com/group/android-developers">android-developers</a></strong>
+(<a href="mailto:android-developers-subscribe@googlegroups.com">subscribe via email</a>)<br>
+You're now an experienced Android application developer. You've grasped the basics of Android app development, you're comfortable using the SDK, now you want to move to advanced topics. Get help here with troubleshooting applications, advice on implementation, and strategies for improving your application's performance and user experience. This is the not the right place to discuss user issues (use android-discuss for that) or beginner questions with the Android SDK (use android-beginners for that).
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-discuss">android-discuss</a></strong>
+(<a href="mailto:android-discuss-subscribe@googlegroups.com">subscribe via email</a>)<br>
+The "water cooler" of Android discussion. You can discuss just about anything Android-related here, ideas for the Android platform, announcements about your applications, discussions about Android devices, community resources... As long as your discussion is related to Android, it's on-topic here. However, if you have a discussion here that could belong on another list, you are probably not reaching all of your target audience here and may want to consider shifting to a more targeted list.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-ndk">android-ndk</a></strong>
+(<a href="mailto:android-ndk-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A place for discussing the Android NDK and topics related to using native code in Android applications.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-security-discuss">android-security-discuss</a></strong>
+(<a href="mailto:android-security-discuss-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A place for open discussion on secure development, emerging security concerns, and best practices for and by android developers. Please don't disclose vulnerabilities directly on this list, you'd be putting all Android users at risk.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-security-announce">android-security-announce</a></strong>
+(<a href="mailto:android-security-announce-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A low-volume group for security-related announcements by the Android Security Team.
+</li>
+</ul>
 
 
+<h2 id="MarketHelp">Android Market Help Forum</h2>
 
+<p>The <a href="http://www.google.com/support/forum/p/Android+Market">Android Market Help Forum</a> is a web-based discussion forum where you can ask questions or report issues relating to Android Market.</p>
 
-
-
-</div>
+<p style="margin-left: 2em"><a href="http://www.google.com/support/forum/p/Android+Market">http://www.google.com/support/forum/p/Android+Market</a></p>
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index f5c573e..e337e38 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -11,7 +11,7 @@
     </h2>
     <ul>
       <li><a href="<?cs var:toroot ?>resources/community-groups.html">
-            <span class="en">Android Developer Groups</span>
+            <span class="en">Developer Forums</span>
           </a></li>
       <li><a href="<?cs var:toroot ?>resources/community-more.html">
             <span class="en">IRC, Twitter</span>
diff --git a/docs/html/sdk/download.jd b/docs/html/sdk/download.jd
index 47505e6..029de21 100644
--- a/docs/html/sdk/download.jd
+++ b/docs/html/sdk/download.jd
@@ -58,10 +58,8 @@
   <h2>Thank you for downloading the Android SDK!</h2>
   <p>Your download should be underway. If not, <a id="click-download">click here to start the download</a>.</p>
   <p>To set up your Android development environment, please read the guide to
-    <a href="installing.html">Installing the Android SDK</a>.
-    Once you have completed the installation, see the
-    <a href="/guide/index.html">Dev Guide</a> for documentation about
-    developing Android applications.</p>
+    <a href="installing.html">Installing the Android SDK</a> and ensure that your development
+    machine meets the system requirements linked on that page.</p>
 </div>
 
 <script type="text/javascript">
diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd
index f861afd..1d99c91 100644
--- a/docs/html/sdk/eclipse-adt.jd
+++ b/docs/html/sdk/eclipse-adt.jd
@@ -25,7 +25,7 @@
 Development Tools (ADT), that is designed to give you a powerful,
 integrated environment in which to build Android applications. </p>
 
-<p>ADT extends the capabilites of Eclipse to let you quickly set up new Android
+<p>ADT extends the capabilities of Eclipse to let you quickly set up new Android
 projects, create an application UI, add components based on the Android
 Framework API, debug your applications using the Android SDK tools, and even
 export signed (or unsigned) APKs in order to distribute your application.</p>
@@ -77,7 +77,7 @@
     <li>Click <strong>Add Site...</strong> </li>
     <li>In the Add Site dialog that appears, enter this URL in the "Location" field:
       <pre style="margin-left:0">https://dl-ssl.google.com/android/eclipse/</pre>
-        <p>Note: If you have trouble aqcuiring the plugin, try using "http" in the Location URL,
+        <p>Note: If you have trouble acquiring the plugin, try using "http" in the Location URL,
         instead of "https" (https is preferred for security reasons).</p>   
       <p>Click <strong>OK</strong>.</p></li>
     <li>Back in the Available Software view, you should see the plugin listed by the URL,
@@ -94,13 +94,13 @@
 <!-- 3.5 steps -->
 <ol>
     <li>Start Eclipse, then select <strong>Help</strong> &gt; <strong>Install
-        New Softare</strong>. </li>
+        New Software</strong>. </li>
     <li>In the Available Software dialog, click <strong>Add...</strong>.</li>
     <li>In the Add Site dialog that appears, enter a name for the remote site
         (for example, "Android Plugin") in the "Name" field. 
         <p>In the "Location" field, enter this URL:</p>
         <pre>https://dl-ssl.google.com/android/eclipse/</pre>
-        <p>Note: If you have trouble aqcuiring the plugin, you can try
+        <p>Note: If you have trouble acquiring the plugin, you can try
            using "http" in the URL, instead of "https" (https is preferred for 
            security reasons).</p>
         <p>Click <strong>OK</strong>.</p>
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 262ac8d..d364277 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -107,9 +107,5 @@
 
 include $(BUILD_SHARED_LIBRARY)
 
-# Include the subdirectories ====================
-include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk,\
-            java \
-    	))
 
 endif #simulator
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 23888ff..de8df39 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -776,6 +776,17 @@
     glDrawTexfOES(x, y, z, w, h);
 }
 
+static void SC_drawSpriteScreenspaceCropped(float x, float y, float z, float w, float h,
+        float cx0, float cy0, float cx1, float cy1)
+{
+    GET_TLS();
+    rsc->setupCheck();
+
+    GLint crop[4] = {cx0, cy0, cx1, cy1};
+    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+    glDrawTexfOES(x, y, z, w, h);
+}
+
 static void SC_drawSprite(float x, float y, float z, float w, float h)
 {
     GET_TLS();
@@ -1271,6 +1282,8 @@
         "void", "(float x, float y, float z, float w, float h)" },
     { "drawSpriteScreenspace", (void *)&SC_drawSpriteScreenspace,
         "void", "(float x, float y, float z, float w, float h)" },
+    { "drawSpriteScreenspaceCropped", (void *)&SC_drawSpriteScreenspaceCropped,
+        "void", "(float x, float y, float z, float w, float h, float cx0, float cy0, float cx1, float cy1)" },
     { "drawLine", (void *)&SC_drawLine,
         "void", "(float x1, float y1, float z1, float x2, float y2, float z2)" },
     { "drawPoint", (void *)&SC_drawPoint,
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 78215b0..5c0f352 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -111,6 +111,7 @@
         public int mType;
         public int mRadio;
         public int mPriority;
+        public NetworkInfo.State mLastState;
         public NetworkAttributes(String init) {
             String fragments[] = init.split(",");
             mName = fragments[0].toLowerCase();
@@ -131,6 +132,7 @@
                 mType = ConnectivityManager.TYPE_MOBILE_HIPRI;
             }
             mPriority = Integer.parseInt(fragments[2]);
+            mLastState = NetworkInfo.State.UNKNOWN;
         }
         public boolean isDefault() {
             return (mType == mRadio);
@@ -1214,9 +1216,22 @@
             switch (msg.what) {
                 case NetworkStateTracker.EVENT_STATE_CHANGED:
                     info = (NetworkInfo) msg.obj;
+                    int type = info.getType();
+                    NetworkInfo.State state = info.getState();
+                    if (mNetAttributes[type].mLastState == state) {
+                        if (DBG) {
+                            // TODO - remove this after we validate the dropping doesn't break anything
+                            Log.d(TAG, "Dropping ConnectivityChange for " +
+                                    info.getTypeName() + ": " +
+                                    state + "/" + info.getDetailedState());
+                        }
+                        return;
+                    }
+                    mNetAttributes[type].mLastState = state;
+
                     if (DBG) Log.d(TAG, "ConnectivityChange for " +
                             info.getTypeName() + ": " +
-                            info.getState() + "/" + info.getDetailedState());
+                            state + "/" + info.getDetailedState());
 
                     // Connectivity state changed:
                     // [31-13] Reserved for future use
@@ -1234,10 +1249,9 @@
                     if (info.getDetailedState() ==
                             NetworkInfo.DetailedState.FAILED) {
                         handleConnectionFailure(info);
-                    } else if (info.getState() ==
-                            NetworkInfo.State.DISCONNECTED) {
+                    } else if (state == NetworkInfo.State.DISCONNECTED) {
                         handleDisconnect(info);
-                    } else if (info.getState() == NetworkInfo.State.SUSPENDED) {
+                    } else if (state == NetworkInfo.State.SUSPENDED) {
                         // TODO: need to think this over.
                         // the logic here is, handle SUSPENDED the same as
                         // DISCONNECTED. The only difference being we are
@@ -1246,7 +1260,7 @@
                         // opportunity to handle DISCONNECTED and SUSPENDED
                         // differently, or not.
                         handleDisconnect(info);
-                    } else if (info.getState() == NetworkInfo.State.CONNECTED) {
+                    } else if (state == NetworkInfo.State.CONNECTED) {
                         handleConnect(info);
                     }
                     break;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyEventLog.java b/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
index cdce488..0f15cb6 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
@@ -32,4 +32,5 @@
     public static final int EVENT_LOG_PDP_NETWORK_DROP = 50109;
     public static final int EVENT_LOG_CDMA_DATA_SETUP_FAILED = 50110;
     public static final int EVENT_LOG_CDMA_DATA_DROP = 50111;
+    public static final int EVENT_LOG_GSM_RAT_SWITCHED = 50112;
 }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 8140654..3bf76d4 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -38,6 +38,7 @@
 import android.provider.Telephony.Intents;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 import android.util.Config;
@@ -848,6 +849,21 @@
         cellLoc = newCellLoc;
         newCellLoc = tcl;
 
+
+        // Add an event log when network type switched
+        // TODO: we may add filtering to reduce the event logged,
+        // i.e. check preferred network setting, only switch to 2G, etc
+        if (hasNetworkTypeChanged) {
+            int cid = -1;
+            GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+            if (loc != null) cid = loc.getCid();
+            EventLog.List val = new EventLog.List(cid, networkType, newNetworkType);
+            EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_GSM_RAT_SWITCHED, val);
+            Log.d(LOG_TAG,
+                    "RAT switched " + networkTypeToString(networkType) + " -> "
+                    + networkTypeToString(newNetworkType) + " at cell " + cid);
+        }
+
         gprsState = newGPRSState;
         networkType = newNetworkType;
 
diff --git a/tests/DumpRenderTree/AndroidManifest.xml b/tests/DumpRenderTree/AndroidManifest.xml
index efa4113..03b7e26 100644
--- a/tests/DumpRenderTree/AndroidManifest.xml
+++ b/tests/DumpRenderTree/AndroidManifest.xml
@@ -17,14 +17,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree">
     <application android:name="HTMLHostApp">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="Menu" android:label="1 Dump Render Tree">
+        <activity android:name="Menu" android:label="Dump Render Tree"
+          android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.TEST" />
             </intent-filter>
         </activity>
-        <activity android:name="TestShellActivity" android:launchMode="singleTop" />
-        <activity android:name="ReliabilityTestActivity" />
+        <activity android:name="TestShellActivity" android:launchMode="singleTop"
+          android:screenOrientation="portrait"/>
+        <activity android:name="ReliabilityTestActivity" android:screenOrientation="portrait"/>
     </application>
 
     <instrumentation android:name=".LayoutTestsAutoRunner"
diff --git a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
index 52286d1..31ee120 100644
--- a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
+++ b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
@@ -24,9 +24,8 @@
 import android.os.Bundle;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.TransformGestureDetector;
+import android.view.ScaleGestureDetector;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -48,9 +47,6 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        final LayoutInflater li = (LayoutInflater)getSystemService(
-                LAYOUT_INFLATER_SERVICE);
         
         this.setTitle(R.string.act_title);
         LinearLayout root = new LinearLayout(this);
@@ -71,15 +67,19 @@
         private float mPosY;
         private float mScale = 1.f;
         private Matrix mMatrix;
-        private TransformGestureDetector mDetector;
+        private ScaleGestureDetector mDetector;
         
-        private class Listener implements TransformGestureDetector.OnTransformGestureListener {
+        private float mLastX;
+        private float mLastY;
+        
+        private class Listener implements ScaleGestureDetector.OnScaleGestureListener {
 
-            public boolean onTransform(TransformGestureDetector detector) {
-                Log.d("ttest", "Translation: (" + detector.getTranslateX() +
-                        ", " + detector.getTranslateY() + ")");
+            public boolean onScale(ScaleGestureDetector detector) {
                 float scale = detector.getScaleFactor();
+                
                 Log.d("ttest", "Scale: " + scale);
+                
+                // Limit the scale so our object doesn't get too big or disappear
                 if (mScale * scale > 0.1f) {
                     if (mScale * scale < 10.f) {
                         mScale *= scale;
@@ -89,16 +89,13 @@
                 } else {
                     mScale = 0.1f;
                 }
-
-                mPosX += detector.getTranslateX();
-                mPosY += detector.getTranslateY();
                 
                 Log.d("ttest", "mScale: " + mScale + " mPos: (" + mPosX + ", " + mPosY + ")");
                 
                 float sizeX = mDrawable.getIntrinsicWidth()/2;
                 float sizeY = mDrawable.getIntrinsicHeight()/2;
-                float centerX = detector.getCenterX();
-                float centerY = detector.getCenterY();
+                float centerX = detector.getFocusX();
+                float centerY = detector.getFocusY();
                 float diffX = centerX - mPosX;
                 float diffY = centerY - mPosY;
                 diffX = diffX*scale - diffX;
@@ -115,24 +112,20 @@
                 return true;
             }
 
-            public boolean onTransformBegin(TransformGestureDetector detector) {
+            public boolean onScaleBegin(ScaleGestureDetector detector) {
                 return true;
             }
 
-            public boolean onTransformEnd(TransformGestureDetector detector) {
-                return true;
-            }
-
-            public boolean onTransformFling(TransformGestureDetector detector) {
-                return false;
-            }
-            
+            public void onScaleEnd(ScaleGestureDetector detector) {
+                mLastX = detector.getFocusX();
+                mLastY = detector.getFocusY();
+            }            
         }
         
         public TransformView(Context context) {
             super(context);
             mMatrix = new Matrix();
-            mDetector = new TransformGestureDetector(context, new Listener());
+            mDetector = new ScaleGestureDetector(context, new Listener());
             DisplayMetrics metrics = context.getResources().getDisplayMetrics();
             mPosX = metrics.widthPixels/2;
             mPosY = metrics.heightPixels/2;
@@ -151,12 +144,37 @@
         
         @Override
         public boolean onTouchEvent(MotionEvent event) {
-            boolean handled = mDetector.onTouchEvent(event);
+            mDetector.onTouchEvent(event);
             
-            int pointerCount = event.getPointerCount();
-            Log.d("ttest", "pointerCount: " + pointerCount);
+            // Handling single finger pan
+            if (!mDetector.isInProgress()) {
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        mLastX = event.getX();
+                        mLastY = event.getY();
+                        break;
+                        
+                    case MotionEvent.ACTION_MOVE:
+                        final float x = event.getX();
+                        final float y = event.getY();
+                        mPosX += x - mLastX;
+                        mPosY += y - mLastY;
+                        mLastX = x;
+                        mLastY = y;
+                        
+                        float sizeX = mDrawable.getIntrinsicWidth()/2;
+                        float sizeY = mDrawable.getIntrinsicHeight()/2;
+                        
+                        mMatrix.reset();
+                        mMatrix.postTranslate(-sizeX, -sizeY);
+                        mMatrix.postScale(mScale, mScale);
+                        mMatrix.postTranslate(mPosX, mPosY);
+                        invalidate();
+                        break;
+                }
+            }
 
-            return handled;
+            return true;
         }
         
         @Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
index ff1b295..35f022e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
@@ -20,6 +20,7 @@
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 
 import javax.imageio.ImageIO;
 
@@ -33,6 +34,12 @@
         mImage = ImageIO.read(input);
     }
 
+    public Bitmap(InputStream is) throws IOException {
+        super(1, true, null, -1);
+
+        mImage = ImageIO.read(is);
+    }
+
     Bitmap(BufferedImage image) {
         super(1, true, null, -1);
         mImage = image;
@@ -237,4 +244,35 @@
         return createBitmap(colors, 0, width, width, height, config);
     }
 
+    public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
+            int dstHeight, boolean filter) {
+        Matrix m;
+        synchronized (Bitmap.class) {
+            // small pool of just 1 matrix
+            m = sScaleMatrix;
+            sScaleMatrix = null;
+        }
+
+        if (m == null) {
+            m = new Matrix();
+        }
+
+        final int width = src.getWidth();
+        final int height = src.getHeight();
+        final float sx = dstWidth  / (float)width;
+        final float sy = dstHeight / (float)height;
+        m.setScale(sx, sy);
+        Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
+
+        synchronized (Bitmap.class) {
+            // do we need to check for null? why not just assign everytime?
+            if (sScaleMatrix == null) {
+                sScaleMatrix = m;
+            }
+        }
+
+        return b;
+    }
+
+
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
new file mode 100644
index 0000000..e978fe8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Creates Bitmap objects from various sources, including files, streams,
+ * and byte-arrays.
+ */
+public class BitmapFactory {
+    public static class Options {
+        /**
+         * Create a default Options object, which if left unchanged will give
+         * the same result from the decoder as if null were passed.
+         */
+        public Options() {
+            inDither = true;
+            inScaled = true;
+        }
+
+        /**
+         * If set to true, the decoder will return null (no bitmap), but
+         * the out... fields will still be set, allowing the caller to query
+         * the bitmap without having to allocate the memory for its pixels.
+         */
+        public boolean inJustDecodeBounds;
+
+        /**
+         * If set to a value > 1, requests the decoder to subsample the original
+         * image, returning a smaller image to save memory. The sample size is
+         * the number of pixels in either dimension that correspond to a single
+         * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
+         * an image that is 1/4 the width/height of the original, and 1/16 the
+         * number of pixels. Any value <= 1 is treated the same as 1. Note: the
+         * decoder will try to fulfill this request, but the resulting bitmap
+         * may have different dimensions that precisely what has been requested.
+         * Also, powers of 2 are often faster/easier for the decoder to honor.
+         */
+        public int inSampleSize;
+
+        /**
+         * If this is non-null, the decoder will try to decode into this
+         * internal configuration. If it is null, or the request cannot be met,
+         * the decoder will try to pick the best matching config based on the
+         * system's screen depth, and characteristics of the original image such
+         * as if it has per-pixel alpha (requiring a config that also does).
+         */
+        public Bitmap.Config inPreferredConfig;
+
+        /**
+         * If dither is true, the decoder will attempt to dither the decoded
+         * image.
+         */
+        public boolean inDither;
+
+        /**
+         * The pixel density to use for the bitmap.  This will always result
+         * in the returned bitmap having a density set for it (see
+         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)).  In addition,
+         * if {@link #inScaled} is set (which it is by default} and this
+         * density does not match {@link #inTargetDensity}, then the bitmap
+         * will be scaled to the target density before being returned.
+         *
+         * <p>If this is 0,
+         * {@link BitmapFactory#decodeResource(Resources, int)},
+         * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+         * and {@link BitmapFactory#decodeResourceStream}
+         * will fill in the density associated with the resource.  The other
+         * functions will leave it as-is and no density will be applied.
+         *
+         * @see #inTargetDensity
+         * @see #inScreenDensity
+         * @see #inScaled
+         * @see Bitmap#setDensity(int)
+         * @see android.util.DisplayMetrics#densityDpi
+         */
+        public int inDensity;
+
+        /**
+         * The pixel density of the destination this bitmap will be drawn to.
+         * This is used in conjunction with {@link #inDensity} and
+         * {@link #inScaled} to determine if and how to scale the bitmap before
+         * returning it.
+         *
+         * <p>If this is 0,
+         * {@link BitmapFactory#decodeResource(Resources, int)},
+         * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+         * and {@link BitmapFactory#decodeResourceStream}
+         * will fill in the density associated the Resources object's
+         * DisplayMetrics.  The other
+         * functions will leave it as-is and no scaling for density will be
+         * performed.
+         *
+         * @see #inDensity
+         * @see #inScreenDensity
+         * @see #inScaled
+         * @see android.util.DisplayMetrics#densityDpi
+         */
+        public int inTargetDensity;
+
+        /**
+         * The pixel density of the actual screen that is being used.  This is
+         * purely for applications running in density compatibility code, where
+         * {@link #inTargetDensity} is actually the density the application
+         * sees rather than the real screen density.
+         *
+         * <p>By setting this, you
+         * allow the loading code to avoid scaling a bitmap that is currently
+         * in the screen density up/down to the compatibility density.  Instead,
+         * if {@link #inDensity} is the same as {@link #inScreenDensity}, the
+         * bitmap will be left as-is.  Anything using the resulting bitmap
+         * must also used {@link Bitmap#getScaledWidth(int)
+         * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
+         * Bitmap.getScaledHeight} to account for any different between the
+         * bitmap's density and the target's density.
+         *
+         * <p>This is never set automatically for the caller by
+         * {@link BitmapFactory} itself.  It must be explicitly set, since the
+         * caller must deal with the resulting bitmap in a density-aware way.
+         *
+         * @see #inDensity
+         * @see #inTargetDensity
+         * @see #inScaled
+         * @see android.util.DisplayMetrics#densityDpi
+         */
+        public int inScreenDensity;
+
+        /**
+         * When this flag is set, if {@link #inDensity} and
+         * {@link #inTargetDensity} are not 0, the
+         * bitmap will be scaled to match {@link #inTargetDensity} when loaded,
+         * rather than relying on the graphics system scaling it each time it
+         * is drawn to a Canvas.
+         *
+         * <p>This flag is turned on by default and should be turned off if you need
+         * a non-scaled version of the bitmap.  Nine-patch bitmaps ignore this
+         * flag and are always scaled.
+         */
+        public boolean inScaled;
+
+        /**
+         * If this is set to true, then the resulting bitmap will allocate its
+         * pixels such that they can be purged if the system needs to reclaim
+         * memory. In that instance, when the pixels need to be accessed again
+         * (e.g. the bitmap is drawn, getPixels() is called), they will be
+         * automatically re-decoded.
+         *
+         * For the re-decode to happen, the bitmap must have access to the
+         * encoded data, either by sharing a reference to the input
+         * or by making a copy of it. This distinction is controlled by
+         * inInputShareable. If this is true, then the bitmap may keep a shallow
+         * reference to the input. If this is false, then the bitmap will
+         * explicitly make a copy of the input data, and keep that. Even if
+         * sharing is allowed, the implementation may still decide to make a
+         * deep copy of the input data.
+         */
+        public boolean inPurgeable;
+
+        /**
+         * This field works in conjuction with inPurgeable. If inPurgeable is
+         * false, then this field is ignored. If inPurgeable is true, then this
+         * field determines whether the bitmap can share a reference to the
+         * input data (inputstream, array, etc.) or if it must make a deep copy.
+         */
+        public boolean inInputShareable;
+
+        /**
+         * Normally bitmap allocations count against the dalvik heap, which
+         * means they help trigger GCs when a lot have been allocated. However,
+         * in rare cases, the caller may want to allocate the bitmap outside of
+         * that heap. To request that, set inNativeAlloc to true. In these
+         * rare instances, it is solely up to the caller to ensure that OOM is
+         * managed explicitly by calling bitmap.recycle() as soon as such a
+         * bitmap is no longer needed.
+         *
+         * @hide pending API council approval
+         */
+        public boolean inNativeAlloc;
+
+        /**
+         * The resulting width of the bitmap, set independent of the state of
+         * inJustDecodeBounds. However, if there is an error trying to decode,
+         * outWidth will be set to -1.
+         */
+        public int outWidth;
+
+        /**
+         * The resulting height of the bitmap, set independent of the state of
+         * inJustDecodeBounds. However, if there is an error trying to decode,
+         * outHeight will be set to -1.
+         */
+        public int outHeight;
+
+        /**
+         * If known, this string is set to the mimetype of the decoded image.
+         * If not know, or there is an error, it is set to null.
+         */
+        public String outMimeType;
+
+        /**
+         * Temp storage to use for decoding.  Suggest 16K or so.
+         */
+        public byte[] inTempStorage;
+
+        private native void requestCancel();
+
+        /**
+         * Flag to indicate that cancel has been called on this object.  This
+         * is useful if there's an intermediary that wants to first decode the
+         * bounds and then decode the image.  In that case the intermediary
+         * can check, inbetween the bounds decode and the image decode, to see
+         * if the operation is canceled.
+         */
+        public boolean mCancel;
+
+        /**
+         *  This can be called from another thread while this options object is
+         *  inside a decode... call. Calling this will notify the decoder that
+         *  it should cancel its operation. This is not guaranteed to cancel
+         *  the decode, but if it does, the decoder... operation will return
+         *  null, or if inJustDecodeBounds is true, will set outWidth/outHeight
+         *  to -1
+         */
+        public void requestCancelDecode() {
+            mCancel = true;
+            requestCancel();
+        }
+    }
+
+    /**
+     * Decode a file path into a bitmap. If the specified file name is null,
+     * or cannot be decoded into a bitmap, the function returns null.
+     *
+     * @param pathName complete path name for the file to be decoded.
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     */
+    public static Bitmap decodeFile(String pathName, Options opts) {
+        Bitmap bm = null;
+        InputStream stream = null;
+        try {
+            stream = new FileInputStream(pathName);
+            bm = decodeStream(stream, null, opts);
+        } catch (Exception e) {
+            /*  do nothing.
+                If the exception happened on open, bm will be null.
+            */
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // do nothing here
+                }
+            }
+        }
+        return bm;
+    }
+
+    /**
+     * Decode a file path into a bitmap. If the specified file name is null,
+     * or cannot be decoded into a bitmap, the function returns null.
+     *
+     * @param pathName complete path name for the file to be decoded.
+     * @return the resulting decoded bitmap, or null if it could not be decoded.
+     */
+    public static Bitmap decodeFile(String pathName) {
+        return decodeFile(pathName, null);
+    }
+
+    /**
+     * Decode a new Bitmap from an InputStream. This InputStream was obtained from
+     * resources, which we pass to be able to scale the bitmap accordingly.
+     */
+    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
+            InputStream is, Rect pad, Options opts) {
+
+        if (opts == null) {
+            opts = new Options();
+        }
+
+        if (opts.inDensity == 0 && value != null) {
+            final int density = value.density;
+            if (density == TypedValue.DENSITY_DEFAULT) {
+                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+            } else if (density != TypedValue.DENSITY_NONE) {
+                opts.inDensity = density;
+            }
+        }
+
+        if (opts.inTargetDensity == 0 && res != null) {
+            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
+        }
+
+        return decodeStream(is, pad, opts);
+    }
+
+    /**
+     * Synonym for opening the given resource and calling
+     * {@link #decodeResourceStream}.
+     *
+     * @param res   The resources object containing the image data
+     * @param id The resource id of the image data
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     */
+    public static Bitmap decodeResource(Resources res, int id, Options opts) {
+        Bitmap bm = null;
+        InputStream is = null;
+
+        try {
+            final TypedValue value = new TypedValue();
+            is = res.openRawResource(id, value);
+
+            bm = decodeResourceStream(res, value, is, null, opts);
+        } catch (Exception e) {
+            /*  do nothing.
+                If the exception happened on open, bm will be null.
+                If it happened on close, bm is still valid.
+            */
+        } finally {
+            try {
+                if (is != null) is.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+
+        return bm;
+    }
+
+    /**
+     * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}
+     * will null Options.
+     *
+     * @param res The resources object containing the image data
+     * @param id The resource id of the image data
+     * @return The decoded bitmap, or null if the image could not be decode.
+     */
+    public static Bitmap decodeResource(Resources res, int id) {
+        return decodeResource(res, id, null);
+    }
+
+    /**
+     * Decode an immutable bitmap from the specified byte array.
+     *
+     * @param data byte array of compressed image data
+     * @param offset offset into imageData for where the decoder should begin
+     *               parsing.
+     * @param length the number of bytes, beginning at offset, to parse
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     */
+    public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
+        if ((offset | length) < 0 || data.length < offset + length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge.
+        return null;
+        //return nativeDecodeByteArray(data, offset, length, opts);
+    }
+
+    /**
+     * Decode an immutable bitmap from the specified byte array.
+     *
+     * @param data byte array of compressed image data
+     * @param offset offset into imageData for where the decoder should begin
+     *               parsing.
+     * @param length the number of bytes, beginning at offset, to parse
+     * @return The decoded bitmap, or null if the image could not be decode.
+     */
+    public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
+        return decodeByteArray(data, offset, length, null);
+    }
+
+    /**
+     * Decode an input stream into a bitmap. If the input stream is null, or
+     * cannot be used to decode a bitmap, the function returns null.
+     * The stream's position will be where ever it was after the encoded data
+     * was read.
+     *
+     * @param is The input stream that holds the raw data to be decoded into a
+     *           bitmap.
+     * @param outPadding If not null, return the padding rect for the bitmap if
+     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
+     *                   no bitmap is returned (null) then padding is
+     *                   unchanged.
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     */
+    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+        // we don't throw in this case, thus allowing the caller to only check
+        // the cache, and not force the image to be decoded.
+        if (is == null) {
+            return null;
+        }
+
+        // we need mark/reset to work properly
+
+        if (!is.markSupported()) {
+            is = new BufferedInputStream(is, 16 * 1024);
+        }
+
+        // so we can call reset() if a given codec gives up after reading up to
+        // this many bytes. FIXME: need to find out from the codecs what this
+        // value should be.
+        is.mark(1024);
+
+        Bitmap  bm;
+
+        if (is instanceof AssetManager.AssetInputStream) {
+            // FIXME: log this.
+            return null;
+        } else {
+            // pass some temp storage down to the native code. 1024 is made up,
+            // but should be large enough to avoid too many small calls back
+            // into is.read(...) This number is not related to the value passed
+            // to mark(...) above.
+            try {
+                bm = new Bitmap(is);
+            } catch (IOException e) {
+                return null;
+            }
+        }
+
+        return finishDecode(bm, outPadding, opts);
+    }
+
+    private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
+        if (bm == null || opts == null) {
+            return bm;
+        }
+
+        final int density = opts.inDensity;
+        if (density == 0) {
+            return bm;
+        }
+
+        bm.setDensity(density);
+        final int targetDensity = opts.inTargetDensity;
+        if (targetDensity == 0 || density == targetDensity
+                || density == opts.inScreenDensity) {
+            return bm;
+        }
+
+        byte[] np = bm.getNinePatchChunk();
+        final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np);
+        if (opts.inScaled || isNinePatch) {
+            float scale = targetDensity / (float)density;
+            // TODO: This is very inefficient and should be done in native by Skia
+            final Bitmap oldBitmap = bm;
+            bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
+                    (int) (bm.getHeight() * scale + 0.5f), true);
+            oldBitmap.recycle();
+
+            if (isNinePatch) {
+                //np = nativeScaleNinePatch(np, scale, outPadding);
+                bm.setNinePatchChunk(np);
+            }
+            bm.setDensity(targetDensity);
+        }
+
+        return bm;
+    }
+
+    /**
+     * Decode an input stream into a bitmap. If the input stream is null, or
+     * cannot be used to decode a bitmap, the function returns null.
+     * The stream's position will be where ever it was after the encoded data
+     * was read.
+     *
+     * @param is The input stream that holds the raw data to be decoded into a
+     *           bitmap.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     */
+    public static Bitmap decodeStream(InputStream is) {
+        return decodeStream(is, null, null);
+    }
+
+    /**
+     * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+     * return null. The position within the descriptor will not be changed when
+     * this returns, so the descriptor can be used again as-is.
+     *
+     * @param fd The file descriptor containing the bitmap data to decode
+     * @param outPadding If not null, return the padding rect for the bitmap if
+     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
+     *                   no bitmap is returned (null) then padding is
+     *                   unchanged.
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return the decoded bitmap, or null
+     */
+    public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {
+        return null;
+
+        /* FIXME: implement as needed
+        try {
+            if (MemoryFile.isMemoryFile(fd)) {
+                int mappedlength = MemoryFile.getMappedSize(fd);
+                MemoryFile file = new MemoryFile(fd, mappedlength, "r");
+                InputStream is = file.getInputStream();
+                Bitmap bm = decodeStream(is, outPadding, opts);
+                return finishDecode(bm, outPadding, opts);
+            }
+        } catch (IOException ex) {
+            // invalid filedescriptor, no need to call nativeDecodeFileDescriptor()
+            return null;
+        }
+        //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+        //return finishDecode(bm, outPadding, opts);
+        */
+    }
+
+    /**
+     * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+     * return null. The position within the descriptor will not be changed when
+     * this returns, so the descriptor can be used again as is.
+     *
+     * @param fd The file descriptor containing the bitmap data to decode
+     * @return the decoded bitmap, or null
+     */
+    public static Bitmap decodeFileDescriptor(FileDescriptor fd) {
+        return decodeFileDescriptor(fd, null, null);
+    }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
index 8bf7fcc..ad3974c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import java.awt.Paint;
+
 public class BitmapShader extends Shader {
 
     // we hold on just for the GC, since our native counterpart is using it
@@ -31,11 +33,16 @@
     public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) {
         mBitmap = bitmap;
     }
-    
+
     //---------- Custom methods
-    
+
     public Bitmap getBitmap() {
         return mBitmap;
     }
+
+    @Override
+    Paint getJavaPaint() {
+        return null;
+    }
 }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index 4986c77..8bf5e85 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -31,6 +31,7 @@
 import android.graphics.Region.Op;
 
 import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Composite;
 import java.awt.Graphics2D;
@@ -103,13 +104,37 @@
      * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
      * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
      */
-    private Graphics2D getNewGraphics(Paint paint, Graphics2D g) {
+    private Graphics2D getCustomGraphics(Paint paint) {
         // make new one
+        Graphics2D g = getGraphics2d();
         g = (Graphics2D)g.create();
+
+        // configure it
         g.setColor(new Color(paint.getColor()));
         int alpha = paint.getAlpha();
         float falpha = alpha / 255.f;
 
+        Style style = paint.getStyle();
+        if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+            PathEffect e = paint.getPathEffect();
+            if (e instanceof DashPathEffect) {
+                DashPathEffect dpe = (DashPathEffect)e;
+                g.setStroke(new BasicStroke(
+                        paint.getStrokeWidth(),
+                        paint.getStrokeCap().getJavaCap(),
+                        paint.getStrokeJoin().getJavaJoin(),
+                        paint.getStrokeMiter(),
+                        dpe.getIntervals(),
+                        dpe.getPhase()));
+            } else {
+                g.setStroke(new BasicStroke(
+                        paint.getStrokeWidth(),
+                        paint.getStrokeCap().getJavaCap(),
+                        paint.getStrokeJoin().getJavaJoin(),
+                        paint.getStrokeMiter()));
+            }
+        }
+
         Xfermode xfermode = paint.getXfermode();
         if (xfermode instanceof PorterDuffXfermode) {
             PorterDuff.Mode mode = ((PorterDuffXfermode)xfermode).getMode();
@@ -125,13 +150,16 @@
         }
 
         Shader shader = paint.getShader();
-        if (shader instanceof LinearGradient) {
-            g.setPaint(((LinearGradient)shader).getPaint());
-        } else {
-            if (mLogger != null && shader != null) {
-                mLogger.warning(String.format(
-                        "Shader '%1$s' is not supported in the Layout Editor.",
-                        shader.getClass().getCanonicalName()));
+        if (shader != null) {
+            java.awt.Paint shaderPaint = shader.getJavaPaint();
+            if (shaderPaint != null) {
+                g.setPaint(shaderPaint);
+            } else {
+                if (mLogger != null) {
+                    mLogger.warning(String.format(
+                            "Shader '%1$s' is not supported in the Layout Editor.",
+                            shader.getClass().getCanonicalName()));
+                }
             }
         }
 
@@ -236,10 +264,15 @@
      */
     @Override
     public int save() {
+        // get the current save count
+        int count = mGraphicsStack.size();
+
+        // create a new graphics and add it to the stack
         Graphics2D g = (Graphics2D)getGraphics2d().create();
         mGraphicsStack.push(g);
 
-        return mGraphicsStack.size() - 1;
+        // return the old save count
+        return count;
     }
 
     /* (non-Javadoc)
@@ -274,10 +307,9 @@
      */
     @Override
     public int getSaveCount() {
-        return mGraphicsStack.size() - 1;
+        return mGraphicsStack.size();
     }
 
-
     /* (non-Javadoc)
      * @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op)
      */
@@ -405,7 +437,7 @@
 
         g.setColor(new Color(color));
 
-        getGraphics2d().fillRect(0, 0, getWidth(), getHeight());
+        g.fillRect(0, 0, getWidth(), getHeight());
 
         g.setComposite(composite);
 
@@ -776,24 +808,24 @@
     }
 
     private final void doDrawRect(int left, int top, int width, int height, Paint paint) {
-        // get current graphisc
-        Graphics2D g = getGraphics2d();
+        if (width > 0 && height > 0) {
+            // get a Graphics2D object configured with the drawing parameters.
+            Graphics2D g = getCustomGraphics(paint);
 
-        g = getNewGraphics(paint, g);
+            Style style = paint.getStyle();
 
-        Style style = paint.getStyle();
+            // draw
+            if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
+                g.fillRect(left, top, width, height);
+            }
 
-        // draw
-        if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
-            g.fillRect(left, top, width, height);
+            if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+                g.drawRect(left, top, width, height);
+            }
+
+            // dispose Graphics2D object
+            g.dispose();
         }
-
-        if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
-            g.drawRect(left, top, width, height);
-        }
-
-        // dispose Graphics2D object
-        g.dispose();
     }
 
     /* (non-Javadoc)
@@ -801,30 +833,30 @@
      */
     @Override
     public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
-        // get current graphisc
-        Graphics2D g = getGraphics2d();
+        if (rect.width() > 0 && rect.height() > 0) {
+            // get a Graphics2D object configured with the drawing parameters.
+            Graphics2D g = getCustomGraphics(paint);
 
-        g = getNewGraphics(paint, g);
+            Style style = paint.getStyle();
 
-        Style style = paint.getStyle();
+            // draw
 
-        // draw
+            int arcWidth = (int)(rx * 2);
+            int arcHeight = (int)(ry * 2);
 
-        int arcWidth = (int)(rx * 2);
-        int arcHeight = (int)(ry * 2);
+            if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
+                g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+                        arcWidth, arcHeight);
+            }
 
-        if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
-            g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                    arcWidth, arcHeight);
+            if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+                g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+                        arcWidth, arcHeight);
+            }
+
+            // dispose Graphics2D object
+            g.dispose();
         }
-
-        if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
-            g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                    arcWidth, arcHeight);
-        }
-
-        // dispose Graphics2D object
-        g.dispose();
     }
 
 
@@ -833,10 +865,8 @@
      */
     @Override
     public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
-        // get current graphisc
-        Graphics2D g = getGraphics2d();
-
-        g = getNewGraphics(paint, g);
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D g = getCustomGraphics(paint);
 
         g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
 
@@ -849,10 +879,8 @@
      */
     @Override
     public void drawLines(float[] pts, int offset, int count, Paint paint) {
-        // get current graphisc
-        Graphics2D g = getGraphics2d();
-
-        g = getNewGraphics(paint, g);
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D g = getCustomGraphics(paint);
 
         for (int i = 0 ; i < count ; i += 4) {
             g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
@@ -876,10 +904,8 @@
      */
     @Override
     public void drawCircle(float cx, float cy, float radius, Paint paint) {
-        // get current graphisc
-        Graphics2D g = getGraphics2d();
-
-        g = getNewGraphics(paint, g);
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D g = getCustomGraphics(paint);
 
         Style style = paint.getStyle();
 
@@ -903,10 +929,8 @@
      */
     @Override
     public void drawOval(RectF oval, Paint paint) {
-        // get current graphics
-        Graphics2D g = getGraphics2d();
-
-        g = getNewGraphics(paint, g);
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D g = getCustomGraphics(paint);
 
         Style style = paint.getStyle();
 
@@ -928,10 +952,8 @@
      */
     @Override
     public void drawPath(Path path, Paint paint) {
-        // get current graphics
-        Graphics2D g = getGraphics2d();
-
-        g = getNewGraphics(paint, g);
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D g = getCustomGraphics(paint);
 
         Style style = paint.getStyle();
 
@@ -953,10 +975,6 @@
      */
     @Override
     public void setMatrix(Matrix matrix) {
-        // since SetMatrix *replaces* all the other transformation, we have to restore/save
-        restore();
-        save();
-
         // get the new current graphics
         Graphics2D g = getGraphics2d();
 
@@ -968,6 +986,27 @@
         }
     }
 
+    /* (non-Javadoc)
+     * @see android.graphics.Canvas#concat(android.graphics.Matrix)
+     */
+    @Override
+    public void concat(Matrix matrix) {
+        // get the current top graphics2D object.
+        Graphics2D g = getGraphics2d();
+
+        // get its current matrix
+        AffineTransform currentTx = g.getTransform();
+        // get the AffineTransform of the given matrix
+        AffineTransform matrixTx = matrix.getTransform();
+
+        // combine them so that the given matrix is applied after.
+        currentTx.preConcatenate(matrixTx);
+
+        // give it to the graphics2D as a new matrix replacing all previous transform
+        g.setTransform(currentTx);
+    }
+
+
     // --------------------
 
     /* (non-Javadoc)
@@ -1008,15 +1047,6 @@
     }
 
     /* (non-Javadoc)
-     * @see android.graphics.Canvas#concat(android.graphics.Matrix)
-     */
-    @Override
-    public void concat(Matrix matrix) {
-        // TODO Auto-generated method stub
-        super.concat(matrix);
-    }
-
-    /* (non-Javadoc)
      * @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint)
      */
     @Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
index 968a597..863d64a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import java.awt.Paint;
+
 /** A subclass of shader that returns the composition of two other shaders, combined by
     an {@link android.graphics.Xfermode} subclass.
 */
@@ -42,5 +44,10 @@
     public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
         // FIXME Implement shader
     }
+
+    @Override
+    Paint getJavaPaint() {
+        return null;
+    }
 }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java
new file mode 100644
index 0000000..46d4c70
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+public class DashPathEffect extends PathEffect {
+
+    private final float[] mIntervals;
+    private final float mPhase;
+
+    /**
+     * The intervals array must contain an even number of entries (>=2), with
+     * the even indices specifying the "on" intervals, and the odd indices
+     * specifying the "off" intervals. phase is an offset into the intervals
+     * array (mod the sum of all of the intervals). The intervals array
+     * controls the length of the dashes. The paint's strokeWidth controls the
+     * thickness of the dashes.
+     * Note: this patheffect only affects drawing with the paint's style is set
+     * to STROKE or STROKE_AND_FILL. It is ignored if the drawing is done with
+     * style == FILL.
+     * @param intervals array of ON and OFF distances
+     * @param phase offset into the intervals array
+     */
+    public DashPathEffect(float intervals[], float phase) {
+        if (intervals.length < 2) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        mIntervals = intervals;
+        mPhase = phase;
+    }
+
+    public float[] getIntervals() {
+        return mIntervals;
+    }
+
+    public float getPhase() {
+        return mPhase;
+    }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
index 1a0dc05..38ffed3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
@@ -16,56 +16,298 @@
 
 package android.graphics;
 
-import java.awt.GradientPaint;
-import java.awt.Color;
 import java.awt.Paint;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
 
 public class LinearGradient extends Shader {
-    
-    private GradientPaint mGradientPaint;
 
-    /** Create a shader that draws a linear gradient along a line.
-        @param x0           The x-coordinate for the start of the gradient line
-        @param y0           The y-coordinate for the start of the gradient line
-        @param x1           The x-coordinate for the end of the gradient line
-        @param y1           The y-coordinate for the end of the gradient line
-        @param  colors      The colors to be distributed along the gradient line
-        @param  positions   May be null. The relative positions [0..1] of
-                            each corresponding color in the colors array. If this is null,
-                            the the colors are distributed evenly along the gradient line.
-        @param  tile        The Shader tiling mode
-    */
-    public LinearGradient(float x0, float y0, float x1, float y1,
-                          int colors[], float positions[], TileMode tile) {
+    private Paint mJavaPaint;
+
+    /**
+     * Create a shader that draws a linear gradient along a line.
+     *
+     * @param x0 The x-coordinate for the start of the gradient line
+     * @param y0 The y-coordinate for the start of the gradient line
+     * @param x1 The x-coordinate for the end of the gradient line
+     * @param y1 The y-coordinate for the end of the gradient line
+     * @param colors The colors to be distributed along the gradient line
+     * @param positions May be null. The relative positions [0..1] of each
+     *            corresponding color in the colors array. If this is null, the
+     *            the colors are distributed evenly along the gradient line.
+     * @param tile The Shader tiling mode
+     */
+    public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
+            TileMode tile) {
         if (colors.length < 2) {
             throw new IllegalArgumentException("needs >= 2 number of colors");
         }
         if (positions != null && colors.length != positions.length) {
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
-        
-        // FIXME implement multi color linear gradient
+
+        if (positions == null) {
+            float spacing = 1.f / (colors.length - 1);
+            positions = new float[colors.length];
+            positions[0] = 0.f;
+            positions[colors.length-1] = 1.f;
+            for (int i = 1; i < colors.length - 1 ; i++) {
+                positions[i] = spacing * i;
+            }
+        }
+
+        mJavaPaint = new MultiPointLinearGradientPaint(x0, y0, x1, y1, colors, positions, tile);
     }
 
-    /** Create a shader that draws a linear gradient along a line.
-        @param x0       The x-coordinate for the start of the gradient line
-        @param y0       The y-coordinate for the start of the gradient line
-        @param x1       The x-coordinate for the end of the gradient line
-        @param y1       The y-coordinate for the end of the gradient line
-        @param  color0  The color at the start of the gradient line.
-        @param  color1  The color at the end of the gradient line.
-        @param  tile    The Shader tiling mode
-    */
-    public LinearGradient(float x0, float y0, float x1, float y1,
-                          int color0, int color1, TileMode tile) {
-        mGradientPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */),
-                x1,y1, new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
+    /**
+     * Create a shader that draws a linear gradient along a line.
+     *
+     * @param x0 The x-coordinate for the start of the gradient line
+     * @param y0 The y-coordinate for the start of the gradient line
+     * @param x1 The x-coordinate for the end of the gradient line
+     * @param y1 The y-coordinate for the end of the gradient line
+     * @param color0 The color at the start of the gradient line.
+     * @param color1 The color at the end of the gradient line.
+     * @param tile The Shader tiling mode
+     */
+    public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
+            TileMode tile) {
+        this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile);
     }
-    
-    //---------- Custom Methods
-    
-    public Paint getPaint() {
-        return mGradientPaint;
+
+    // ---------- Custom Methods
+
+    @Override
+    public Paint getJavaPaint() {
+        return mJavaPaint;
+    }
+
+    private static class MultiPointLinearGradientPaint implements Paint {
+        private final static int GRADIENT_SIZE = 100;
+
+        private final float mX0;
+        private final float mY0;
+        private final float mDx;
+        private final float mDy;
+        private final float mDSize2;
+        private final int[] mColors;
+        private final float[] mPositions;
+        private final TileMode mTile;
+        private int[] mGradient;
+
+        public MultiPointLinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+                float positions[], TileMode tile) {
+                mX0 = x0;
+                mY0 = y0;
+                mDx = x1 - x0;
+                mDy = y1 - y0;
+                mDSize2 = mDx * mDx + mDy * mDy;
+
+                mColors = colors;
+                mPositions = positions;
+                mTile = tile;
+        }
+
+        public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
+                Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+            prepareColors();
+            return new MultiPointLinearGradientPaintContext(cm, deviceBounds,
+                    userBounds, xform, hints);
+        }
+
+        public int getTransparency() {
+            return TRANSLUCENT;
+        }
+
+        private synchronized void prepareColors() {
+            if (mGradient == null) {
+                // actually create an array with an extra size, so that we can really go
+                // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+                mGradient = new int[GRADIENT_SIZE+1];
+
+                int prevPos = 0;
+                int nextPos = 1;
+                for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
+                    // compute current position
+                    float currentPos = (float)i/GRADIENT_SIZE;
+                    while (currentPos > mPositions[nextPos]) {
+                        prevPos = nextPos++;
+                    }
+
+                    float percent = (currentPos - mPositions[prevPos]) /
+                            (mPositions[nextPos] - mPositions[prevPos]);
+
+                    mGradient[i] = getColor(mColors[prevPos], mColors[nextPos], percent);
+                }
+            }
+        }
+
+        /**
+         * Returns the color between c1, and c2, based on the percent of the distance
+         * between c1 and c2.
+         */
+        private int getColor(int c1, int c2, float percent) {
+            int a = getChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+            int r = getChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+            int g = getChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
+            int b = getChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
+            return a << 24 | r << 16 | g << 8 | b;
+        }
+
+        /**
+         * Returns the channel value between 2 values based on the percent of the distance between
+         * the 2 values..
+         */
+        private int getChannel(int c1, int c2, float percent) {
+            return c1 + (int)((percent * (c2-c1)) + .5);
+        }
+
+        private class MultiPointLinearGradientPaintContext implements PaintContext {
+
+            private ColorModel mColorModel;
+            private final Rectangle mDeviceBounds;
+            private final Rectangle2D mUserBounds;
+            private final AffineTransform mXform;
+            private final RenderingHints mHints;
+
+            public MultiPointLinearGradientPaintContext(ColorModel cm, Rectangle deviceBounds,
+                    Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+                mColorModel = cm;
+                // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix?
+                mDeviceBounds = deviceBounds;
+                mUserBounds = userBounds;
+                mXform = xform;
+                mHints = hints;
+            }
+
+            public void dispose() {
+            }
+
+            public ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            public Raster getRaster(int x, int y, int w, int h) {
+                BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+                int[] data = new int[w*h];
+
+                if (mDx == 0) { // vertical gradient
+                    // compute first column and copy to all other columns
+                    int index = 0;
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        int color = getColor(iy + y, mY0, mDy);
+                        for (int ix = 0 ; ix < w ; ix++) {
+                            data[index++] = color;
+                        }
+                    }
+                } else if (mDy == 0) { // horizontal
+                    // compute first line in a tmp array and copy to all lines
+                    int[] line = new int[w];
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        line[ix] = getColor(ix + x, mX0, mDx);
+                    }
+
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        System.arraycopy(line, 0, data, iy*w, line.length);
+                    }
+                } else {
+                    int index = 0;
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        for (int ix = 0 ; ix < w ; ix++) {
+                            data[index++] = getColor(ix + x, iy + y);
+                        }
+                    }
+                }
+
+                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+                return image.getRaster();
+            }
+        }
+
+        /** Returns a color for the easy vertical/horizontal mode */
+        private int getColor(float absPos, float refPos, float refSize) {
+            float pos = (absPos - refPos) / refSize;
+
+            return getIndexFromPos(pos);
+        }
+
+        /**
+         * Returns a color for an arbitrary point.
+         */
+        private int getColor(float x, float y) {
+            // find the x position on the gradient vector.
+            float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+            // from it get the position relative to the vector
+            float pos = (float) ((_x - mX0) / mDx);
+
+            return getIndexFromPos(pos);
+        }
+
+        /**
+         * Returns the color based on the position in the gradient.
+         * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
+         * will use {@link TileMode} value to convert it into a [0,1] value.
+         */
+        private int getIndexFromPos(float pos) {
+            if (pos < 0.f) {
+                switch (mTile) {
+                    case CLAMP:
+                        pos = 0.f;
+                        break;
+                    case REPEAT:
+                        // remove the integer part to stay in the [0,1] range
+                        // careful: this is a negative value, so use ceil instead of floor
+                        pos = pos - (float)Math.ceil(pos);
+                        break;
+                    case MIRROR:
+                        // get the integer and the decimal part
+                        // careful: this is a negative value, so use ceil instead of floor
+                        int intPart = (int)Math.ceil(pos);
+                        pos = pos - intPart;
+                        // 0  -> -1 : mirrored order
+                        // -1 -> -2: normal order
+                        // etc..
+                        // this means if the intpart is even we invert
+                        if ((intPart % 2) == 0) {
+                            pos = 1.f - pos;
+                        }
+                        break;
+                }
+            } else if (pos > 1f) {
+                switch (mTile) {
+                    case CLAMP:
+                        pos = 1.f;
+                        break;
+                    case REPEAT:
+                        // remove the integer part to stay in the [0,1] range
+                        pos = pos - (float)Math.floor(pos);
+                        break;
+                    case MIRROR:
+                        // get the integer and the decimal part
+                        int intPart = (int)Math.floor(pos);
+                        pos = pos - intPart;
+                        // 0 -> 1 : normal order
+                        // 1 -> 2: mirrored
+                        // etc..
+                        // this means if the intpart is odd we invert
+                        if ((intPart % 2) == 1) {
+                            pos = 1.f - pos;
+                        }
+                        break;
+                }
+            }
+
+            int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+            return mGradient[index];
+        }
     }
 }
-
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
index 3974e08..522415c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
@@ -87,8 +87,12 @@
     }
 
     public AffineTransform getTransform() {
-        return new AffineTransform(mValues[0], mValues[1], mValues[2],
-                mValues[3], mValues[4], mValues[5]);
+        // the AffineTransform constructor takes the value in a different order
+        // for a matrix [ 0 1 2 ]
+        //              [ 3 4 5 ]
+        // the order is 0, 3, 1, 4, 2, 5...
+        return new AffineTransform(mValues[0], mValues[3], mValues[1],
+                mValues[4], mValues[2], mValues[5]);
     }
 
     public boolean hasPerspective() {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java
index f3af133..2d03618 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java
@@ -21,6 +21,7 @@
 import android.text.SpannedString;
 import android.text.TextUtils;
 
+import java.awt.BasicStroke;
 import java.awt.Font;
 import java.awt.Toolkit;
 import java.awt.font.FontRenderContext;
@@ -59,23 +60,14 @@
     private final FontRenderContext mFontContext = new FontRenderContext(
             new AffineTransform(), true, true);
 
-    @SuppressWarnings("hiding")
     public static final int ANTI_ALIAS_FLAG       = _Original_Paint.ANTI_ALIAS_FLAG;
-    @SuppressWarnings("hiding")
     public static final int FILTER_BITMAP_FLAG    = _Original_Paint.FILTER_BITMAP_FLAG;
-    @SuppressWarnings("hiding")
     public static final int DITHER_FLAG           = _Original_Paint.DITHER_FLAG;
-    @SuppressWarnings("hiding")
     public static final int UNDERLINE_TEXT_FLAG   = _Original_Paint.UNDERLINE_TEXT_FLAG;
-    @SuppressWarnings("hiding")
     public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG;
-    @SuppressWarnings("hiding")
     public static final int FAKE_BOLD_TEXT_FLAG   = _Original_Paint.FAKE_BOLD_TEXT_FLAG;
-    @SuppressWarnings("hiding")
     public static final int LINEAR_TEXT_FLAG      = _Original_Paint.LINEAR_TEXT_FLAG;
-    @SuppressWarnings("hiding")
     public static final int SUBPIXEL_TEXT_FLAG    = _Original_Paint.SUBPIXEL_TEXT_FLAG;
-    @SuppressWarnings("hiding")
     public static final int DEV_KERN_TEXT_FLAG    = _Original_Paint.DEV_KERN_TEXT_FLAG;
 
     public static class FontMetrics extends _Original_Paint.FontMetrics {
@@ -136,6 +128,19 @@
             this.nativeInt = nativeInt;
         }
         final int nativeInt;
+
+        /** custom for layoutlib */
+        public int getJavaCap() {
+            switch (this) {
+                case BUTT:
+                    return BasicStroke.CAP_BUTT;
+                case ROUND:
+                    return BasicStroke.CAP_ROUND;
+                default:
+                case SQUARE:
+                    return BasicStroke.CAP_SQUARE;
+            }
+        }
     }
 
     /**
@@ -160,6 +165,19 @@
             this.nativeInt = nativeInt;
         }
         final int nativeInt;
+
+        /** custom for layoutlib */
+        public int getJavaJoin() {
+            switch (this) {
+                default:
+                case MITER:
+                    return BasicStroke.JOIN_MITER;
+                case ROUND:
+                    return BasicStroke.JOIN_ROUND;
+                case BEVEL:
+                    return BasicStroke.JOIN_BEVEL;
+            }
+        }
     }
 
     /**
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
index 61b693a..13848c5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import java.awt.Paint;
+
 public class RadialGradient extends Shader {
 
    /** Create a shader that draws a radial gradient given the center and radius.
@@ -58,5 +60,11 @@
        }
        // FIXME Implement shader
    }
+
+    @Override
+    Paint getJavaPaint() {
+        // TODO Auto-generated method stub
+        return null;
+    }
 }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader.java b/tools/layoutlib/bridge/src/android/graphics/Shader.java
index 3a9fda5..0cc5940 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Shader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Shader.java
@@ -16,14 +16,16 @@
 
 package android.graphics;
 
+
+
 /**
  * Shader is the based class for objects that return horizontal spans of colors
  * during drawing. A subclass of Shader is installed in a Paint calling
  * paint.setShader(shader). After that any object (other than a bitmap) that is
  * drawn with that paint will get its color(s) from the shader.
  */
-public class Shader {
-    
+public abstract class Shader {
+
     private final Matrix mMatrix = new Matrix();
 
     public enum TileMode {
@@ -41,7 +43,7 @@
          * mirror images so that adjacent images always seam
          */
         MIRROR  (2);
-    
+
         TileMode(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -57,7 +59,7 @@
         if (localM != null) {
             localM.set(mMatrix);
         }
-        
+
         return !mMatrix.isIdentity();
     }
 
@@ -73,4 +75,9 @@
             mMatrix.reset();
         }
     }
+
+    /**
+     * Returns a java.awt.Paint object matching this shader.
+     */
+    abstract java.awt.Paint getJavaPaint();
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
index e79e970..21d8244 100644
--- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import java.awt.Paint;
+
 public class SweepGradient extends Shader {
 
     /**
@@ -41,7 +43,7 @@
             throw new IllegalArgumentException(
                         "color and position arrays must be of equal length");
         }
-        
+
         // FIXME Implement shader
     }
 
@@ -56,5 +58,11 @@
     public SweepGradient(float cx, float cy, int color0, int color1) {
         // FIXME Implement shader
     }
+
+    @Override
+    Paint getJavaPaint() {
+        // TODO Auto-generated method stub
+        return null;
+    }
 }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 5a13b0b..2ed8641 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -43,9 +43,11 @@
     public final static String[] RENAMED_CLASSES =
         new String[] {
             "android.graphics.Bitmap",              "android.graphics._Original_Bitmap",
+            "android.graphics.BitmapFactory",       "android.graphics._Original_BitmapFactory",
             "android.graphics.BitmapShader",        "android.graphics._Original_BitmapShader",
             "android.graphics.Canvas",              "android.graphics._Original_Canvas",
             "android.graphics.ComposeShader",       "android.graphics._Original_ComposeShader",
+            "android.graphics.DashPathEffect",       "android.graphics._Original_DashPathEffect",
             "android.graphics.LinearGradient",      "android.graphics._Original_LinearGradient",
             "android.graphics.Matrix",              "android.graphics._Original_Matrix",
             "android.graphics.Paint",               "android.graphics._Original_Paint",