Merge "Updated developer groups documentation to point to Stack Overflow, also rearranged a few things." into eclair
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index 96d44b6..4491a8a 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -75,4 +75,9 @@
      * when a UMS host is detected.
      */
     void setAutoStartUms(boolean value);
+
+    /**
+     * Shuts down the MountService and gracefully unmounts all external media.
+     */
+    void shutdown();
 }
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index 3679e47..bc76180 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -17,6 +17,8 @@
 package android.os;
 
 import java.io.IOException;
+import android.os.ServiceManager;
+import android.os.IMountService;
 
 /**
  * Class that provides access to some of the power management functions.
@@ -97,5 +99,19 @@
      * @throws IOException if reboot fails for some reason (eg, lack of
      *         permission)
      */
-    public static native void reboot(String reason) throws IOException;
+    public static void reboot(String reason) throws IOException
+    {
+        IMountService mSvc = IMountService.Stub.asInterface(
+                ServiceManager.getService("mount"));
+
+        if (mSvc != null) {
+            try {
+                mSvc.shutdown();
+            } catch (Exception e) {
+            }
+        }
+        rebootNative(reason);
+    }
+
+    private static native void rebootNative(String reason) throws IOException ;
 }
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/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 5d89c46..9581080 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -55,12 +55,12 @@
                 }
             }, 2));
 
-    final float mPastX[] = new float[NUM_PAST];
-    final float mPastY[] = new float[NUM_PAST];
-    final long mPastTime[] = new long[NUM_PAST];
+    final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+    final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+    final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
 
-    float mYVelocity;
-    float mXVelocity;
+    float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
+    float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
 
     private VelocityTracker mNext;
 
@@ -105,7 +105,9 @@
      * Reset the velocity tracker back to its initial state.
      */
     public void clear() {
-        mPastTime[0] = 0;
+        for (int i = 0; i < MotionEvent.BASE_AVAIL_POINTERS; i++) {
+            mPastTime[i][0] = 0;
+        }
     }
     
     /**
@@ -120,18 +122,21 @@
     public void addMovement(MotionEvent ev) {
         long time = ev.getEventTime();
         final int N = ev.getHistorySize();
-        for (int i=0; i<N; i++) {
-            addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
-                    ev.getHistoricalEventTime(i));
+        final int pointerCount = ev.getPointerCount();
+        for (int p = 0; p < pointerCount; p++) {
+            for (int i=0; i<N; i++) {
+                addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
+                        ev.getHistoricalEventTime(i));
+            }
+            addPoint(p, ev.getX(p), ev.getY(p), time);
         }
-        addPoint(ev.getX(), ev.getY(), time);
     }
 
-    private void addPoint(float x, float y, long time) {
+    private void addPoint(int pos, float x, float y, long time) {
         int drop = -1;
         int i;
         if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
-        final long[] pastTime = mPastTime;
+        final long[] pastTime = mPastTime[pos];
         for (i=0; i<NUM_PAST; i++) {
             if (pastTime[i] == 0) {
                 break;
@@ -146,8 +151,8 @@
             drop = 0;
         }
         if (drop == i) drop--;
-        final float[] pastX = mPastX;
-        final float[] pastY = mPastY;
+        final float[] pastX = mPastX[pos];
+        final float[] pastY = mPastY[pos];
         if (drop >= 0) {
             if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
             final int start = drop+1;
@@ -190,44 +195,48 @@
      * must be positive.
      */
     public void computeCurrentVelocity(int units, float maxVelocity) {
-        final float[] pastX = mPastX;
-        final float[] pastY = mPastY;
-        final long[] pastTime = mPastTime;
-        
-        // Kind-of stupid.
-        final float oldestX = pastX[0];
-        final float oldestY = pastY[0];
-        final long oldestTime = pastTime[0];
-        float accumX = 0;
-        float accumY = 0;
-        int N=0;
-        while (N < NUM_PAST) {
-            if (pastTime[N] == 0) {
-                break;
+        for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
+            final float[] pastX = mPastX[pos];
+            final float[] pastY = mPastY[pos];
+            final long[] pastTime = mPastTime[pos];
+
+            // Kind-of stupid.
+            final float oldestX = pastX[0];
+            final float oldestY = pastY[0];
+            final long oldestTime = pastTime[0];
+            float accumX = 0;
+            float accumY = 0;
+            int N=0;
+            while (N < NUM_PAST) {
+                if (pastTime[N] == 0) {
+                    break;
+                }
+                N++;
             }
-            N++;
+            // Skip the last received event, since it is probably pretty noisy.
+            if (N > 3) N--;
+
+            for (int i=1; i < N; i++) {
+                final int dur = (int)(pastTime[i] - oldestTime);
+                if (dur == 0) continue;
+                float dist = pastX[i] - oldestX;
+                float vel = (dist/dur) * units;   // pixels/frame.
+                if (accumX == 0) accumX = vel;
+                else accumX = (accumX + vel) * .5f;
+
+                dist = pastY[i] - oldestY;
+                vel = (dist/dur) * units;   // pixels/frame.
+                if (accumY == 0) accumY = vel;
+                else accumY = (accumY + vel) * .5f;
+            }
+            mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+                    : Math.min(accumX, maxVelocity);
+            mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+                    : Math.min(accumY, maxVelocity);
+
+            if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
+                    + mXVelocity + " N=" + N);
         }
-        // Skip the last received event, since it is probably pretty noisy.
-        if (N > 3) N--;
-        
-        for (int i=1; i < N; i++) {
-            final int dur = (int)(pastTime[i] - oldestTime);
-            if (dur == 0) continue;
-            float dist = pastX[i] - oldestX;
-            float vel = (dist/dur) * units;   // pixels/frame.
-            if (accumX == 0) accumX = vel;
-            else accumX = (accumX + vel) * .5f;
-            
-            dist = pastY[i] - oldestY;
-            vel = (dist/dur) * units;   // pixels/frame.
-            if (accumY == 0) accumY = vel;
-            else accumY = (accumY + vel) * .5f;
-        }
-        mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
-        mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
-        
-        if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
-                + mXVelocity + " N=" + N);
     }
     
     /**
@@ -237,7 +246,7 @@
      * @return The previously computed X velocity.
      */
     public float getXVelocity() {
-        return mXVelocity;
+        return mXVelocity[0];
     }
     
     /**
@@ -247,6 +256,32 @@
      * @return The previously computed Y velocity.
      */
     public float getYVelocity() {
-        return mYVelocity;
+        return mYVelocity[0];
+    }
+    
+    /**
+     * Retrieve the last computed X velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     * 
+     * @param pos Which pointer's velocity to return.
+     * @return The previously computed X velocity.
+     * 
+     * @hide Pending API approval
+     */
+    public float getXVelocity(int pos) {
+        return mXVelocity[pos];
+    }
+    
+    /**
+     * Retrieve the last computed Y velocity.  You must first call
+     * {@link #computeCurrentVelocity(int)} before calling this function.
+     * 
+     * @param pos Which pointer's velocity to return.
+     * @return The previously computed Y velocity.
+     * 
+     * @hide Pending API approval
+     */
+    public float getYVelocity(int pos) {
+        return mYVelocity[pos];
     }
 }
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 8308fd1..7d0c53b 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() {
@@ -2098,6 +2122,7 @@
             mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
             mLastWidthSent = newWidth;
             mLastHeightSent = newHeight;
+            mAnchorX = mAnchorY = 0;
             return true;
         }
         return false;
@@ -3698,17 +3723,10 @@
     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 int mAnchorX;
-    private int mAnchorY;
-
-    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();
             // reset the zoom overview mode so that the page won't auto grow
@@ -3718,29 +3736,22 @@
             if (inEditingMode() && nativeFocusCandidateIsPassword()) {
                 mWebTextView.setInPassword(false);
             }
-            // start multi (2-pointer) touch
-            mPreviewZoomOnly = true;
-            float x0 = ev.getX(0);
-            float y0 = ev.getY(0);
-            float x1 = ev.getX(1);
-            float y1 = ev.getY(1);
-            mLastTouchTime = ev.getEventTime();
-            mPinchDistance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
-                    * (y0 - y1));
-            mZoomCenterX = mZoomCenterY = 0;
-        } else if ((action & 0xff) == MotionEvent.ACTION_POINTER_UP) {
-            mPreviewZoomOnly = false;
-            // for testing only, default don't reflow now
-            boolean reflowNow = !getSettings().getPluginsEnabled();
-            // force zoom after mPreviewZoomOnly is set to false so that the new
-            // view size will be passed to the WebKit
-            if (reflowNow && (mZoomCenterX != 0) && (mZoomCenterY != 0)) {
+            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();
+                // force zoom after mPreviewZoomOnly is set to false so that the
+                // new view size will be passed to the WebKit
+                setNewZoomScale(mActualScale, reflowNow, true);
+                // call invalidate() to draw without zoom filter
+                invalidate();
             }
-            setNewZoomScale(mActualScale, reflowNow, true);
-            // call invalidate() to draw without zoom filter
-            invalidate();
             // adjust the edit text view if needed
             if (inEditingMode()) {
                 adjustTextView(true);
@@ -3749,44 +3760,29 @@
             // 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);
-            long time = ev.getEventTime();
-            // add distance/time checking to avoid the minor shift right before
-            // lifting the fingers after a pause
-            if (Math.abs(scale - mActualScale) >= 0.01f
-                    && ((time - mLastTouchTime) < 300 || Math.abs(distance
-                            - mPinchDistance) > (10 * mDefaultScale))) {
+            if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
+                mPreviewZoomOnly = true;
                 // limit the scale change per step
                 if (scale > mActualScale) {
                     scale = Math.min(scale, mActualScale * 1.25f);
                 } 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;
-                mLastTouchTime = time;
+                return true;
             }
-        } else {
-            Log.w(LOGTAG, action + " should not happen during doMultiTouch");
             return false;
         }
-        return true;
     }
 
     @Override
@@ -3800,9 +3796,12 @@
                     + mTouchMode);
         }
 
-        if (mSupportMultiTouch && getSettings().supportZoom()
+        // FIXME: we may consider to give WebKit an option to handle multi-touch
+        // events later.
+        if (mSupportMultiTouch && mMinZoomScale < mMaxZoomScale
                 && ev.getPointerCount() > 1) {
-            return doMultiTouch(ev);
+            mLastTouchTime = ev.getEventTime();
+            return mScaleDetector.onTouchEvent(ev);
         }
 
         int action = ev.getAction();
@@ -5149,7 +5148,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,
@@ -5157,14 +5157,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);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 27b67d1..c3817fb 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, nativeGetContentMinPrefWidth());
+                width = Math.max(w, textwrapWidth);
             }
         }
         nativeSetSize(width, width == w ? h : Math.round((float) width * h / w),
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index 2060cf8..c110f95 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -32,6 +32,7 @@
 import android.os.Power;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.IMountService;
 
 import com.android.internal.telephony.ITelephony;
 import android.util.Log;
@@ -189,6 +190,10 @@
         final IBluetooth bluetooth =
                 IBluetooth.Stub.asInterface(ServiceManager.checkService(
                         BluetoothAdapter.BLUETOOTH_SERVICE));
+
+        final IMountService mount =
+                IMountService.Stub.asInterface(
+                        ServiceManager.checkService("mount"));
         
         try {
             bluetoothOff = bluetooth == null ||
@@ -241,6 +246,17 @@
             SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
         }
 
+        // Shutdown MountService to ensure media is in a safe state
+        try {
+            if (mount != null) {
+                mount.shutdown();
+            } else {
+                Log.w(TAG, "MountService unavailable for shutdown");
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Exception during MountService shutdown", e);
+        }
+
         //shutdown power
         Log.i(TAG, "Performing low-level shutdown...");
         Power.shutdown();
diff --git a/core/jni/android_os_Power.cpp b/core/jni/android_os_Power.cpp
index df5edba..a46c2dd 100644
--- a/core/jni/android_os_Power.cpp
+++ b/core/jni/android_os_Power.cpp
@@ -105,7 +105,7 @@
     { "setLastUserActivityTimeout", "(J)I", (void*)setLastUserActivityTimeout },
     { "setScreenState", "(Z)I", (void*)setScreenState },
     { "shutdown", "()V", (void*)android_os_Power_shutdown },
-    { "reboot", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
+    { "rebootNative", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
 };
 
 int register_android_os_Power(JNIEnv *env)
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/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/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 204389e..ddf7c56 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -108,13 +108,86 @@
 
     BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+            String action = intent.getAction();
+
+            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
                 Thread thread = new Thread(mListener, MountListener.class.getName());
                 thread.start();
             }
         }
     };
 
+    public void shutdown() {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.SHUTDOWN)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires SHUTDOWN permission");
+        }
+
+        Log.d(TAG, "Shutting down");
+        String state = Environment.getExternalStorageState();
+
+        if (state.equals(Environment.MEDIA_SHARED)) {
+            /*
+             * If the media is currently shared, unshare it.
+             * XXX: This is still dangerous!. We should not
+             * be rebooting at *all* if UMS is enabled, since
+             * the UMS host could have dirty FAT cache entries
+             * yet to flush.
+             */
+            try {
+               setMassStorageEnabled(false);
+            } catch (Exception e) {
+                Log.e(TAG, "ums disable failed", e);
+            }
+        } else if (state.equals(Environment.MEDIA_CHECKING)) {
+            /*
+             * If the media is being checked, then we need to wait for
+             * it to complete before being able to proceed.
+             */
+            // XXX: @hackbod - Should we disable the ANR timer here?
+            int retries = 30;
+            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException iex) {
+                    Log.e(TAG, "Interrupted while waiting for media", iex);
+                    break;
+                }
+                state = Environment.getExternalStorageState();
+            }
+            if (retries == 0) {
+                Log.e(TAG, "Timed out waiting for media to check");
+            }
+        }
+
+        if (state.equals(Environment.MEDIA_MOUNTED)) {
+            /*
+             * If the media is mounted, then gracefully unmount it.
+             */
+            try {
+                String m = Environment.getExternalStorageDirectory().toString();
+                unmountMedia(m);
+
+                int retries = 12;
+                while (!state.equals(Environment.MEDIA_UNMOUNTED) && (retries-- >=0)) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException iex) {
+                        Log.e(TAG, "Interrupted while waiting for media", iex);
+                        break;
+                    }
+                    state = Environment.getExternalStorageState();
+                }
+                if (retries == 0) {
+                    Log.e(TAG, "Timed out waiting for media to unmount");
+            }
+            } catch (Exception e) {
+                Log.e(TAG, "external storage unmount failed", e);
+            }
+        }
+    }
+
     /**
      * @return true if USB mass storage support is enabled.
      */
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/TransformTest/Android.mk b/tests/TransformTest/Android.mk
new file mode 100644
index 0000000..2d3637d
--- /dev/null
+++ b/tests/TransformTest/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TransformTest
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/TransformTest/AndroidManifest.xml b/tests/TransformTest/AndroidManifest.xml
new file mode 100644
index 0000000..5c9995f
--- /dev/null
+++ b/tests/TransformTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.transform">
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="7" />
+    <application android:label="TransformTest">
+        <activity android:name="TransformTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/TransformTest/res/drawable/logo.png b/tests/TransformTest/res/drawable/logo.png
new file mode 100644
index 0000000..4d717a8
--- /dev/null
+++ b/tests/TransformTest/res/drawable/logo.png
Binary files differ
diff --git a/tests/TransformTest/res/values/strings.xml b/tests/TransformTest/res/values/strings.xml
new file mode 100644
index 0000000..a0eb81f
--- /dev/null
+++ b/tests/TransformTest/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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>
+    <string name="act_title">TransformTest</string>
+</resources>
diff --git a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
new file mode 100644
index 0000000..31ee120
--- /dev/null
+++ b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2008 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 com.google.android.test.transform;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class TransformTestActivity extends Activity {
+    public TransformTestActivity() {
+        super();
+        init(false);
+    }
+    
+    public TransformTestActivity(boolean noCompat) {
+        super();
+        init(noCompat);
+    }
+    
+    public void init(boolean noCompat) {
+
+    }
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        this.setTitle(R.string.act_title);
+        LinearLayout root = new LinearLayout(this);
+        root.setOrientation(LinearLayout.VERTICAL);
+
+        TransformView view = new TransformView(getApplicationContext());
+        Drawable drawable = getResources().getDrawable(R.drawable.logo);
+        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicWidth());
+        view.setDrawable(drawable);
+
+        root.addView(view);
+        setContentView(root);
+    }
+    
+    private class TransformView extends View {
+        private Drawable mDrawable;
+        private float mPosX;
+        private float mPosY;
+        private float mScale = 1.f;
+        private Matrix mMatrix;
+        private ScaleGestureDetector mDetector;
+        
+        private float mLastX;
+        private float mLastY;
+        
+        private class Listener implements ScaleGestureDetector.OnScaleGestureListener {
+
+            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;
+                    } else {
+                        mScale = 10.f;
+                    }
+                } else {
+                    mScale = 0.1f;
+                }
+                
+                Log.d("ttest", "mScale: " + mScale + " mPos: (" + mPosX + ", " + mPosY + ")");
+                
+                float sizeX = mDrawable.getIntrinsicWidth()/2;
+                float sizeY = mDrawable.getIntrinsicHeight()/2;
+                float centerX = detector.getFocusX();
+                float centerY = detector.getFocusY();
+                float diffX = centerX - mPosX;
+                float diffY = centerY - mPosY;
+                diffX = diffX*scale - diffX;
+                diffY = diffY*scale - diffY;
+                mPosX -= diffX;
+                mPosY -= diffY;
+                mMatrix.reset();
+                mMatrix.postTranslate(-sizeX, -sizeY);
+                mMatrix.postScale(mScale, mScale);
+                mMatrix.postTranslate(mPosX, mPosY);
+                                
+                invalidate();
+
+                return true;
+            }
+
+            public boolean onScaleBegin(ScaleGestureDetector detector) {
+                return true;
+            }
+
+            public void onScaleEnd(ScaleGestureDetector detector) {
+                mLastX = detector.getFocusX();
+                mLastY = detector.getFocusY();
+            }            
+        }
+        
+        public TransformView(Context context) {
+            super(context);
+            mMatrix = new Matrix();
+            mDetector = new ScaleGestureDetector(context, new Listener());
+            DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+            mPosX = metrics.widthPixels/2;
+            mPosY = metrics.heightPixels/2;
+        }
+        
+        public void setDrawable(Drawable d) {
+            mDrawable = d;
+            
+            float sizeX = mDrawable.getIntrinsicWidth()/2;
+            float sizeY = mDrawable.getIntrinsicHeight()/2;
+            mMatrix.reset();
+            mMatrix.postTranslate(-sizeX, -sizeY);
+            mMatrix.postScale(mScale, mScale);
+            mMatrix.postTranslate(mPosX, mPosY);
+        }
+        
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            mDetector.onTouchEvent(event);
+            
+            // 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 true;
+        }
+        
+        @Override
+        public void onDraw(Canvas canvas) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.concat(mMatrix);
+            mDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+    }
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index fdcada4..02b46aa 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1745,31 +1745,33 @@
                     fprintf(stderr, "ERROR: %s\n", error.string());
                     return -1;
                 }
-                // asdf     --> package.asdf
-                // .asdf  .a.b  --> package.asdf package.a.b
-                // asdf.adsf --> asdf.asdf
-                String8 rule("-keep class ");
-                const char* p = name.string();
-                const char* q = strchr(p, '.');
-                if (p == q) {
-                    rule += pkg;
-                    rule += name;
-                } else if (q == NULL) {
-                    rule += pkg;
-                    rule += ".";
-                    rule += name;
-                } else {
-                    rule += name;
+                if (name.length() > 0) {
+                    // asdf     --> package.asdf
+                    // .asdf  .a.b  --> package.asdf package.a.b
+                    // asdf.adsf --> asdf.asdf
+                    String8 rule("-keep class ");
+                    const char* p = name.string();
+                    const char* q = strchr(p, '.');
+                    if (p == q) {
+                        rule += pkg;
+                        rule += name;
+                    } else if (q == NULL) {
+                        rule += pkg;
+                        rule += ".";
+                        rule += name;
+                    } else {
+                        rule += name;
+                    }
+
+                    String8 location = tag;
+                    location += " ";
+                    location += assFile->getSourceFile();
+                    char lineno[20];
+                    sprintf(lineno, ":%d", tree.getLineNumber());
+                    location += lineno;
+
+                    keep->add(rule, location);
                 }
-
-                String8 location = tag;
-                location += " ";
-                location += assFile->getSourceFile();
-                char lineno[20];
-                sprintf(lineno, ":%d", tree.getLineNumber());
-                location += lineno;
-
-                keep->add(rule, location);
             }
         }
     }
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..9f4dfd0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -125,13 +125,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 +239,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 +282,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 +412,7 @@
 
         g.setColor(new Color(color));
 
-        getGraphics2d().fillRect(0, 0, getWidth(), getHeight());
+        g.fillRect(0, 0, getWidth(), getHeight());
 
         g.setComposite(composite);
 
@@ -953,10 +960,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 +971,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 +1032,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/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
index 1a0dc05..945a539 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
@@ -19,53 +19,302 @@
 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.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
 
 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 (colors.length == 2) { // for 2 colors: use the Java implementation
+            // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
+            // If true the alpha is read from the int.
+            mJavaPaint = new GradientPaint(x0, y0, new Color(colors[0], true /* hasalpha */),
+                    x1, y1, new Color(colors[1], true /* hasalpha */), tile != TileMode.CLAMP);
+        } else {
+            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) {
+        // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
+        // If true the alpha is read from the int.
+        mJavaPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */), x1, y1,
+                new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
     }
-    
-    //---------- 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) {
+                SampleModel sampleModel = mColorModel.createCompatibleSampleModel(w, h);
+                WritableRaster raster = Raster.createWritableRaster(sampleModel,
+                        new java.awt.Point(x, y));
+
+                DataBuffer data = raster.getDataBuffer();
+
+                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.setElem(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);
+                    }
+
+                    int index = 0;
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        for (int ix = 0 ; ix < w ; ix++) {
+                            data.setElem(index++, line[ix]);
+                        }
+                    }
+                } else {
+                    int index = 0;
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        for (int ix = 0 ; ix < w ; ix++) {
+                            data.setElem(index++, getColor(ix + x, iy + y));
+                        }
+                    }
+                }
+
+                return raster;
+            }
+        }
+
+        /** 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..312dab3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java
@@ -59,23 +59,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 {
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..2623570 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,6 +43,7 @@
     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",