Merge "Only store Application context"
diff --git a/api/current.xml b/api/current.xml
index a30426a..35daa43c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -104013,6 +104013,17 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<field name="AAC"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="AMR_NB"
  type="int"
  transient="false"
@@ -104024,6 +104035,17 @@
  visibility="public"
 >
 </field>
+<field name="AMR_WB"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="DEFAULT"
  type="int"
  transient="false"
@@ -104191,6 +104213,28 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<field name="AMR_NB"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="AMR_WB"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="DEFAULT"
  type="int"
  transient="false"
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 5da04f1..c958e1b 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -493,7 +493,7 @@
                 // We need to save the state now, if we don't currently
                 // already have it or the activity is currently resumed.
                 final Bundle childState = new Bundle();
-                r.activity.onSaveInstanceState(childState);
+                r.activity.performSaveInstanceState(childState);
                 r.instanceState = childState;
             }
             if (r.instanceState != null) {
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
new file mode 100644
index 0000000..7b083f1
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -0,0 +1,299 @@
+/*
+ * 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.bluetooth;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.DhcpInfo;
+import android.net.LinkAddress;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class tracks the data connection associated with Bluetooth
+ * reverse tethering. This is a singleton class and an instance will be
+ * created by ConnectivityService. BluetoothService will call into this
+ * when a reverse tethered connection needs to be activated.
+ *
+ * @hide
+ */
+public class BluetoothTetheringDataTracker implements NetworkStateTracker {
+    private static final String NETWORKTYPE = "BLUETOOTH_TETHER";
+    private static final String TAG = "BluetoothTethering";
+
+    private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
+    private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
+    private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
+    private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
+
+    private LinkProperties mLinkProperties;
+    private LinkCapabilities mLinkCapabilities;
+    private NetworkInfo mNetworkInfo;
+
+    private BluetoothPan mBluetoothPan;
+    private BluetoothDevice mDevice;
+    private static String mIface;
+
+    /* For sending events to connectivity service handler */
+    private Handler mCsHandler;
+    private Context mContext;
+    public static BluetoothTetheringDataTracker sInstance;
+
+    private BluetoothTetheringDataTracker() {
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORKTYPE, "");
+        mLinkProperties = new LinkProperties();
+        mLinkCapabilities = new LinkCapabilities();
+
+        mNetworkInfo.setIsAvailable(false);
+        setTeardownRequested(false);
+    }
+
+    public static synchronized BluetoothTetheringDataTracker getInstance() {
+        if (sInstance == null) sInstance = new BluetoothTetheringDataTracker();
+        return sInstance;
+    }
+
+    public Object Clone() throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+    public void setTeardownRequested(boolean isRequested) {
+        mTeardownRequested.set(isRequested);
+    }
+
+    public boolean isTeardownRequested() {
+        return mTeardownRequested.get();
+    }
+
+    /**
+     * Begin monitoring connectivity
+     */
+    public void startMonitoring(Context context, Handler target) {
+        mContext = context;
+        mCsHandler = target;
+        mBluetoothPan = new BluetoothPan(mContext);
+    }
+
+    /**
+     * Disable connectivity to a network
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean teardown() {
+        mTeardownRequested.set(true);
+        for (BluetoothDevice device: mBluetoothPan.getConnectedDevices()) {
+            mBluetoothPan.disconnect(device);
+        }
+        return true;
+    }
+
+    /**
+     * Re-enable connectivity to a network after a {@link #teardown()}.
+     */
+    public boolean reconnect() {
+        mTeardownRequested.set(false);
+        //Ignore
+        return true;
+    }
+
+    /**
+     * Turn the wireless radio off for a network.
+     * @param turnOn {@code true} to turn the radio on, {@code false}
+     */
+    public boolean setRadio(boolean turnOn) {
+        return true;
+    }
+
+    /**
+     * @return true - If are we currently tethered with another device.
+     */
+    public synchronized boolean isAvailable() {
+        return mNetworkInfo.isAvailable();
+    }
+
+    /**
+     * Tells the underlying networking system that the caller wants to
+     * begin using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature to be used
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     * TODO: needs to go away
+     */
+    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        return -1;
+    }
+
+    /**
+     * Tells the underlying networking system that the caller is finished
+     * using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature that is no longer needed.
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     * TODO: needs to go away
+     */
+    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        return -1;
+    }
+
+    /**
+     * @param enabled
+     */
+    public void setDataEnable(boolean enabled) {
+        android.util.Log.d(TAG, "setDataEnabled: IGNORING enabled=" + enabled);
+    }
+
+    /**
+     * Check if private DNS route is set for the network
+     */
+    public boolean isPrivateDnsRouteSet() {
+        return mPrivateDnsRouteSet.get();
+    }
+
+    /**
+     * Set a flag indicating private DNS route is set
+     */
+    public void privateDnsRouteSet(boolean enabled) {
+        mPrivateDnsRouteSet.set(enabled);
+    }
+
+    /**
+     * Fetch NetworkInfo for the network
+     */
+    public synchronized NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    /**
+     * Fetch LinkProperties for the network
+     */
+    public synchronized LinkProperties getLinkProperties() {
+        return new LinkProperties(mLinkProperties);
+    }
+
+   /**
+     * A capability is an Integer/String pair, the capabilities
+     * are defined in the class LinkSocket#Key.
+     *
+     * @return a copy of this connections capabilities, may be empty but never null.
+     */
+    public LinkCapabilities getLinkCapabilities() {
+        return new LinkCapabilities(mLinkCapabilities);
+    }
+
+    /**
+     * Fetch default gateway address for the network
+     */
+    public int getDefaultGatewayAddr() {
+        return mDefaultGatewayAddr.get();
+    }
+
+    /**
+     * Check if default route is set
+     */
+    public boolean isDefaultRouteSet() {
+        return mDefaultRouteSet.get();
+    }
+
+    /**
+     * Set a flag indicating default route is set for the network
+     */
+    public void defaultRouteSet(boolean enabled) {
+        mDefaultRouteSet.set(enabled);
+    }
+
+    /**
+     * Return the system properties name associated with the tcp buffer sizes
+     * for this network.
+     */
+    public String getTcpBufferSizesPropName() {
+        return "net.tcp.buffersize.wifi";
+    }
+
+
+    public synchronized void startReverseTether(String iface, BluetoothDevice device) {
+        mIface = iface;
+        mDevice = device;
+        Thread dhcpThread = new Thread(new Runnable() {
+            public void run() {
+                //TODO(): Add callbacks for failure and success case.
+                //Currently this thread runs independently.
+                DhcpInfo dhcpInfo = new DhcpInfo();
+                if (!NetworkUtils.runDhcp(mIface, dhcpInfo)) {
+                    Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
+                    return;
+                }
+                mLinkProperties.addLinkAddress(new LinkAddress(
+                    NetworkUtils.intToInetAddress(dhcpInfo.ipAddress),
+                    NetworkUtils.intToInetAddress(dhcpInfo.netmask)));
+                mLinkProperties.setGateway(NetworkUtils.intToInetAddress(dhcpInfo.gateway));
+                InetAddress dns1Addr = NetworkUtils.intToInetAddress(dhcpInfo.dns1);
+                if (dns1Addr == null || dns1Addr.equals("0.0.0.0")) {
+                    mLinkProperties.addDns(dns1Addr);
+                }
+                InetAddress dns2Addr = NetworkUtils.intToInetAddress(dhcpInfo.dns2);
+                if (dns2Addr == null || dns2Addr.equals("0.0.0.0")) {
+                    mLinkProperties.addDns(dns2Addr);
+                }
+                mLinkProperties.setInterfaceName(mIface);
+
+                mNetworkInfo.setIsAvailable(true);
+                mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+
+                Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
+                msg.sendToTarget();
+
+                msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+                msg.sendToTarget();
+            }
+        });
+        dhcpThread.start();
+    }
+
+    public synchronized void stopReverseTether(String iface) {
+        NetworkUtils.stopDhcp(iface);
+
+        mLinkProperties.clear();
+        mNetworkInfo.setIsAvailable(false);
+        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+
+        Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
+        msg.sendToTarget();
+
+        msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+        msg.sendToTarget();
+    }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 3d685cb..7e809f5 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -213,10 +213,16 @@
      */
     public static final int TYPE_WIMAX       = 6;
 
+    /**
+     * Bluetooth data connection. This is used for Bluetooth reverse tethering.
+     * @hide
+     */
+    public static final int TYPE_BLUETOOTH   = 7;
+
     /** {@hide} */
-    public static final int TYPE_DUMMY       = 7;
+    public static final int TYPE_DUMMY       = 8;
     /** {@hide} TODO: Need to adjust this for WiMAX. */
-    public static final int MAX_RADIO_TYPE   = TYPE_WIFI;
+    public static final int MAX_RADIO_TYPE   = TYPE_DUMMY;
     /** {@hide} TODO: Need to adjust this for WiMAX. */
     public static final int MAX_NETWORK_TYPE = TYPE_DUMMY;
 
diff --git a/core/java/android/nfc/technology/Ndef.java b/core/java/android/nfc/technology/Ndef.java
index cd12249..53db0c5 100644
--- a/core/java/android/nfc/technology/Ndef.java
+++ b/core/java/android/nfc/technology/Ndef.java
@@ -140,6 +140,7 @@
             return null;
         }
     }
+
     /**
      * Overwrite the primary NDEF message
      * @throws IOException
diff --git a/core/java/android/nfc/technology/NdefFormatable.java b/core/java/android/nfc/technology/NdefFormatable.java
index 899b95f..222c558 100644
--- a/core/java/android/nfc/technology/NdefFormatable.java
+++ b/core/java/android/nfc/technology/NdefFormatable.java
@@ -45,16 +45,6 @@
     }
 
     /**
-     * Returns whether a tag can be formatted with {@link
-     * NdefFormatable#format(NdefMessage)}
-     */
-    public boolean canBeFormatted() throws IOException {
-        checkConnected();
-
-        throw new UnsupportedOperationException();
-    }
-
-    /**
      * Formats a tag as NDEF, if possible. You may supply a first
      * NdefMessage to be written on the tag.
      */
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index ff8be15..c04bb52 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -425,9 +425,11 @@
             }
         } else if (name.equals("Interface")) {
             String iface = propValues[1];
-            mBluetoothService.handlePanDeviceStateChange(device, iface,
-                                          BluetoothPan.STATE_CONNECTED,
-                                          BluetoothPan.LOCAL_PANU_ROLE);
+            if (!iface.equals("")) {
+                mBluetoothService.handlePanDeviceStateChange(device, iface,
+                                              BluetoothPan.STATE_CONNECTED,
+                                              BluetoothPan.LOCAL_PANU_ROLE);
+            }
         }
     }
 
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index eaf49bb..9261ff6 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -34,6 +34,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfileState;
 import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothTetheringDataTracker;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
@@ -54,7 +55,6 @@
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemService;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
@@ -79,11 +79,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 public class BluetoothService extends IBluetooth.Stub {
     private static final String TAG = "BluetoothService";
@@ -169,6 +167,8 @@
     private static String mDockAddress;
     private String mDockPin;
 
+    private String mIface;
+
     private int mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
 
     private static class RemoteService {
@@ -1582,7 +1582,6 @@
         }
         if (prevState == state) return;
 
-        // TODO: We might need this for PANU role too.
         if (role == BluetoothPan.LOCAL_NAP_ROLE) {
             if (state == BluetoothPan.STATE_CONNECTED) {
                 ifaceAddr = enableTethering(iface);
@@ -1593,6 +1592,16 @@
                     ifaceAddr = null;
                 }
             }
+        } else {
+            // PANU Role = reverse Tether
+            if (state == BluetoothPan.STATE_CONNECTED) {
+                mIface = iface;
+                BluetoothTetheringDataTracker.getInstance().startReverseTether(iface, device);
+            } else if (state == BluetoothPan.STATE_DISCONNECTED &&
+                  (prevState == BluetoothPan.STATE_CONNECTED ||
+                  prevState == BluetoothPan.STATE_DISCONNECTING)) {
+                BluetoothTetheringDataTracker.getInstance().stopReverseTether(mIface);
+            }
         }
 
         Pair<Integer, String> value = new Pair<Integer, String>(state, ifaceAddr);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 55d3b16..33ea66c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4674,8 +4674,8 @@
     }
 
     /**
-     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
-     * KeyEvent.Callback.onKeyMultiple()}: perform press of the view
+     * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
+     * KeyEvent.Callback.onKeyDown()}: perform press of the view
      * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
      * is released, if the view is enabled and clickable.
      *
@@ -4718,8 +4718,8 @@
     }
 
     /**
-     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
-     * KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view
+     * Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
+     * KeyEvent.Callback.onKeyUp()}: perform clicking of the view
      * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
      * {@link KeyEvent#KEYCODE_ENTER} is released.
      *
@@ -5830,6 +5830,7 @@
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate();
             }
+            mBackgroundSizeChanged = true;
         }
     }
 
@@ -5882,6 +5883,7 @@
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate();
             }
+            mBackgroundSizeChanged = true;
         }
     }
 
@@ -5937,7 +5939,7 @@
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate();
             }
-
+            mBackgroundSizeChanged = true;
         }
     }
 
@@ -5990,6 +5992,7 @@
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate();
             }
+            mBackgroundSizeChanged = true;
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7b3cc6c..58fadb9 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.os.Build;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.util.AttributeSet;
@@ -350,6 +351,10 @@
         mGroupFlags |= FLAG_ANIMATION_CACHE;
         mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
 
+        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
+        }
+
         setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
 
         mChildren = new View[ARRAY_INITIAL_CAPACITY];
@@ -1568,7 +1573,8 @@
 
     /**
      * Enable or disable the splitting of MotionEvents to multiple children during touch event
-     * dispatch. This behavior is disabled by default.
+     * dispatch. This behavior is enabled by default for applications that target an
+     * SDK version of {@link Build.VERSION_CODES#HONEYCOMB} or newer.
      *
      * <p>When this option is enabled MotionEvents may be split and dispatched to different child
      * views depending on where each pointer initially went down. This allows for user interactions
@@ -1591,6 +1597,7 @@
     }
 
     /**
+     * Returns true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
      * @return true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
      */
     public boolean isMotionEventSplittingEnabled() {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index d31acec..ab98763 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1475,8 +1475,16 @@
             ss.position = getSelectedItemPosition();
             ss.firstId = INVALID_POSITION;
         } else {
-            if (haveChildren) {
-                // Remember the position of the first child
+            if (haveChildren && mFirstPosition > 0) {
+                // Remember the position of the first child.
+                // We only do this if we are not currently at the top of
+                // the list, for two reasons:
+                // (1) The list may be in the process of becoming empty, in
+                // which case mItemCount may not be 0, but if we try to
+                // ask for any information about position 0 we will crash.
+                // (2) Being "at the top" seems like a special case, anyway,
+                // and the user wouldn't expect to end up somewhere else when
+                // they revisit the list even if its content has changed.
                 View v = getChildAt(0);
                 ss.viewTop = v.getTop();
                 int firstPos = mFirstPosition;
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
index 289f8cf..d808bcf 100644
--- a/core/res/assets/webkit/youtube.html
+++ b/core/res/assets/webkit/youtube.html
@@ -38,19 +38,29 @@
                 // All images are loaded, so display them.
                 // (Note that the images are loaded from javascript, so might load
                 // after document.onload fires)
-                ctx.drawImage(background, 0, 0, width, height);
+
                 playWidth = play.width;
                 playHeight = play.height;
                 logoWidth = logo.width;
                 logoHeight = logo.height;
                 var ratio = 1;
                 // If the page is smaller than it 'should' be in either dimension
-                // we scale the play button and logo according to the dimension that
-                // has been shrunk the most.
+                // we scale the background, play button and logo according to the
+                // dimension that has been shrunk the most.
                 if (width / height > defWidth / defHeight && height < defHeight) {
                     ratio = height / defHeight;
+                    // Stretch the background in this dimension only.
+                    backgroundHeight = background.height / ratio;
+                    ctx.drawImage(background, 0, 0, background.width, background.height,
+                        0, (height - backgroundHeight) / 2, width, backgroundHeight);
                 } else if (width / height < defWidth / defHeight && width < defWidth) {
                     ratio = width / defWidth;
+                    backgroundWidth = background.width / ratio;
+                    ctx.drawImage(background, 0, 0, background.width, background.height,
+                        (width - backgroundWidth) / 2, 0, backgroundWidth, height);
+                } else {
+                    // In this case stretch the background in both dimensions to fill the space.
+                    ctx.drawImage(background, 0, 0, width, height);
                 }
                 playWidth *= ratio;
                 playHeight *= ratio;
diff --git a/core/res/res/layout-xlarge/keyguard_screen_status_land.xml b/core/res/res/layout-xlarge/keyguard_screen_status_land.xml
index 7a20f9a..f91fe4f 100644
--- a/core/res/res/layout-xlarge/keyguard_screen_status_land.xml
+++ b/core/res/res/layout-xlarge/keyguard_screen_status_land.xml
@@ -60,7 +60,7 @@
             android:ellipsize="none"
             android:textSize="120sp"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="#999999"
+            android:textColor="@color/lockscreen_clock_background"
             android:layout_marginBottom="6dip"
             />
 
@@ -71,7 +71,7 @@
             android:ellipsize="none"
             android:textSize="120sp"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="#666666"
+            android:textColor="@color/lockscreen_clock_foreground"
             android:layout_alignLeft="@id/timeDisplayBackground"
             android:layout_alignTop="@id/timeDisplayBackground"
             android:layout_marginBottom="6dip"
@@ -87,6 +87,7 @@
             android:textSize="30sp"
             android:layout_marginLeft="8dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/lockscreen_clock_am_pm"
             />
 
     </com.android.internal.widget.DigitalClock>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_status_port.xml b/core/res/res/layout-xlarge/keyguard_screen_status_port.xml
index 4e87b21..c529e0b 100644
--- a/core/res/res/layout-xlarge/keyguard_screen_status_port.xml
+++ b/core/res/res/layout-xlarge/keyguard_screen_status_port.xml
@@ -59,7 +59,7 @@
             android:ellipsize="none"
             android:textSize="120sp"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="#999999"
+            android:textColor="@color/lockscreen_clock_background"
             android:layout_marginBottom="6dip"
             />
 
@@ -70,7 +70,7 @@
             android:ellipsize="none"
             android:textSize="120sp"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="#666666"
+            android:textColor="@color/lockscreen_clock_foreground"
             android:layout_marginBottom="6dip"
             android:layout_alignLeft="@id/timeDisplayBackground"
             android:layout_alignTop="@id/timeDisplayBackground"
@@ -86,6 +86,7 @@
             android:textSize="30sp"
             android:layout_marginLeft="8dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/lockscreen_clock_am_pm"
             />
 
     </com.android.internal.widget.DigitalClock>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 03bd45b..77ae0d3 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -77,7 +77,7 @@
             android:textSize="72sp"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:layout_marginBottom="6dip"
-            android:textColor="#999999"
+            android:textColor="@color/lockscreen_clock_background"
             />
 
         <TextView android:id="@+id/timeDisplayForeground"
@@ -88,7 +88,7 @@
             android:textSize="72sp"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:layout_marginBottom="6dip"
-            android:textColor="#666666"
+            android:textColor="@color/lockscreen_clock_foreground"
             android:layout_alignLeft="@id/timeDisplayBackground"
             android:layout_alignTop="@id/timeDisplayBackground"
             />
@@ -103,6 +103,7 @@
             android:textSize="22sp"
             android:layout_marginLeft="8dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/lockscreen_clock_am_pm"
             />
 
     </com.android.internal.widget.DigitalClock>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index bb0c012..e48df20 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -78,7 +78,7 @@
                 android:textSize="72sp"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:layout_marginBottom="6dip"
-                android:textColor="#999999"
+                android:textColor="@color/lockscreen_clock_background"
                 />
 
             <TextView android:id="@+id/timeDisplayForeground"
@@ -89,7 +89,7 @@
                 android:textSize="72sp"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:layout_marginBottom="6dip"
-                android:textColor="#666666"
+                android:textColor="@color/lockscreen_clock_foreground"
                 android:layout_alignLeft="@id/timeDisplayBackground"
                 android:layout_alignTop="@id/timeDisplayBackground"
                 />
@@ -104,6 +104,7 @@
                 android:textSize="22sp"
                 android:layout_marginLeft="8dip"
                 android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/lockscreen_clock_am_pm"
                 />
 
         </com.android.internal.widget.DigitalClock>
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index a6face9..c14afbf 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -78,7 +78,7 @@
                 android:textSize="72sp"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:layout_marginBottom="6dip"
-                android:textColor="#999999"
+                android:textColor="@color/lockscreen_clock_background"
                 />
 
             <TextView android:id="@+id/timeDisplayForeground"
@@ -91,7 +91,7 @@
                 android:layout_marginBottom="6dip"
                 android:layout_alignLeft="@id/timeDisplayBackground"
                 android:layout_alignTop="@id/timeDisplayBackground"
-                android:textColor="#666666"
+                android:textColor="@color/lockscreen_clock_foreground"
                 />
 
 
@@ -106,6 +106,7 @@
                 android:textSize="22sp"
                 android:layout_marginLeft="8dip"
                 android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/lockscreen_clock_am_pm"
                 />
 
         </com.android.internal.widget.DigitalClock>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 42144ab..85e1db1 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -68,7 +68,7 @@
                 android:textSize="56sp"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:layout_marginBottom="6dip"
-                android:textColor="#999999"
+                android:textColor="@color/lockscreen_clock_background"
                 />
 
             <TextView android:id="@+id/timeDisplayForeground"
@@ -79,7 +79,7 @@
                 android:textSize="56sp"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:layout_marginBottom="6dip"
-                android:textColor="#666666"
+                android:textColor="@color/lockscreen_clock_foreground"
                 />
 
             <TextView android:id="@+id/am_pm"
@@ -92,6 +92,7 @@
                 android:textSize="18sp"
                 android:layout_marginLeft="4dip"
                 android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/lockscreen_clock_am_pm"
                 />
 
         </com.android.internal.widget.DigitalClock>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index b1c54ff..a286265 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -103,6 +103,11 @@
     <color name="keyguard_text_color_soundon">#e69310</color>
     <color name="keyguard_text_color_decline">#fe0a5a</color>
 
+    <!-- keyguard clock -->
+    <color name="lockscreen_clock_background">#ff9a9a9a</color>
+    <color name="lockscreen_clock_foreground">#ff666666</color>
+    <color name="lockscreen_clock_am_pm">#ff9a9a9a</color>
+
     <!-- For holo theme -->
 	  <drawable name="screen_background_holo_light">#fff3f3f3</drawable>
 	  <drawable name="screen_background_holo_dark">#ff000000</drawable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d840c2..8edea0d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -214,6 +214,14 @@
          rotations as the default behavior. -->
     <bool name="config_allowAllRotations">true</bool>
 
+    <!-- If true, the direction rotation is applied to get to an application's requested
+         orientation is reversed.  Normally, the model is that landscape is
+         clockwise from portrait; thus on a portrait device an app requesting
+         landscape will cause a clockwise rotation, and on a landscape device an
+         app requesting portrait will cause a counter-clockwise rotation.  Setting
+         true here reverses that logic. -->
+    <bool name="config_reverseDefaultRotation">false</bool>
+
     <!-- The number of degrees to rotate the display when the keyboard is open. -->
     <integer name="config_lidOpenRotation">90</integer>
 
diff --git a/data/fonts/AndroidClock.ttf b/data/fonts/AndroidClock.ttf
index 3945183..24a7f08 100644
--- a/data/fonts/AndroidClock.ttf
+++ b/data/fonts/AndroidClock.ttf
Binary files differ
diff --git a/data/fonts/AndroidClock_Highlight.ttf b/data/fonts/AndroidClock_Highlight.ttf
index fa0221e..1e14a8f 100644
--- a/data/fonts/AndroidClock_Highlight.ttf
+++ b/data/fonts/AndroidClock_Highlight.ttf
Binary files differ
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index 5197eeb..64eb7d2 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -37,13 +37,16 @@
 
 
 ScriptC::ScriptC(Context *rsc) : Script(rsc) {
+    LOGD(">>>> ScriptC ctor called, obj=%x", this);
     mBccScript = NULL;
     memset(&mProgram, 0, sizeof(mProgram));
 }
 
 ScriptC::~ScriptC() {
+    LOGD(">>>> ~ScriptC()");
     if (mBccScript) {
         bccDeleteScript(mBccScript);
+        LOGD(">>>> ~ScriptC(mBCCScript)");
     }
     free(mEnviroment.mScriptText);
     mEnviroment.mScriptText = NULL;
@@ -436,7 +439,7 @@
                       s->mEnviroment.mScriptTextLength,
                       modWhen,
                       crc32,
-                      NULL,
+                      resName,
                       cacheDir);
             bccCompileBC(s->mBccScript);
           }
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 39c4a28..db308c7 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -188,11 +188,12 @@
 
         /** The following formats are audio only .aac or .amr formats **/
         /** @deprecated  Deprecated in favor of AMR_NB */
-        /** TODO: change link when AMR_NB is exposed. Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB */
+        /** Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB */
+        /** AMR NB file format */
         public static final int RAW_AMR = 3;
-        /** @hide AMR NB file format */
+        /** AMR NB file format */
         public static final int AMR_NB = 3;
-        /** @hide AMR WB file format */
+        /** AMR WB file format */
         public static final int AMR_WB = 4;
         /** @hide AAC ADIF file format */
         public static final int AAC_ADIF = 5;
@@ -218,9 +219,9 @@
         public static final int DEFAULT = 0;
         /** AMR (Narrowband) audio codec */
         public static final int AMR_NB = 1;
-        /** @hide AMR (Wideband) audio codec */
+        /** AMR (Wideband) audio codec */
         public static final int AMR_WB = 2;
-        /** @hide AAC audio codec */
+        /** AAC audio codec */
         public static final int AAC = 3;
         /** @hide enhanced AAC audio codec */
         public static final int AAC_PLUS = 4;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 9662817..4ccdd9a 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -22,6 +22,20 @@
 #include <utils/Log.h>
 #include <utils/threads.h>
 #include <core/SkBitmap.h>
+
+// Please do not enable "USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR"
+// This mode will be removed and it is kept here for
+// convenient comparsion just in case. Will be removed soon.
+// Tests show that this mode is also ~30ms slower,
+// when rotation is involved.
+#define USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR 0
+
+#if (!USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR)
+#include <core/SkCanvas.h>
+#include <core/SkDevice.h>
+#include <core/SkScalar.h>
+#endif
+
 #include <media/mediametadataretriever.h>
 #include <private/media/VideoFrame.h>
 
@@ -35,8 +49,15 @@
 struct fields_t {
     jfieldID context;
     jclass bitmapClazz;
+#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
     jmethodID bitmapConstructor;
+    jmethodID createBitmapRotationMethod;
+#else
+    jfieldID nativeBitmap;
     jmethodID createBitmapMethod;
+    jclass configClazz;
+    jmethodID createConfigMethod;
+#endif
 };
 
 static fields_t fields;
@@ -162,6 +183,12 @@
         return NULL;
     }
 
+    LOGV("Dimension = %dx%d and bytes = %d",
+            videoFrame->mDisplayWidth,
+            videoFrame->mDisplayHeight,
+            videoFrame->mSize);
+
+#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
     jobject matrix = NULL;
     if (videoFrame->mRotationAngle != 0) {
         LOGD("Create a rotation matrix: %d degrees", videoFrame->mRotationAngle);
@@ -217,9 +244,9 @@
     jobject jSrcBitmap = env->NewObject(fields.bitmapClazz,
             fields.bitmapConstructor, (int) bitmap, NULL, true, NULL, -1);
 
-    LOGV("Return a new bitmap constructed with the rotation matrix");
-    return env->CallStaticObjectMethod(
-                fields.bitmapClazz, fields.createBitmapMethod,
+    jobject jBitmap = env->CallStaticObjectMethod(
+                fields.bitmapClazz,
+                fields.createBitmapRotationMethod,
                 jSrcBitmap,                     // source Bitmap
                 0,                              // x
                 0,                              // y
@@ -227,6 +254,39 @@
                 videoFrame->mDisplayHeight,     // height
                 matrix,                         // transform matrix
                 false);                         // filter
+
+#else
+
+    jobject config = env->CallStaticObjectMethod(
+                        fields.configClazz,
+                        fields.createConfigMethod,
+                        SkBitmap::kRGB_565_Config);
+
+    jobject jBitmap = env->CallStaticObjectMethod(
+                            fields.bitmapClazz,
+                            fields.createBitmapMethod,
+                            videoFrame->mDisplayWidth,
+                            videoFrame->mDisplayHeight,
+                            config);
+    SkBitmap *bitmap =
+            (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
+
+    bitmap->lockPixels();
+
+    memcpy((uint8_t*)bitmap->getPixels(),
+            (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
+
+    bitmap->unlockPixels();
+
+    if (videoFrame->mRotationAngle != 0) {
+        SkDevice device(*bitmap);
+        SkCanvas canvas(&device);
+        canvas.rotate((SkScalar) (videoFrame->mRotationAngle * 1.0));
+        canvas.drawBitmap(*bitmap, 0, 0);
+    }
+#endif
+    LOGV("Return a new bitmap constructed with the rotation matrix");
+    return jBitmap;
 }
 
 static jbyteArray android_media_MediaMetadataRetriever_extractAlbumArt(JNIEnv *env, jobject thiz)
@@ -293,7 +353,6 @@
 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
 {
     LOGV("native_finalize");
-    
     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
     android_media_MediaMetadataRetriever_release(env, thiz);
 }
@@ -320,21 +379,52 @@
         jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
         return;
     }
-
+#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
     fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(I[BZ[BI)V");
     if (fields.bitmapConstructor == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
         return;
     }
-    fields.createBitmapMethod =
+    fields.createBitmapRotationMethod =
             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
                     "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
                     "Landroid/graphics/Bitmap;");
-    if (fields.createBitmapMethod == NULL) {
+    if (fields.createBitmapRotationMethod == NULL) {
         jniThrowException(env, "java/lang/RuntimeException",
                 "Can't find Bitmap.createBitmap method");
         return;
     }
+#else
+    fields.createBitmapMethod =
+            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
+                    "(IILandroid/graphics/Bitmap$Config;)"
+                    "Landroid/graphics/Bitmap;");
+    if (fields.createBitmapMethod == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "Can't find Bitmap.createBitmap(int, int, Config)  method");
+        return;
+    }
+    fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
+    if (fields.nativeBitmap == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "Can't find Bitmap.mNativeBitmap field");
+    }
+
+    fields.configClazz = env->FindClass("android/graphics/Bitmap$Config");
+    if (fields.configClazz == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                               "Can't find Bitmap$Config class");
+        return;
+    }
+    fields.createConfigMethod =
+            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
+                    "(I)Landroid/graphics/Bitmap$Config;");
+    if (fields.createConfigMethod == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "Can't find Bitmap$Config.nativeToConfig(int)  method");
+        return;
+    }
+#endif
 }
 
 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index d3dab13..967fa49 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -32,8 +32,6 @@
 #include <media/stagefright/MetaData.h>
 #include <surfaceflinger/Surface.h>
 
-#define SHUTDOWN_ON_DISCONTINUITY       0
-
 namespace android {
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -78,6 +76,26 @@
     (new AMessage(kWhatStart, id()))->post();
 }
 
+// static
+bool NuPlayer::IsFlushingState(FlushStatus state, bool *formatChange) {
+    switch (state) {
+        case FLUSHING_DECODER:
+            if (formatChange != NULL) {
+                *formatChange = false;
+            }
+            return true;
+
+        case FLUSHING_DECODER_FORMATCHANGE:
+            if (formatChange != NULL) {
+                *formatChange = true;
+            }
+            return true;
+
+        default:
+            return false;
+    }
+}
+
 void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatSetDataSource:
@@ -181,28 +199,30 @@
             } else if (what == ACodec::kWhatEOS) {
                 mRenderer->queueEOS(audio, ERROR_END_OF_STREAM);
             } else if (what == ACodec::kWhatFlushCompleted) {
+                bool formatChange;
+
                 if (audio) {
-                    CHECK_EQ((int)mFlushingAudio, (int)FLUSHING_DECODER);
+                    CHECK(IsFlushingState(mFlushingAudio, &formatChange));
                     mFlushingAudio = FLUSHED;
                 } else {
-                    CHECK_EQ((int)mFlushingVideo, (int)FLUSHING_DECODER);
+                    CHECK(IsFlushingState(mFlushingVideo, &formatChange));
                     mFlushingVideo = FLUSHED;
                 }
 
                 LOGI("decoder %s flush completed", audio ? "audio" : "video");
 
-#if SHUTDOWN_ON_DISCONTINUITY
-                LOGI("initiating %s decoder shutdown",
-                     audio ? "audio" : "video");
+                if (formatChange) {
+                    LOGI("initiating %s decoder shutdown",
+                         audio ? "audio" : "video");
 
-                (audio ? mAudioDecoder : mVideoDecoder)->initiateShutdown();
+                    (audio ? mAudioDecoder : mVideoDecoder)->initiateShutdown();
 
-                if (audio) {
-                    mFlushingAudio = SHUTTING_DOWN_DECODER;
-                } else {
-                    mFlushingVideo = SHUTTING_DOWN_DECODER;
+                    if (audio) {
+                        mFlushingAudio = SHUTTING_DOWN_DECODER;
+                    } else {
+                        mFlushingVideo = SHUTTING_DOWN_DECODER;
+                    }
                 }
-#endif
 
                 finishFlushIfPossible();
             } else if (what == ACodec::kWhatOutputFormatChanged) {
@@ -451,8 +471,8 @@
     sp<AMessage> reply;
     CHECK(msg->findMessage("reply", &reply));
 
-    if ((audio && mFlushingAudio == FLUSHING_DECODER)
-            || (!audio && mFlushingVideo == FLUSHING_DECODER)) {
+    if ((audio && IsFlushingState(mFlushingAudio))
+            || (!audio && IsFlushingState(mFlushingVideo))) {
         reply->setInt32("err", INFO_DISCONTINUITY);
         reply->post();
         return OK;
@@ -467,14 +487,25 @@
         return err;
     } else if (err != OK) {
         if (err == INFO_DISCONTINUITY) {
-            LOGI("%s discontinuity", audio ? "audio" : "video");
+            int32_t formatChange;
+            if (!accessUnit->meta()->findInt32(
+                        "format-change", &formatChange)) {
+                formatChange = 0;
+            }
+
+            LOGI("%s discontinuity (formatChange=%d)",
+                 audio ? "audio" : "video", formatChange);
+
             (audio ? mAudioDecoder : mVideoDecoder)->signalFlush();
             mRenderer->flush(audio);
 
             if (audio) {
                 CHECK(mFlushingAudio == NONE
                         || mFlushingAudio == AWAITING_DISCONTINUITY);
-                mFlushingAudio = FLUSHING_DECODER;
+
+                mFlushingAudio = formatChange
+                    ? FLUSHING_DECODER_FORMATCHANGE : FLUSHING_DECODER;
+
                 if (mFlushingVideo == NONE) {
                     mFlushingVideo = (mVideoDecoder != NULL)
                         ? AWAITING_DISCONTINUITY
@@ -483,7 +514,10 @@
             } else {
                 CHECK(mFlushingVideo == NONE
                         || mFlushingVideo == AWAITING_DISCONTINUITY);
-                mFlushingVideo = FLUSHING_DECODER;
+
+                mFlushingVideo = formatChange
+                    ? FLUSHING_DECODER_FORMATCHANGE : FLUSHING_DECODER;
+
                 if (mFlushingAudio == NONE) {
                     mFlushingAudio = (mAudioDecoder != NULL)
                         ? AWAITING_DISCONTINUITY
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index fad1ce1..d4e7428 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -79,6 +79,7 @@
         NONE,
         AWAITING_DISCONTINUITY,
         FLUSHING_DECODER,
+        FLUSHING_DECODER_FORMATCHANGE,
         SHUTTING_DOWN_DECODER,
         FLUSHED,
         SHUT_DOWN,
@@ -104,6 +105,8 @@
 
     void finishFlushIfPossible();
 
+    static bool IsFlushingState(FlushStatus state, bool *formatChange = NULL);
+
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
 };
 
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index afacb2e..ee9b5739 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -334,7 +334,7 @@
 
             if (mStreamType == 0x1b && mSource != NULL) {
                 // Don't signal discontinuities on audio streams.
-                mSource->queueDiscontinuity();
+                mSource->queueDiscontinuity(true /* formatChange */);
             }
             break;
         }
@@ -348,7 +348,7 @@
 
             if (mSource != NULL) {
                 mSource->clear();
-                mSource->queueDiscontinuity();
+                mSource->queueDiscontinuity(!isASeek);
             }
             break;
         }
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 7a1d5b0..a8fe2c1 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -63,8 +63,6 @@
         int32_t discontinuity;
         if ((*buffer)->meta()->findInt32("discontinuity", &discontinuity)
                 && discontinuity) {
-            buffer->clear();
-
             return INFO_DISCONTINUITY;
         }
 
@@ -125,10 +123,14 @@
     mCondition.signal();
 }
 
-void AnotherPacketSource::queueDiscontinuity() {
+void AnotherPacketSource::queueDiscontinuity(bool formatChange) {
     sp<ABuffer> buffer = new ABuffer(0);
     buffer->meta()->setInt32("discontinuity", true);
 
+    if (formatChange) {
+        buffer->meta()->setInt32("format-change", true);
+    }
+
     Mutex::Autolock autoLock(mLock);
 
     mBuffers.push_back(buffer);
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index 2bc7404..f25a067 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -42,7 +42,7 @@
     status_t nextBufferTime(int64_t *timeUs);
 
     void queueAccessUnit(const sp<ABuffer> &buffer);
-    void queueDiscontinuity();
+    void queueDiscontinuity(bool formatChange);
     void signalEOS(status_t result);
 
     void clear();
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml b/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml
index 2989be0..3fdfdbb 100644
--- a/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml
@@ -31,7 +31,6 @@
         android:layout_alignParentTop="true"
         android:layout_marginLeft="105dip"
         android:scaleType="center"
-        android:background="@drawable/recents_thumbnail_bg"
     />
 
     <ImageView android:id="@+id/app_icon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
index edcf096..bd9bdb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
@@ -23,8 +23,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.app.ActivityManager;
-import android.app.IThumbnailReceiver;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -33,20 +31,20 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Shader.TileMode;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -55,12 +53,12 @@
 import com.android.systemui.R;
 
 public class RecentAppsPanel extends LinearLayout implements StatusBarPanel, OnClickListener {
-    private static final int COLLAPSE_DURATION = 360;
+    private static final int GLOW_PADDING = 15;
     private static final String TAG = "RecentAppsPanel";
     private static final boolean DEBUG = TabletStatusBar.DEBUG;
-    private static final int DISPLAY_TASKS_PORTRAIT = 8;
+    private static final int DISPLAY_TASKS_PORTRAIT = 7; // Limited by max binder transaction size
     private static final int DISPLAY_TASKS_LANDSCAPE = 5; // number of recent tasks to display
-    private static final int MAX_TASKS = DISPLAY_TASKS_PORTRAIT + 2; // allow extra for non-apps
+    private static final int MAX_TASKS = DISPLAY_TASKS_PORTRAIT + 1; // allow extra for non-apps
     private static final int STAGGER_ANIMATION_DELAY = 30;
     private static final long ALPHA_ANIMATION_DURATION = 120;
     private TabletStatusBar mBar;
@@ -70,6 +68,7 @@
     private int mIconDpi;
     private AnimatorSet mAnimationSet;
     private View mBackgroundProtector;
+    private Bitmap mGlowBitmap;
 
     static class ActivityDescription {
         int id;
@@ -121,6 +120,7 @@
                 & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
 
         mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
+        mGlowBitmap = BitmapFactory.decodeResource(res, R.drawable.recents_thumbnail_bg);
     }
 
     @Override
@@ -225,8 +225,8 @@
                 if (title != null && title.length() > 0 && icon != null) {
                     if (DEBUG) Log.v(TAG, "creating activity desc for id=" + id + ", label=" + title);
                     ActivityDescription item = new ActivityDescription(
-                            crop(recentInfo.thumbnail), icon, title, recentInfo.description,
-                            intent, id, index, info.packageName);
+                            recentInfo.thumbnail, icon, title,
+                            recentInfo.description, intent, id, index, info.packageName);
                     activityDescriptions.add(item);
                     ++index;
                 } else {
@@ -255,21 +255,22 @@
         updateUiElements(getResources().getConfiguration(), true);
     }
 
-    private Bitmap crop(Bitmap bitmap) {
-        if (bitmap == null || bitmap.getWidth() >= bitmap.getHeight()) {
-            return bitmap;
-        }
-        final int width = bitmap.getWidth();
-        final int height = bitmap.getHeight();
-        Bitmap outBitmap = Bitmap.createBitmap(height, width, bitmap.getConfig());
+    private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) {
+        Bitmap outBitmap = background.copy(background.getConfig(), true);
         Canvas canvas = new Canvas(outBitmap);
         Paint paint = new Paint();
         paint.setAntiAlias(true);
         paint.setFilterBitmap(true);
-        canvas.drawBitmap(bitmap,
-                new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight() - height * width / height),
-                new Rect(0, 0, outBitmap.getWidth(), outBitmap.getHeight()),
-                paint);
+        paint.setAlpha(255);
+        final int srcWidth = thumbnail.getWidth();
+        final int height = thumbnail.getHeight();
+        final int srcHeight = srcWidth > height ? height : (height - height * srcWidth / height);
+        canvas.drawBitmap(thumbnail,
+                new Rect(0, 0, srcWidth-1, srcHeight-1),
+                new RectF(GLOW_PADDING,
+                        GLOW_PADDING - 4.0f,
+                        outBitmap.getWidth() - GLOW_PADDING + 2.0f,
+                        outBitmap.getHeight() - GLOW_PADDING + 3.0f), paint);
         return outBitmap;
     }
 
@@ -291,7 +292,7 @@
             TextView appLabel = (TextView) view.findViewById(R.id.app_label);
             TextView appDesc = (TextView) view.findViewById(R.id.app_description);
             final Bitmap thumb = activityDescription.thumbnail;
-            appThumbnail.setImageBitmap(crop(thumb));
+            appThumbnail.setImageBitmap(compositeBitmap(mGlowBitmap, thumb));
             appIcon.setImageDrawable(activityDescription.icon);
             appLabel.setText(activityDescription.label);
             appDesc.setText(activityDescription.description);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c6984a4..2e89a21 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2360,15 +2360,27 @@
             Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                     .getDefaultDisplay();
             if (d.getWidth() > d.getHeight()) {
-                mPortraitRotation = Surface.ROTATION_90;
                 mLandscapeRotation = Surface.ROTATION_0;
-                mUpsideDownRotation = Surface.ROTATION_270;
                 mSeascapeRotation = Surface.ROTATION_180;
+                if (mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_reverseDefaultRotation)) {
+                    mPortraitRotation = Surface.ROTATION_90;
+                    mUpsideDownRotation = Surface.ROTATION_270;
+                } else {
+                    mPortraitRotation = Surface.ROTATION_270;
+                    mUpsideDownRotation = Surface.ROTATION_90;
+                }
             } else {
                 mPortraitRotation = Surface.ROTATION_0;
-                mLandscapeRotation = Surface.ROTATION_90;
                 mUpsideDownRotation = Surface.ROTATION_180;
-                mSeascapeRotation = Surface.ROTATION_270;
+                if (mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_reverseDefaultRotation)) {
+                    mLandscapeRotation = Surface.ROTATION_270;
+                    mSeascapeRotation = Surface.ROTATION_90;
+                } else {
+                    mLandscapeRotation = Surface.ROTATION_90;
+                    mSeascapeRotation = Surface.ROTATION_270;
+                }
             }
         }
 
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 30ea48c..bec35d1 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -115,7 +115,7 @@
 
     public ClipDescription getPrimaryClipDescription() {
         synchronized (this) {
-            return mPrimaryClip.getDescription();
+            return mPrimaryClip != null ? mPrimaryClip.getDescription() : null;
         }
     }
 
@@ -211,7 +211,7 @@
         } catch (NameNotFoundException e) {
             throw new IllegalArgumentException("Unknown package " + pkg, e);
         }
-        if (!mActivePermissionOwners.contains(pkg)) {
+        if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) {
             final int N = mPrimaryClip.getItemCount();
             for (int i=0; i<N; i++) {
                 grantItemLocked(mPrimaryClip.getItem(i), pkg);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index f82a243..e7e4302 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,8 +16,7 @@
 
 package com.android.server;
 
-import android.app.Notification;
-import android.app.NotificationManager;
+import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,9 +25,9 @@
 import android.net.ConnectivityManager;
 import android.net.DummyDataStateTracker;
 import android.net.IConnectivityManager;
+import android.net.LinkProperties;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkInfo;
-import android.net.LinkProperties;
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.net.Proxy;
@@ -50,7 +49,6 @@
 import android.util.Slog;
 
 import com.android.internal.telephony.Phone;
-
 import com.android.server.connectivity.Tethering;
 
 import java.io.FileDescriptor;
@@ -58,7 +56,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -412,6 +409,10 @@
                         mNetAttributes[netType].mName);
                 mNetTrackers[netType].startMonitoring(context, mHandler);
                 break;
+            case ConnectivityManager.TYPE_BLUETOOTH:
+                mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance();
+                mNetTrackers[netType].startMonitoring(context, mHandler);
+                break;
             default:
                 loge("Trying to create a DataStateTracker for an unknown radio type " +
                         mNetAttributes[netType].mRadio);
@@ -1097,6 +1098,7 @@
                     // gotten a positive report we don't want to overwrite, but if not we need to
                     // clear this now to turn our cellular sig strength white
                     mDefaultInetConditionPublished = 0;
+                    intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
                 }
                 intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
             } else {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 748f2e9..94ed813 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -6814,6 +6814,11 @@
         // Input channel
         InputChannel mInputChannel;
         
+        // Used to improve performance of toString()
+        String mStringNameCache;
+        CharSequence mLastTitle;
+        boolean mWasPaused;
+
         WindowState(Session s, IWindow c, WindowToken token,
                WindowState attachedWindow, WindowManager.LayoutParams a,
                int viewVisibility) {
@@ -8131,9 +8136,14 @@
 
         @Override
         public String toString() {
-            return "Window{"
-                + Integer.toHexString(System.identityHashCode(this))
-                + " " + mAttrs.getTitle() + " paused=" + mToken.paused + "}";
+            if (mStringNameCache == null || mLastTitle != mAttrs.getTitle()
+                    || mWasPaused != mToken.paused) {
+                mLastTitle = mAttrs.getTitle();
+                mWasPaused = mToken.paused;
+                mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
+                        + " " + mLastTitle + " paused=" + mWasPaused + "}";
+            }
+            return mStringNameCache;
         }
     }
 
diff --git a/tests/HwAccelerationTest/res/layout/_layers.xml b/tests/HwAccelerationTest/res/layout/_layers.xml
new file mode 100644
index 0000000..c2b186d
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/_layers.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.test.hwui.LayersActivity.LayersView
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="fill_parent"
+  android:layout_height="fill_parent">
+</com.android.test.hwui.LayersActivity.LayersView>
diff --git a/tests/HwAccelerationTest/res/layout/_newlayers.xml b/tests/HwAccelerationTest/res/layout/_newlayers.xml
new file mode 100644
index 0000000..062a2e1
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/_newlayers.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.test.hwui.NewLayersActivity.LayersView
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="fill_parent"
+  android:layout_height="fill_parent">
+</com.android.test.hwui.NewLayersActivity.LayersView>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java
index b705117..9d5cd28 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java
@@ -33,11 +33,11 @@
         setContentView(new LayersView(this));
     }
 
-    static class LayersView extends View {
+    public static class LayersView extends View {
         private Paint mLayerPaint;
         private final Paint mRectPaint;
 
-        LayersView(Context c) {
+        public LayersView(Context c) {
             super(c);
 
             mLayerPaint = new Paint();
@@ -47,11 +47,11 @@
         @Override
         protected void onDraw(Canvas canvas) {
             super.onDraw(canvas);
-            
+
             canvas.translate(140.0f, 100.0f);
 
             //canvas.drawRGB(255, 255, 255);
-            
+
             int count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
                     Canvas.ALL_SAVE_FLAG);
 
@@ -59,9 +59,9 @@
             canvas.drawRect(0.0f, 0.0f, 200.0f, 100.0f, mRectPaint);
 
             canvas.restoreToCount(count);
-            
+
             canvas.translate(0.0f, 125.0f);
-            
+
             count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
                     Canvas.ALL_SAVE_FLAG);
 
@@ -75,8 +75,8 @@
 
             mRectPaint.setColor(0xff0000ff);
             mRectPaint.setAlpha(255);
-            canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);            
-            
+            canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);
+
             mLayerPaint.setAlpha(127);
             mLayerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
             count = canvas.saveLayer(50.0f, 25.0f, 150.0f, 75.0f, mLayerPaint,
@@ -86,13 +86,13 @@
             canvas.drawRect(50.0f, 25.0f, 150.0f, 75.0f, mRectPaint);
 
             canvas.restoreToCount(count);
-            
+
             canvas.translate(0.0f, 125.0f);
 
             mRectPaint.setColor(0xff0000ff);
             mRectPaint.setAlpha(255);
-            canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);            
-            
+            canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);
+
             mLayerPaint.setColor(0xffff0000);
             mLayerPaint.setAlpha(127);
             mLayerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
@@ -103,7 +103,7 @@
             canvas.drawRect(50.0f, 25.0f, 150.0f, 75.0f, mRectPaint);
 
             canvas.restoreToCount(count);
-            
+
             mLayerPaint = new Paint();
         }
     }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
index d9a2893..2509d367 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
@@ -31,11 +31,11 @@
         setContentView(new LayersView(this));
     }
 
-    static class LayersView extends View {
+    public static class LayersView extends View {
         private Paint mLayerPaint;
         private final Paint mRectPaint;
 
-        LayersView(Context c) {
+        public LayersView(Context c) {
             super(c);
 
             mLayerPaint = new Paint();
@@ -57,7 +57,7 @@
 
             canvas.translate(0.0f, 200.0f);
             drawStuff(canvas, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
-            
+
             canvas.restore();
         }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index b5f5005..2ce6a36 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import com.android.ide.common.rendering.api.ResourceDensity;
+import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 
 import android.graphics.Bitmap.Config;
@@ -29,6 +30,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.Buffer;
+import java.util.Arrays;
 
 import javax.imageio.ImageIO;
 
@@ -57,6 +59,7 @@
     private final Config mConfig;
     private BufferedImage mImage;
     private boolean mHasAlpha = true;
+    private int mGenerationId = 0;
 
 
     // ---- Public Helper methods ----
@@ -173,6 +176,22 @@
         return mConfig;
     }
 
+    /**
+     * Returns the hasAlpha rendering hint
+     * @return true if the bitmap alpha should be used at render time
+     */
+    public boolean hasAlpha() {
+        return mHasAlpha && mConfig != Config.RGB_565;
+    }
+
+    /**
+     * Update the generationId.
+     *
+     * @see Bitmap#getGenerationId()
+     */
+    public void change() {
+        mGenerationId++;
+    }
 
     // ---- native methods ----
 
@@ -183,7 +202,9 @@
         // create the image
         BufferedImage image = new BufferedImage(width, height, imageType);
 
-        // FIXME fill the bitmap!
+        if (colors != null) {
+            image.setRGB(0, 0, width, height, colors, offset, stride);
+        }
 
         // create a delegate with the content of the stream.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
@@ -192,8 +213,31 @@
     }
 
     /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap");
+        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
+        if (srcBmpDelegate == null) {
+            assert false;
+            return null;
+        }
+
+        BufferedImage srcImage = srcBmpDelegate.getImage();
+
+        int width = srcImage.getWidth();
+        int height = srcImage.getHeight();
+
+        int imageType = getBufferedImageType(nativeConfig);
+
+        // create the image
+        BufferedImage image = new BufferedImage(width, height, imageType);
+
+        // copy the source image into the image.
+        int[] argb = new int[width * height];
+        srcImage.getRGB(0, 0, width, height, argb, 0, width);
+        image.setRGB(0, 0, width, height, argb, 0, width);
+
+        // create a delegate with the content of the stream.
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
+
+        return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity());
     }
 
     /*package*/ static void nativeDestructor(int nativeBitmap) {
@@ -206,8 +250,8 @@
 
     /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
             OutputStream stream, byte[] tempStorage) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap");
+        Bridge.getLog().error(null, "Bitmap.compress() is not supported");
+        return true;
     }
 
     /*package*/ static void nativeErase(int nativeBitmap, int color) {
@@ -222,7 +266,7 @@
 
         Graphics2D g = image.createGraphics();
         try {
-            g.setColor(new java.awt.Color(color, delegate.mHasAlpha));
+            g.setColor(new java.awt.Color(color, true));
 
             g.fillRect(0, 0, image.getWidth(), image.getHeight());
         } finally {
@@ -298,20 +342,35 @@
 
     /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
             int stride, int x, int y, int width, int height) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeGetPixels");
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            assert false;
+            return;
+        }
+
+        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
     }
 
 
     /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeSetPixel");
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            assert false;
+            return;
+        }
+
+        delegate.getImage().setRGB(x, y, color);
     }
 
     /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
             int stride, int x, int y, int width, int height) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeSetPixels");
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            assert false;
+            return;
+        }
+
+        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
     }
 
     /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) {
@@ -325,31 +384,68 @@
     }
 
     /*package*/ static int nativeGenerationId(int nativeBitmap) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeGenerationId");
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return delegate.mGenerationId;
     }
 
     /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeCreateFromParcel");
+        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
+        // used during aidl call so really this should not be called.
+        Bridge.getLog().error(null,
+                "AIDL is not suppored, and therefore bitmap cannot be created from parcels");
+        return null;
     }
 
     /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
             int density, Parcel p) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeWriteToParcel");
+        // This is only called when sending a bitmap through aidl, so really this should not
+        // be called.
+        Bridge.getLog().error(null,
+                "AIDL is not suppored, and therefore bitmap cannot be written to parcels");
+        return false;
     }
 
     /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
             int[] offsetXY) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeExtractAlpha");
+        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
+        if (bitmap == null) {
+            assert false;
+            return null;
+        }
+
+        Paint_Delegate paint = null;
+        if (nativePaint > 0) {
+            paint = Paint_Delegate.getDelegate(nativePaint);
+            if (paint == null) {
+                assert false;
+                return null;
+            }
+        }
+
+        if (paint != null && paint.getMaskFilter() != 0) {
+            Bridge.getLog().fidelityWarning(null,
+                    "MaskFilter not supported in Bitmap.extractAlpha",
+                    null);
+        }
+
+        int alpha = paint != null ? paint.getAlpha() : 0xFF;
+        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
+
+        // create the delegate. The actual Bitmap config is only an alpha channel
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
+
+        // the density doesn't matter, it's set by the Java method.
+        return createBitmap(delegate, false /*isMutable*/,
+                ResourceDensity.DEFAULT_DENSITY /*density*/);
     }
 
-
     /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativePrepareToDraw");
+        // nothing to be done here.
     }
 
     /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
@@ -364,8 +460,48 @@
     }
 
     /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeSameAs");
+        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
+        if (delegate1 == null) {
+            assert false;
+            return false;
+        }
+
+        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
+        if (delegate2 == null) {
+            assert false;
+            return false;
+        }
+
+        BufferedImage image1 = delegate1.getImage();
+        BufferedImage image2 = delegate2.getImage();
+        if (delegate1.mConfig != delegate2.mConfig ||
+                image1.getWidth() != image2.getWidth() ||
+                image1.getHeight() != image2.getHeight()) {
+            return false;
+        }
+
+        // get the internal data
+        int w = image1.getWidth();
+        int h = image2.getHeight();
+        int[] argb1 = new int[w*h];
+        int[] argb2 = new int[w*h];
+
+        image1.getRGB(0, 0, w, h, argb1, 0, w);
+        image2.getRGB(0, 0, w, h, argb2, 0, w);
+
+        // compares
+        if (delegate1.mConfig == Config.ALPHA_8) {
+            // in this case we have to manually compare the alpha channel as the rest is garbage.
+            final int length = w*h;
+            for (int i = 0 ; i < length ; i++) {
+                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        return Arrays.equals(argb1, argb2);
     }
 
     // ---- Private delegate/helper methods ----
@@ -382,4 +518,37 @@
         // and create/return a new Bitmap with it
         return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density);
     }
+
+    /**
+     * Creates and returns a copy of a given BufferedImage.
+     * <p/>
+     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
+     *
+     * @param image the image to copy
+     * @param imageType the type of the new image
+     * @param alpha an optional alpha modifier
+     * @return a new BufferedImage
+     */
+    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
+        int w = image.getWidth();
+        int h = image.getHeight();
+
+        BufferedImage result = new BufferedImage(w, h, imageType);
+
+        int[] argb = new int[w * h];
+        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+        if (alpha != 255) {
+            final int length = argb.length;
+            for (int i = 0 ; i < length; i++) {
+                int a = (argb[i] >>> 24 * alpha) / 255;
+                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
+            }
+        }
+
+        result.setRGB(0, 0, w, h, argb, 0, w);
+
+        return result;
+    }
+
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 2720b61..88ec88eb 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -23,14 +23,12 @@
 import android.graphics.Paint_Delegate.FontInfo;
 import android.text.TextUtils;
 
-import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Composite;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Shape;
-import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
 import java.util.List;
@@ -57,13 +55,11 @@
 
     // ---- delegate helper data ----
 
-    private interface Drawable {
-        void draw(Graphics2D graphics, Paint_Delegate paint);
-    }
+    private final static boolean[] sBoolOut = new boolean[1];
 
     // ---- delegate data ----
-    private BufferedImage mBufferedImage;
-    private GcSnapshot mSnapshot = new GcSnapshot();
+    private Bitmap_Delegate mBitmap;
+    private GcSnapshot mSnapshot;
 
     // ---- Public Helper methods ----
 
@@ -84,7 +80,7 @@
     /**
      * Returns the current {@link Graphics2D} used to draw.
      */
-    public GcSnapshot getGcSnapshot() {
+    public GcSnapshot getSnapshot() {
         return mSnapshot;
     }
 
@@ -103,7 +99,7 @@
             return 0;
         }
 
-        return canvasDelegate.mBufferedImage.getWidth();
+        return canvasDelegate.mBitmap.getImage().getWidth();
     }
 
     /*package*/ static int getHeight(Canvas thisCanvas) {
@@ -114,7 +110,7 @@
             return 0;
         }
 
-        return canvasDelegate.mBufferedImage.getHeight();
+        return canvasDelegate.mBitmap.getImage().getHeight();
     }
 
     /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) {
@@ -125,7 +121,7 @@
             return;
         }
 
-        canvasDelegate.getGcSnapshot().translate(dx, dy);
+        canvasDelegate.getSnapshot().translate(dx, dy);
     }
 
     /*package*/ static void rotate(Canvas thisCanvas, float degrees) {
@@ -136,7 +132,7 @@
             return;
         }
 
-        canvasDelegate.getGcSnapshot().rotate(Math.toRadians(degrees));
+        canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
     }
 
     /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) {
@@ -147,7 +143,7 @@
             return;
         }
 
-        canvasDelegate.getGcSnapshot().scale(sx, sy);
+        canvasDelegate.getSnapshot().scale(sx, sy);
     }
 
     /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) {
@@ -159,7 +155,7 @@
         }
 
         // get the current top graphics2D object.
-        GcSnapshot g = canvasDelegate.getGcSnapshot();
+        GcSnapshot g = canvasDelegate.getSnapshot();
 
         // get its current matrix
         AffineTransform currentTx = g.getTransform();
@@ -235,7 +231,7 @@
             return 0;
         }
 
-        return canvasDelegate.getGcSnapshot().size();
+        return canvasDelegate.getSnapshot().size();
     }
 
     /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) {
@@ -260,33 +256,18 @@
         throw new UnsupportedOperationException();
     }
 
-    /*package*/ static void drawLines(Canvas thisCanvas, float[] pts, int offset, int count,
+    /*package*/ static void drawLines(Canvas thisCanvas,
+            final float[] pts, final int offset, final int count,
             Paint paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
-
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint.mNativePaint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-        try {
-            for (int i = 0 ; i < count ; i += 4) {
-                g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
-                        (int)pts[i + offset + 2], (int)pts[i + offset + 3]);
-            }
-        } finally {
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/,
+                false /*forceSrcMode*/, new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        for (int i = 0 ; i < count ; i += 4) {
+                            graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
+                                    (int)pts[i + offset + 2], (int)pts[i + offset + 3]);
+                        }
+                    }
+                });
     }
 
     /*package*/ static void freeCaches() {
@@ -300,7 +281,7 @@
             Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
 
             // create a new Canvas_Delegate with the given bitmap and return its new native int.
-            Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate.getImage());
+            Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
 
             return sManager.addDelegate(newDelegate);
         } else {
@@ -326,34 +307,71 @@
             return;
         }
 
-        canvasDelegate.setBitmap(bitmapDelegate.getImage());
+        canvasDelegate.setBitmap(bitmapDelegate);
     }
 
     /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds,
                                                int paint, int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+        if (paintDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags);
     }
 
     /*package*/ static int native_saveLayer(int nativeCanvas, float l,
                                                float t, float r, float b,
                                                int paint, int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+        if (paintDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayer(new RectF(l, t, r, b),
+                paintDelegate, layerFlags);
     }
 
     /*package*/ static int native_saveLayerAlpha(int nativeCanvas,
                                                     RectF bounds, int alpha,
                                                     int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags);
     }
 
     /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l,
                                                     float t, float r, float b,
                                                     int alpha, int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
     }
 
 
@@ -372,7 +390,7 @@
         }
 
         // get the current top graphics2D object.
-        GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
+        GcSnapshot snapshot = canvasDelegate.getSnapshot();
 
         // get its current matrix
         AffineTransform currentTx = snapshot.getTransform();
@@ -399,7 +417,7 @@
         }
 
         // get the current top graphics2D object.
-        GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
+        GcSnapshot snapshot = canvasDelegate.getSnapshot();
 
         // get the AffineTransform of the given matrix
         AffineTransform matrixTx = matrixDelegate.getAffineTransform();
@@ -458,7 +476,7 @@
             return false;
         }
 
-        Rectangle rect = canvasDelegate.getGcSnapshot().getClip().getBounds();
+        Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
         if (rect != null) {
             bounds.left = rect.x;
             bounds.top = rect.y;
@@ -511,7 +529,7 @@
         native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt);
     }
 
-    /*package*/ static void native_drawColor(int nativeCanvas, int color, int mode) {
+    /*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
@@ -519,27 +537,26 @@
             return;
         }
 
-        // get a new graphics context.
-        Graphics2D graphics = (Graphics2D)canvasDelegate.getGcSnapshot().create();
-        try {
-            // reset its transform just in case
-            graphics.setTransform(new AffineTransform());
+        final int w = canvasDelegate.mBitmap.getImage().getWidth();
+        final int h = canvasDelegate.mBitmap.getImage().getHeight();
+        draw(nativeCanvas, new GcSnapshot.Drawable() {
 
-            // set the color
-            graphics.setColor(new Color(color, true /*alpha*/));
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                // reset its transform just in case
+                graphics.setTransform(new AffineTransform());
 
-            Composite composite = PorterDuffXfermode_Delegate.getComposite(
-                    PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
-            if (composite != null) {
-                graphics.setComposite(composite);
+                // set the color
+                graphics.setColor(new Color(color, true /*alpha*/));
+
+                Composite composite = PorterDuffXfermode_Delegate.getComposite(
+                        PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
+                if (composite != null) {
+                    graphics.setComposite(composite);
+                }
+
+                graphics.fillRect(0, 0, w, h);
             }
-
-            graphics.fillRect(0, 0, canvasDelegate.mBufferedImage.getWidth(),
-                    canvasDelegate.mBufferedImage.getHeight());
-        } finally {
-            // dispose Graphics2D object
-            graphics.dispose();
-        }
+        });
     }
 
     /*package*/ static void native_drawPaint(int nativeCanvas, int paint) {
@@ -547,32 +564,16 @@
         throw new UnsupportedOperationException();
     }
 
-    /*package*/ static void native_drawLine(int nativeCanvas, float startX,
-                                               float startY, float stopX,
-                                               float stopY, int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
+    /*package*/ static void native_drawLine(int nativeCanvas,
+            final float startX, final float startY, final float stopX, final float stopY,
+            int paint) {
 
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-        try {
-            g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
-        } finally {
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
+                    }
+        });
     }
 
     /*package*/ static void native_drawRect(int nativeCanvas, RectF rect,
@@ -580,82 +581,51 @@
         native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint);
     }
 
-    /*package*/ static void native_drawRect(int nativeCanvas, float left,
-                                               float top, float right,
-                                               float bottom, int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
+    /*package*/ static void native_drawRect(int nativeCanvas,
+            final float left, final float top, final float right, final float bottom, int paint) {
 
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        int style = paint.getStyle();
 
-        if (right > left && bottom > top) {
-            // get a Graphics2D object configured with the drawing parameters.
-            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
+                        // draw
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fillRect((int)left, (int)top,
+                                    (int)(right-left), (int)(bottom-top));
+                        }
 
-            try {
-                int style = paintDelegate.getStyle();
-
-                // draw
-                if (style == Paint.Style.FILL.nativeInt ||
-                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                    g.fillRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
-                }
-
-                if (style == Paint.Style.STROKE.nativeInt ||
-                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                    g.drawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
-                }
-            } finally {
-                // dispose Graphics2D object
-                g.dispose();
-            }
-        }
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.drawRect((int)left, (int)top,
+                                    (int)(right-left), (int)(bottom-top));
+                        }
+                    }
+        });
     }
 
-    /*package*/ static void native_drawOval(int nativeCanvas, RectF oval,
-                                               int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
+    /*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) {
         if (oval.right > oval.left && oval.bottom > oval.top) {
-            // get a Graphics2D object configured with the drawing parameters.
-            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
+            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                    new GcSnapshot.Drawable() {
+                        public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                            int style = paint.getStyle();
 
-            int style = paintDelegate.getStyle();
+                            // draw
+                            if (style == Paint.Style.FILL.nativeInt ||
+                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                                graphics.fillOval((int)oval.left, (int)oval.top,
+                                        (int)oval.width(), (int)oval.height());
+                            }
 
-            // draw
-            if (style == Paint.Style.FILL.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
-            }
-
-            if (style == Paint.Style.STROKE.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
-            }
-
-            // dispose Graphics2D object
-            g.dispose();
+                            if (style == Paint.Style.STROKE.nativeInt ||
+                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                                graphics.drawOval((int)oval.left, (int)oval.top,
+                                        (int)oval.width(), (int)oval.height());
+                            }
+                        }
+            });
         }
     }
 
@@ -675,46 +645,31 @@
     }
 
     /*package*/ static void native_drawRoundRect(int nativeCanvas,
-                                                    RectF rect, float rx,
-                                                    float ry, int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
+            final RectF rect, final float rx, final float ry, int paint) {
 
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        int style = paint.getStyle();
 
-        if (rect.right > rect.left && rect.bottom > rect.top) {
-            // get a Graphics2D object configured with the drawing parameters.
-            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
+                        // draw
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fillRoundRect(
+                                    (int)rect.left, (int)rect.top,
+                                    (int)rect.width(), (int)rect.height(),
+                                    (int)rx, (int)ry);
+                        }
 
-            int style = paintDelegate.getStyle();
-
-            // draw
-            if (style == Paint.Style.FILL.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.fillRoundRect(
-                        (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                        (int)rx, (int)ry);
-            }
-
-            if (style == Paint.Style.STROKE.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.drawRoundRect(
-                        (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                        (int)rx, (int)ry);
-            }
-
-            // dispose Graphics2D object
-            g.dispose();
-        }
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.drawRoundRect(
+                                    (int)rect.left, (int)rect.top,
+                                    (int)rect.width(), (int)rect.height(),
+                                    (int)rx, (int)ry);
+                        }
+                    }
+        });
     }
 
     /*package*/ static void native_drawPath(int nativeCanvas, int path,
@@ -725,21 +680,22 @@
             return;
         }
 
-        draw(nativeCanvas, paint, new Drawable() {
-            public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                Shape shape = pathDelegate.getJavaShape();
-                int style = paint.getStyle();
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        Shape shape = pathDelegate.getJavaShape();
+                        int style = paint.getStyle();
 
-                if (style == Paint.Style.FILL.nativeInt ||
-                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                    graphics.fill(shape);
-                }
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fill(shape);
+                        }
 
-                if (style == Paint.Style.STROKE.nativeInt ||
-                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                    graphics.draw(shape);
-                }
-            }
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.draw(shape);
+                        }
+                    }
         });
     }
 
@@ -760,7 +716,7 @@
         float right = left + image.getWidth();
         float bottom = top + image.getHeight();
 
-        drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
+        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
                 0, 0, image.getWidth(), image.getHeight(),
                 (int)left, (int)top, (int)right, (int)bottom);
     }
@@ -780,11 +736,11 @@
         BufferedImage image = bitmapDelegate.getImage();
 
         if (src == null) {
-            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
                     0, 0, image.getWidth(), image.getHeight(),
                     (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
         } else {
-            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
                     src.left, src.top, src.width(), src.height(),
                     (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
         }
@@ -805,29 +761,87 @@
         BufferedImage image = bitmapDelegate.getImage();
 
         if (src == null) {
-            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
                     0, 0, image.getWidth(), image.getHeight(),
                     dst.left, dst.top, dst.right, dst.bottom);
         } else {
-            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
                     src.left, src.top, src.width(), src.height(),
                     dst.left, dst.top, dst.right, dst.bottom);
         }
     }
 
     /*package*/ static void native_drawBitmap(int nativeCanvas, int[] colors,
-                                                int offset, int stride, float x,
-                                                 float y, int width, int height,
+                                                int offset, int stride, final float x,
+                                                 final float y, int width, int height,
                                                  boolean hasAlpha,
                                                  int nativePaintOrZero) {
-        // FIXME
-        throw new UnsupportedOperationException();
+
+        // create a temp BufferedImage containing the content.
+        final BufferedImage image = new BufferedImage(width, height,
+                hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+        image.setRGB(0, 0, width, height, colors, offset, stride);
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        if (paint != null && paint.isFilterBitmap()) {
+                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                        }
+
+                        graphics.drawImage(image, (int) x, (int) y, null);
+                    }
+        });
     }
 
     /*package*/ static void nativeDrawBitmapMatrix(int nCanvas, int nBitmap,
                                                       int nMatrix, int nPaint) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return;
+        }
+
+        // get the delegate from the native int.
+        Paint_Delegate paintDelegate = null;
+        if (nPaint > 0) {
+            paintDelegate = Paint_Delegate.getDelegate(nPaint);
+            if (paintDelegate == null) {
+                assert false;
+                return;
+            }
+        }
+
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nBitmap);
+        if (bitmapDelegate == null) {
+            assert false;
+            return;
+        }
+
+        final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+        if (matrixDelegate == null) {
+            assert false;
+            return;
+        }
+
+        final AffineTransform mtx = matrixDelegate.getAffineTransform();
+
+        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+                public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                    if (paint != null && paint.isFilterBitmap()) {
+                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                    }
+
+                    //FIXME add support for canvas, screen and bitmap densities.
+                    graphics.drawImage(image, mtx, null);
+                }
+        }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
     }
 
     /*package*/ static void nativeDrawBitmapMesh(int nCanvas, int nBitmap,
@@ -846,116 +860,103 @@
         throw new UnsupportedOperationException();
     }
 
-    /*package*/ static void native_drawText(int nativeCanvas, char[] text,
-                                               int index, int count, float x,
-                                               float y, int flags, int paint) {
-        // WARNING: the logic in this method is similar to Paint.measureText.
-        // Any change to this method should be reflected in Paint.measureText
-
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        Graphics2D g = (Graphics2D) canvasDelegate.createCustomGraphics(paintDelegate);
-        try {
-            // Paint.TextAlign indicates how the text is positioned relative to X.
-            // LEFT is the default and there's nothing to do.
-            if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
-                float m = paintDelegate.measureText(text, index, count);
-                if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
-                    x -= m / 2;
-                } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
-                    x -= m;
-                }
-            }
-
-            List<FontInfo> fonts = paintDelegate.getFonts();
-
-            if (fonts.size() > 0) {
-                FontInfo mainFont = fonts.get(0);
-                int i = index;
-                int lastIndex = index + count;
-                while (i < lastIndex) {
-                    // always start with the main font.
-                    int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
-                    if (upTo == -1) {
-                        // draw all the rest and exit.
-                        g.setFont(mainFont.mFont);
-                        g.drawChars(text, i, lastIndex - i, (int)x, (int)y);
-                        return;
-                    } else if (upTo > 0) {
-                        // draw what's possible
-                        g.setFont(mainFont.mFont);
-                        g.drawChars(text, i, upTo - i, (int)x, (int)y);
-
-                        // compute the width that was drawn to increase x
-                        x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
-
-                        // move index to the first non displayed char.
-                        i = upTo;
-
-                        // don't call continue at this point. Since it is certain the main font
-                        // cannot display the font a index upTo (now ==i), we move on to the
-                        // fallback fonts directly.
+    /*package*/ static void native_drawText(int nativeCanvas,
+            final char[] text, final int index, final int count,
+            final float startX, final float startY, int flags, int paint) {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                new GcSnapshot.Drawable() {
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                // WARNING: the logic in this method is similar to Paint.measureText.
+                // Any change to this method should be reflected in Paint.measureText
+                // Paint.TextAlign indicates how the text is positioned relative to X.
+                // LEFT is the default and there's nothing to do.
+                float x = startX;
+                float y = startY;
+                if (paint.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+                    float m = paint.measureText(text, index, count);
+                    if (paint.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+                        x -= m / 2;
+                    } else if (paint.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+                        x -= m;
                     }
+                }
 
-                    // no char supported, attempt to read the next char(s) with the
-                    // fallback font. In this case we only test the first character
-                    // and then go back to test with the main font.
-                    // Special test for 2-char characters.
-                    boolean foundFont = false;
-                    for (int f = 1 ; f < fonts.size() ; f++) {
-                        FontInfo fontInfo = fonts.get(f);
+                List<FontInfo> fonts = paint.getFonts();
 
-                        // need to check that the font can display the character. We test
-                        // differently if the char is a high surrogate.
-                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
-                        upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
+                if (fonts.size() > 0) {
+                    FontInfo mainFont = fonts.get(0);
+                    int i = index;
+                    int lastIndex = index + count;
+                    while (i < lastIndex) {
+                        // always start with the main font.
+                        int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
                         if (upTo == -1) {
-                            // draw that char
-                            g.setFont(fontInfo.mFont);
-                            g.drawChars(text, i, charCount, (int)x, (int)y);
+                            // draw all the rest and exit.
+                            graphics.setFont(mainFont.mFont);
+                            graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y);
+                            return;
+                        } else if (upTo > 0) {
+                            // draw what's possible
+                            graphics.setFont(mainFont.mFont);
+                            graphics.drawChars(text, i, upTo - i, (int)x, (int)y);
 
-                            // update x
-                            x += fontInfo.mMetrics.charsWidth(text, i, charCount);
+                            // compute the width that was drawn to increase x
+                            x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
 
-                            // update the index in the text, and move on
+                            // move index to the first non displayed char.
+                            i = upTo;
+
+                            // don't call continue at this point. Since it is certain the main font
+                            // cannot display the font a index upTo (now ==i), we move on to the
+                            // fallback fonts directly.
+                        }
+
+                        // no char supported, attempt to read the next char(s) with the
+                        // fallback font. In this case we only test the first character
+                        // and then go back to test with the main font.
+                        // Special test for 2-char characters.
+                        boolean foundFont = false;
+                        for (int f = 1 ; f < fonts.size() ; f++) {
+                            FontInfo fontInfo = fonts.get(f);
+
+                            // need to check that the font can display the character. We test
+                            // differently if the char is a high surrogate.
+                            int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+                            upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
+                            if (upTo == -1) {
+                                // draw that char
+                                graphics.setFont(fontInfo.mFont);
+                                graphics.drawChars(text, i, charCount, (int)x, (int)y);
+
+                                // update x
+                                x += fontInfo.mMetrics.charsWidth(text, i, charCount);
+
+                                // update the index in the text, and move on
+                                i += charCount;
+                                foundFont = true;
+                                break;
+
+                            }
+                        }
+
+                        // in case no font can display the char, display it with the main font.
+                        // (it'll put a square probably)
+                        if (foundFont == false) {
+                            int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+
+                            graphics.setFont(mainFont.mFont);
+                            graphics.drawChars(text, i, charCount, (int)x, (int)y);
+
+                            // measure it to advance x
+                            x += mainFont.mMetrics.charsWidth(text, i, charCount);
+
+                            // and move to the next chars.
                             i += charCount;
-                            foundFont = true;
-                            break;
-
                         }
                     }
-
-                    // in case no font can display the char, display it with the main font.
-                    // (it'll put a square probably)
-                    if (foundFont == false) {
-                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
-
-                        g.setFont(mainFont.mFont);
-                        g.drawChars(text, i, charCount, (int)x, (int)y);
-
-                        // measure it to advance x
-                        x += mainFont.mMetrics.charsWidth(text, i, charCount);
-
-                        // and move to the next chars.
-                        i += charCount;
-                    }
                 }
             }
-        } finally {
-            g.dispose();
-        }
+        });
     }
 
     /*package*/ static void native_drawText(int nativeCanvas, String text,
@@ -1041,9 +1042,12 @@
     // ---- Private delegate/helper methods ----
 
     /**
-     * Executes a {@link Drawable} with a given canvas and paint.
+     * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, int, int)}.
      */
-    private static void draw(int nCanvas, int nPaint, Drawable drawable) {
+    private static void draw(int nCanvas, int nPaint, boolean compositeOnly, boolean forceSrcMode,
+            GcSnapshot.Drawable drawable) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
         if (canvasDelegate == null) {
@@ -1051,28 +1055,42 @@
             return;
         }
 
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-        if (paintDelegate == null) {
+        // paint could be 0 meaning no paint
+        Paint_Delegate paintDelegate = null;
+        if (nPaint > 0) {
+            paintDelegate = Paint_Delegate.getDelegate(nPaint);
+            if (paintDelegate == null) {
+                assert false;
+                return;
+            }
+        }
+
+        canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
+    }
+
+    /**
+     * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
+     * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, int, int)}.
+     */
+    private static void draw(int nCanvas, GcSnapshot.Drawable drawable) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
             assert false;
             return;
         }
 
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-        try {
-            drawable.draw(g, paintDelegate);
-        } finally {
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        canvasDelegate.mSnapshot.draw(drawable);
     }
 
-    private Canvas_Delegate(BufferedImage image) {
-        setBitmap(image);
+    private Canvas_Delegate(Bitmap_Delegate bitmap) {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
     }
 
     private Canvas_Delegate() {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
     }
 
     /**
@@ -1086,8 +1104,23 @@
         // get the current save count
         int count = mSnapshot.size();
 
-        // create a new snapshot and add it to the stack
-        mSnapshot = new GcSnapshot(mSnapshot, saveFlags);
+        mSnapshot = mSnapshot.save(saveFlags);
+
+        // return the old save count
+        return count;
+    }
+
+    private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
+        Paint_Delegate paint = new Paint_Delegate();
+        paint.setAlpha(alpha);
+        return saveLayer(rect, paint, saveFlags);
+    }
+
+    private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
+        // get the current save count
+        int count = mSnapshot.size();
+
+        mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
 
         // return the old save count
         return count;
@@ -1113,129 +1146,18 @@
         return mSnapshot.clipRect(left, top, right, bottom, regionOp);
     }
 
-    private void setBitmap(BufferedImage image) {
-        mBufferedImage = image;
+    private void setBitmap(Bitmap_Delegate bitmap) {
+        mBitmap = bitmap;
         assert mSnapshot.size() == 1;
-        mSnapshot.setGraphics2D(mBufferedImage.createGraphics());
-    }
-
-    /**
-     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
-     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
-     */
-    /*package*/ Graphics2D createCustomGraphics(Paint_Delegate paint) {
-        // make new one
-        Graphics2D g = getGcSnapshot().create();
-
-        // configure it
-
-        if (paint.isAntiAliased()) {
-            g.setRenderingHint(
-                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-            g.setRenderingHint(
-                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-        }
-
-        // get the shader first, as it'll replace the color if it can be used it.
-        boolean customShader = false;
-        int nativeShader = paint.getShader();
-        if (nativeShader > 0) {
-            Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
-            assert shaderDelegate != null;
-            if (shaderDelegate != null) {
-                if (shaderDelegate.isSupported()) {
-                    java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
-                    assert shaderPaint != null;
-                    if (shaderPaint != null) {
-                        g.setPaint(shaderPaint);
-                        customShader = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(null,
-                            shaderDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-        }
-
-        // if no shader, use the paint color
-        if (customShader == false) {
-            g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
-        }
-
-        boolean customStroke = false;
-        int pathEffect = paint.getPathEffect();
-        if (pathEffect > 0) {
-            PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
-            assert effectDelegate != null;
-            if (effectDelegate != null) {
-                if (effectDelegate.isSupported()) {
-                    Stroke stroke = effectDelegate.getStroke(paint);
-                    assert stroke != null;
-                    if (stroke != null) {
-                        g.setStroke(stroke);
-                        customStroke = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(null,
-                            effectDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-        }
-
-        // if no custom stroke as been set, set the default one.
-        if (customStroke == false) {
-            g.setStroke(new BasicStroke(
-                    paint.getStrokeWidth(),
-                    paint.getJavaCap(),
-                    paint.getJavaJoin(),
-                    paint.getJavaStrokeMiter()));
-        }
-
-        // the alpha for the composite. Always opaque if the normal paint color is used since
-        // it contains the alpha
-        int alpha = customShader ? paint.getAlpha() : 0xFF;
-
-        boolean customXfermode = false;
-        int xfermode = paint.getXfermode();
-        if (xfermode > 0) {
-            Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
-            assert xfermodeDelegate != null;
-            if (xfermodeDelegate != null) {
-                if (xfermodeDelegate.isSupported()) {
-                    Composite composite = xfermodeDelegate.getComposite(alpha);
-                    assert composite != null;
-                    if (composite != null) {
-                        g.setComposite(composite);
-                        customXfermode = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(null,
-                            xfermodeDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-        }
-
-        // if there was no custom xfermode, but we have alpha (due to a shader and a non
-        // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
-        // that will handle the alpha.
-        if (customXfermode == false && alpha != 0xFF) {
-            g.setComposite(PorterDuffXfermode_Delegate.getComposite(
-                    PorterDuff.Mode.SRC_OVER, alpha));
-        }
-
-        return g;
+        mSnapshot.setBitmap(mBitmap);
     }
 
     private static void drawBitmap(
             int nativeCanvas,
-            BufferedImage image,
-            Bitmap.Config mBitmapConfig,
+            Bitmap_Delegate bitmap,
             int nativePaintOrZero,
-            int sleft, int stop, int sright, int sbottom,
-            int dleft, int dtop, int dright, int dbottom) {
+            final int sleft, final int stop, final int sright, final int sbottom,
+            final int dleft, final int dtop, final int dright, final int dbottom) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
@@ -1253,47 +1175,85 @@
             }
         }
 
-        drawBitmap(canvasDelegate, image, mBitmapConfig, paintDelegate,
-                sleft, stop, sright, sbottom,
-                dleft, dtop, dright, dbottom);
+        final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        if (paint != null && paint.isFilterBitmap()) {
+                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                        }
+
+                        //FIXME add support for canvas, screen and bitmap densities.
+                        graphics.drawImage(image, dleft, dtop, dright, dbottom,
+                                sleft, stop, sright, sbottom, null);
+                    }
+        });
     }
 
-    private static void drawBitmap(
-            Canvas_Delegate canvasDelegate,
-            BufferedImage image,
-            Bitmap.Config mBitmapConfig,
-            Paint_Delegate paintDelegate,
-            int sleft, int stop, int sright, int sbottom,
-            int dleft, int dtop, int dright, int dbottom) {
-        //FIXME add support for canvas, screen and bitmap densities.
 
-        Graphics2D g = canvasDelegate.getGcSnapshot().create();
-        try {
-            if (paintDelegate != null && paintDelegate.isFilterBitmap()) {
-                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-            }
+    /**
+     * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
+     * The image returns, through a 1-size boolean array, whether the drawing code should
+     * use a SRC composite no matter what the paint says.
+     *
+     * @param bitmap the bitmap
+     * @param paint the paint that will be used to draw
+     * @param forceSrcMode whether the composite will have to be SRC
+     * @return the image to draw
+     */
+    private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
+            boolean[] forceSrcMode) {
+        BufferedImage image = bitmap.getImage();
+        forceSrcMode[0] = false;
 
-            // if the bitmap config is alpha_8, then we erase all color value from it
-            // before drawing it.
-            if (mBitmapConfig == Bitmap.Config.ALPHA_8) {
-                int w = image.getWidth();
-                int h = image.getHeight();
-                int[] argb = new int[w*h];
-                image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+        // if the bitmap config is alpha_8, then we erase all color value from it
+        // before drawing it.
+        if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
+            fixAlpha8Bitmap(image);
+        } else if (bitmap.hasAlpha() == false) {
+            // hasAlpha is merely a rendering hint. There can in fact be alpha values
+            // in the bitmap but it should be ignored at drawing time.
+            // There is two ways to do this:
+            // - override the composite to be SRC. This can only be used if the composite
+            //   was going to be SRC or SRC_OVER in the first place
+            // - Create a different bitmap to draw in which all the alpha channel values is set
+            //   to 0xFF.
+            if (paint != null) {
+                int xfermode = paint.getXfermode();
+                if (xfermode > 0) {
+                    Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(xfermode);
+                    if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
+                        PorterDuff.Mode mode =
+                            ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
 
-                final int length = argb.length;
-                for (int i = 0 ; i < length; i++) {
-                    argb[i] &= 0xFF000000;
+                        forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER ||
+                                mode == PorterDuff.Mode.SRC;
+                    }
                 }
-                image.setRGB(0, 0, w, h, argb, 0, w);
             }
 
-            g.drawImage(image, dleft, dtop, dright, dbottom,
-                    sleft, stop, sright, sbottom, null);
-        } finally {
-            g.dispose();
+            // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
+            if (forceSrcMode[0] == false) {
+                image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
+            }
         }
+
+        return image;
+    }
+
+    private static void fixAlpha8Bitmap(final BufferedImage image) {
+        int w = image.getWidth();
+        int h = image.getHeight();
+        int[] argb = new int[w * h];
+        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+        final int length = argb.length;
+        for (int i = 0 ; i < length; i++) {
+            argb[i] &= 0xFF000000;
+        }
+        image.setRGB(0, 0, w, h, argb, 0, w);
     }
 }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
index 954c658..a6844d4 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
@@ -55,7 +55,7 @@
 
     @Override
     public String getSupportMessage() {
-        return "Composte Path Effects are not supported in Layout Preview mode.";
+        return "Compose Path Effects are not supported in Layout Preview mode.";
     }
 
     // ---- native methods ----
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
index 2853222..b4baa6f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
@@ -55,7 +55,7 @@
 
     @Override
     public String getSupportMessage() {
-        return "Compose Shader are not supported in Layout Preview mode.";
+        return "Compose Shaders are not supported in Layout Preview mode.";
     }
 
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index f6cee5e..463f4e9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -106,6 +106,45 @@
         return true;
     }
 
+    public static Matrix_Delegate make(AffineTransform matrix) {
+        float[] values = new float[MATRIX_SIZE];
+        values[0] = (float) matrix.getScaleX();
+        values[1] = (float) matrix.getShearX();
+        values[2] = (float) matrix.getTranslateX();
+        values[3] = (float) matrix.getShearY();
+        values[4] = (float) matrix.getScaleY();
+        values[5] = (float) matrix.getTranslateY();
+        values[6] = 0.f;
+        values[7] = 0.f;
+        values[8] = 1.f;
+
+        return new Matrix_Delegate(values);
+    }
+
+    public boolean mapRect(RectF dst, RectF src) {
+        // array with 4 corners
+        float[] corners = new float[] {
+                src.left, src.top,
+                src.right, src.top,
+                src.right, src.bottom,
+                src.left, src.bottom,
+        };
+
+        // apply the transform to them.
+        mapPoints(corners);
+
+        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+
+
+        return (computeTypeMask() & kRectStaysRect_Mask) != 0;
+    }
+
+
     /**
      * Returns an {@link AffineTransform} matching the matrix.
      */
@@ -655,26 +694,7 @@
             return false;
         }
 
-        // array with 4 corners
-        float[] corners = new float[] {
-                src.left, src.top,
-                src.right, src.top,
-                src.right, src.bottom,
-                src.left, src.bottom,
-        };
-
-        // apply the transform to them.
-        d.mapPoints(corners);
-
-        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
-        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
-        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
-
-        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
-        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
-
-
-        return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
+        return d.mapRect(dst, src);
     }
 
     /*package*/ static float native_mapRadius(int native_object, float radius) {
@@ -740,7 +760,6 @@
                 matrix[4], matrix[2], matrix[5]);
     }
 
-
     /**
      * Reset a matrix to the identity
      */
@@ -830,6 +849,10 @@
         reset();
     }
 
+    private Matrix_Delegate(float[] values) {
+        System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
+    }
+
     /**
      * Adds the given transformation to the current Matrix
      * <p/>This in effect does this = this*matrix
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index b8f76a6..25e0795 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -18,6 +18,7 @@
 
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
 import com.android.ninepatch.NinePatchChunk;
 
 import android.graphics.drawable.NinePatchDrawable;
@@ -123,11 +124,11 @@
     }
 
    private static void draw(int canvas_instance,
-           int left, int top, int right, int bottom,
+           final int left, final int top, final int right, final int bottom,
            int bitmap_instance, byte[] c, int paint_instance_or_null,
-           int destDensity, int srcDensity) {
+           final int destDensity, final int srcDensity) {
        // get the delegate from the native int.
-       Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
+       final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
        if (bitmap_delegate == null) {
            assert false;
            return;
@@ -143,7 +144,7 @@
            return;
        }
 
-       NinePatchChunk chunkObject = getChunk(c);
+       final NinePatchChunk chunkObject = getChunk(c);
        assert chunkObject != null;
        if (chunkObject == null) {
            return;
@@ -158,19 +159,13 @@
        // this one can be null
        Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
 
-       Graphics2D graphics;
-       if (paint_delegate != null) {
-           graphics = canvas_delegate.createCustomGraphics(paint_delegate);
-       } else {
-           graphics = canvas_delegate.getGcSnapshot().create();
-       }
+       canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+               public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                   chunkObject.draw(bitmap_delegate.getImage(), graphics,
+                           left, top, right - left, bottom - top, destDensity, srcDensity);
+               }
+           }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/);
 
-       try {
-           chunkObject.draw(bitmap_delegate.getImage(), graphics,
-                   left, top, right - left, bottom - top, destDensity, srcDensity);
-       } finally {
-           graphics.dispose();
-       }
     }
 
     /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 0a597ca..7cb30dd 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -118,6 +118,10 @@
         return mColor >>> 24;
     }
 
+    public void setAlpha(int alpha) {
+        mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
+    }
+
     public int getTextAlign() {
         return mTextAlign;
     }
@@ -130,7 +134,11 @@
      * returns the value of stroke miter needed by the java api.
      */
     public float getJavaStrokeMiter() {
-        return mStrokeMiter * mStrokeWidth;
+        float miter = mStrokeMiter * mStrokeWidth;
+        if (miter < 1.f) {
+            miter = 1.f;
+        }
+        return miter;
     }
 
     public int getJavaCap() {
@@ -274,7 +282,7 @@
             return;
         }
 
-        delegate.mColor = (a << 24) | (delegate.mColor & 0x00FFFFFF);
+        delegate.setAlpha(a);
     }
 
     /*package*/ static float getStrokeWidth(Paint thisPaint) {
@@ -835,7 +843,7 @@
 
     // ---- Private delegate/helper methods ----
 
-    private Paint_Delegate() {
+    /*package*/ Paint_Delegate() {
         reset();
     }
 
@@ -866,14 +874,14 @@
 
     private void reset() {
         mFlags = Paint.DEFAULT_PAINT_FLAGS;
-        mColor = 0;
+        mColor = 0xFF000000;
         mStyle = Paint.Style.FILL.nativeInt;
         mCap = Paint.Cap.BUTT.nativeInt;
         mJoin = Paint.Join.MITER.nativeInt;
         mTextAlign = 0;
         mTypeface = Typeface.sDefaults[0].native_instance;
         mStrokeWidth = 1.f;
-        mStrokeMiter = 2.f;
+        mStrokeMiter = 4.f;
         mTextSize = 20.f;
         mTextScaleX = 1.f;
         mTextSkewX = 0.f;
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
index 097dfce..b7d5dc9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -45,6 +45,10 @@
 
     // ---- Public Helper methods ----
 
+    public PorterDuff.Mode getMode() {
+        return getPorterDuffMode(mMode);
+    }
+
     @Override
     public Composite getComposite(int alpha) {
         return getComposite(getPorterDuffMode(mMode), alpha);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index a09524c..6ebc56c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -107,7 +107,8 @@
             Bridge.prepareThread();
             mLastResult = mSession.acquire(Params.DEFAULT_TIMEOUT);
             if (mLastResult.isSuccess()) {
-                mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index, listener);
+                mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index,
+                        listener);
             }
         } finally {
             mSession.release();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 8c6b1be..27268fc 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -16,20 +16,41 @@
 
 package com.android.layoutlib.bridge.impl;
 
-import android.graphics.Canvas;
-import android.graphics.Region;
+import com.android.layoutlib.bridge.Bridge;
 
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint_Delegate;
+import android.graphics.PathEffect_Delegate;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Shader_Delegate;
+import android.graphics.Xfermode_Delegate;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
 import java.awt.Graphics2D;
+import java.awt.RenderingHints;
 import java.awt.Shape;
+import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
 
 /**
  * Class representing a graphics context snapshot, as well as a context stack as a linked list.
  * <p>
  * This is based on top of {@link Graphics2D} but can operate independently if none are available
  * yet when setting transforms and clip information.
+ * <p>
+ * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and
+ * {@link #draw(Drawable, Paint_Delegate)}
  *
  */
 public class GcSnapshot {
@@ -37,38 +58,241 @@
     private final GcSnapshot mPrevious;
     private final int mFlags;
 
-    private Graphics2D mGraphics2D = null;
+    /** list of layers. */
+    private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
+
     /** temp transform in case transformation are set before a Graphics2D exists */
     private AffineTransform mTransform = null;
     /** temp clip in case clipping is set before a Graphics2D exists */
     private Area mClip = null;
 
+    // local layer data
+    private final Layer mLocalLayer;
+    private final Paint_Delegate mLocalLayerPaint;
+    private Rect mLocalLayerRegion;
+
+    public interface Drawable {
+        void draw(Graphics2D graphics, Paint_Delegate paint);
+    }
+
     /**
-     * Creates a new {@link GcSnapshot} on top of another one.
-     * <p/>
-     * This is basically the equivalent of {@link Canvas#save(int)}
-     * @param previous the previous snapshot head.
-     * @param flags the flags regarding what should be saved.
+     * class containing information about a layer.
      */
-    public GcSnapshot(GcSnapshot previous, int flags) {
-        assert previous != null;
-        mPrevious = previous;
-        mFlags = flags;
-        mGraphics2D = (Graphics2D) previous.mGraphics2D.create();
+    private static class Layer {
+        private final Graphics2D mGraphics;
+        private final Bitmap_Delegate mBitmap;
+        private final BufferedImage mImage;
+        private BufferedImage mOriginalCopy;
+
+        /**
+         * Creates a layer with a graphics and a bitmap.
+         *
+         * @param graphics the graphics
+         * @param bitmap the bitmap
+         */
+        Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
+            mGraphics = graphics;
+            mBitmap = bitmap;
+            mImage = mBitmap.getImage();
+        }
+
+        /**
+         * Creates a layer with a graphics and an image. If the image belongs to a
+         * {@link Bitmap_Delegate}, then {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should
+         * be used.
+         *
+         * @param graphics the graphics
+         * @param image the image
+         */
+        Layer(Graphics2D graphics, BufferedImage image) {
+            mGraphics = graphics;
+            mBitmap = null;
+            mImage = image;
+        }
+
+        /** The Graphics2D, guaranteed to be non null */
+        Graphics2D getGraphics() {
+            return mGraphics;
+        }
+
+        /** The BufferedImage, guaranteed to be non null */
+        BufferedImage getImage() {
+            return mImage;
+        }
+
+        Layer makeCopy() {
+            if (mBitmap != null) {
+                return new Layer((Graphics2D) mGraphics.create(), mBitmap);
+            }
+
+            return new Layer((Graphics2D) mGraphics.create(), mImage);
+        }
+
+        /** sets an optional copy of the original content to be used during restore */
+        void setOriginalCopy(BufferedImage image) {
+            mOriginalCopy = image;
+        }
+
+        BufferedImage getOriginalCopy() {
+            return mOriginalCopy;
+        }
+
+        void change() {
+            if (mBitmap != null) {
+                mBitmap.change();
+            }
+        }
+    }
+
+    /**
+     * Creates the root snapshot associating it with a given bitmap.
+     * <p>
+     * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
+     * called before the snapshot can be used to draw. Transform and clip operations are permitted
+     * before.
+     *
+     * @param image the image to associate to the snapshot or null.
+     * @return the root snapshot
+     */
+    public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
+        GcSnapshot snapshot = new GcSnapshot();
+        if (bitmap != null) {
+            snapshot.setBitmap(bitmap);
+        }
+
+        return snapshot;
+    }
+
+    /**
+     * Saves the current state according to the given flags and returns the new current snapshot.
+     * <p/>
+     * This is the equivalent of {@link Canvas#save(int)}
+     *
+     * @param flags the save flags.
+     * @return the new snapshot
+     *
+     * @see Canvas#save(int)
+     */
+    public GcSnapshot save(int flags) {
+        return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
+    }
+
+    /**
+     * Saves the current state and creates a new layer, and returns the new current snapshot.
+     * <p/>
+     * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
+     *
+     * @param layerBounds the layer bounds
+     * @param paint the Paint information used to blit the layer back into the layers underneath
+     *          upon restore
+     * @param flags the save flags.
+     * @return the new snapshot
+     *
+     * @see Canvas#saveLayer(RectF, Paint, int)
+     */
+    public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
+        return new GcSnapshot(this, layerBounds, paint, flags);
     }
 
     /**
      * Creates the root snapshot.
      * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
      */
-    public GcSnapshot() {
+    private GcSnapshot() {
         mPrevious = null;
         mFlags = 0;
+        mLocalLayer = null;
+        mLocalLayerPaint = null;
+    }
+
+    /**
+     * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
+     * into the main graphics when {@link #restore()} is called.
+     *
+     * @param previous the previous snapshot head.
+     * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
+     * @param paint the Paint information used to blit the layer back into the layers underneath
+     *          upon restore
+     * @param flags the flags regarding what should be saved.
+     */
+    private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
+        assert previous != null;
+        mPrevious = previous;
+        mFlags = flags;
+
+        // make a copy of the current layers before adding the new one.
+        // This keeps the same BufferedImage reference but creates new Graphics2D for this
+        // snapshot.
+        // It does not copy whatever original copy the layers have, as they will be done
+        // only if the new layer doesn't clip drawing to itself.
+        for (Layer layer : mPrevious.mLayers) {
+            mLayers.add(layer.makeCopy());
+        }
+
+        if (layerBounds != null) {
+            // get the current transform
+            AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
+
+            // transform the layerBounds and puts it into a int rect
+            RectF rect2 = new RectF();
+            mapRect(matrix, rect2, layerBounds);
+            mLocalLayerRegion = new Rect();
+            rect2.round(mLocalLayerRegion);
+
+            // get the base layer (always at index 0)
+            Layer baseLayer = mLayers.get(0);
+
+            // create the image for the layer
+            BufferedImage layerImage = new BufferedImage(
+                    baseLayer.getImage().getWidth(),
+                    baseLayer.getImage().getHeight(),
+                    BufferedImage.TYPE_INT_ARGB);
+
+            // create a graphics for it so that drawing can be done.
+            Graphics2D layerGraphics = layerImage.createGraphics();
+
+            // because this layer inherits the current context for transform and clip,
+            // set them to one from the base layer.
+            AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
+            layerGraphics.setTransform(currentMtx);
+
+            Shape currentClip = baseLayer.getGraphics().getClip();
+            layerGraphics.setClip(currentClip);
+
+            // create a new layer for this new layer and add it to the list at the end.
+            mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage));
+
+            // if the drawing is not clipped to the local layer only, we save the current content
+            // of all other layers. We are only interested in the part that will actually
+            // be drawn, so we create as small bitmaps as we can.
+            // This is so that we can erase the drawing that goes in the layers below that will
+            // be coming from the layer itself.
+            if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
+                int w = mLocalLayerRegion.width();
+                int h = mLocalLayerRegion.height();
+                for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
+                    Layer layer = mLayers.get(i);
+                    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+                    Graphics2D graphics = image.createGraphics();
+                    graphics.drawImage(layer.getImage(),
+                            0, 0, w, h,
+                            mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                    mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                            null);
+                    graphics.dispose();
+                    layer.setOriginalCopy(image);
+                }
+            }
+        } else {
+            mLocalLayer = null;
+        }
+
+        mLocalLayerPaint  = paint;
     }
 
     public void dispose() {
-        if (mGraphics2D != null) {
-            mGraphics2D.dispose();
+        for (Layer layer : mLayers) {
+            layer.getGraphics().dispose();
         }
 
         if (mPrevious != null) {
@@ -102,35 +326,35 @@
     }
 
     /**
-     * Sets the Graphics2D object for this snapshot if it was created through {@link #GcSnapshot()}.
+     * Link the snapshot to a Bitmap_Delegate.
+     * <p/>
+     * This is only for the case where the snapshot was created with a null image when calling
+     * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
+     * a previous snapshot.
+     * <p/>
      * If any transform or clip information was set before, they are put into the Graphics object.
-     * @param graphics2D the graphics object to set.
+     * @param bitmap the bitmap to link to.
      */
-    public void setGraphics2D(Graphics2D graphics2D) {
-        mGraphics2D = graphics2D;
+    public void setBitmap(Bitmap_Delegate bitmap) {
+        assert mLayers.size() == 0;
+        Graphics2D graphics2D = bitmap.getImage().createGraphics();
+        mLayers.add(new Layer(graphics2D, bitmap));
         if (mTransform != null) {
-            mGraphics2D.setTransform(mTransform);
+            graphics2D.setTransform(mTransform);
             mTransform = null;
         }
 
         if (mClip != null) {
-            mGraphics2D.setClip(mClip);
+            graphics2D.setClip(mClip);
             mClip = null;
         }
     }
 
-    /**
-     * Creates and return a copy of the current {@link Graphics2D}.
-     * @return a new {@link Graphics2D}.
-     */
-    public Graphics2D create() {
-        assert mGraphics2D != null;
-        return (Graphics2D) mGraphics2D.create();
-    }
-
     public void translate(float dx, float dy) {
-        if (mGraphics2D != null) {
-            mGraphics2D.translate(dx, dy);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().translate(dx, dy);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -140,8 +364,10 @@
     }
 
     public void rotate(double radians) {
-        if (mGraphics2D != null) {
-            mGraphics2D.rotate(radians);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().rotate(radians);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -151,8 +377,10 @@
     }
 
     public void scale(float sx, float sy) {
-        if (mGraphics2D != null) {
-            mGraphics2D.scale(sx, sy);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().scale(sx, sy);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -162,8 +390,9 @@
     }
 
     public AffineTransform getTransform() {
-        if (mGraphics2D != null) {
-            return mGraphics2D.getTransform();
+        if (mLayers.size() > 0) {
+            // all graphics2D in the list have the same transform
+            return mLayers.get(0).getGraphics().getTransform();
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -173,8 +402,10 @@
     }
 
     public void setTransform(AffineTransform transform) {
-        if (mGraphics2D != null) {
-            mGraphics2D.setTransform(transform);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().setTransform(transform);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -184,40 +415,52 @@
     }
 
     public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
-        if (mGraphics2D != null) {
+        if (mLayers.size() > 0) {
+            Shape clip = null;
             if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
-                Area newClip = new Area(mGraphics2D.getClip());
+                Area newClip = new Area(getClip());
                 newClip.subtract(new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                mGraphics2D.setClip(newClip);
+                clip = newClip;
 
             } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
-                mGraphics2D.clipRect((int) left, (int) top,
-                        (int) (right - left), (int) (bottom - top));
+                for (Layer layer : mLayers) {
+                    layer.getGraphics().clipRect(
+                            (int) left, (int) top, (int) (right - left), (int) (bottom - top));
+                }
 
             } else if (regionOp == Region.Op.UNION.nativeInt) {
-                Area newClip = new Area(mGraphics2D.getClip());
+                Area newClip = new Area(getClip());
                 newClip.add(new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                mGraphics2D.setClip(newClip);
+                clip = newClip;
 
             } else if (regionOp == Region.Op.XOR.nativeInt) {
-                Area newClip = new Area(mGraphics2D.getClip());
+                Area newClip = new Area(getClip());
                 newClip.exclusiveOr(new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                mGraphics2D.setClip(newClip);
+                clip = newClip;
 
             } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
                 Area newClip = new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top));
-                newClip.subtract(new Area(mGraphics2D.getClip()));
-                mGraphics2D.setClip(newClip);
+                newClip.subtract(new Area(getClip()));
+                clip = newClip;
+
             } else if (regionOp == Region.Op.REPLACE.nativeInt) {
-                mGraphics2D.setClip((int) left, (int) top,
-                        (int) (right - left), (int) (bottom - top));
+                for (Layer layer : mLayers) {
+                    layer.getGraphics().setClip(
+                            (int) left, (int) top, (int) (right - left), (int) (bottom - top));
+                }
             }
 
-            return mGraphics2D.getClip().getBounds().isEmpty() == false;
+            if (clip != null) {
+                for (Layer layer : mLayers) {
+                    layer.getGraphics().setClip(clip);
+                }
+            }
+
+            return getClip().getBounds().isEmpty() == false;
         } else {
             if (mClip == null) {
                 mClip = new Area();
@@ -238,8 +481,9 @@
     }
 
     public Shape getClip() {
-        if (mGraphics2D != null) {
-            return mGraphics2D.getClip();
+        if (mLayers.size() > 0) {
+            // they all have the same clip
+            return mLayers.get(0).getGraphics().getClip();
         } else {
             if (mClip == null) {
                 mClip = new Area();
@@ -263,26 +507,268 @@
         }
     }
 
+    /**
+     * Executes the Drawable's draw method, with a null paint delegate.
+     * <p/>
+     * Note that the method can be called several times if there are more than one active layer.
+     * @param drawable
+     */
+    public void draw(Drawable drawable) {
+        draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
+    }
+
+    /**
+     * Executes the Drawable's draw method.
+     * <p/>
+     * Note that the method can be called several times if there are more than one active layer.
+     * @param drawable
+     * @param paint
+     * @param compositeOnly whether the paint is used for composite only. This is typically
+     *          the case for bitmaps.
+     * @param forceSrcMode if true, this overrides the composite to be SRC
+     */
+    public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
+            boolean forceSrcMode) {
+        // if we clip to the layer, then we only draw in the layer
+        if (mLocalLayer != null && (mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) != 0) {
+            drawInLayer(mLocalLayer, drawable, paint, compositeOnly, forceSrcMode);
+        } else {
+            // draw in all the layers
+            for (Layer layer : mLayers) {
+                drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode);
+            }
+        }
+    }
+
+    private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
+            boolean compositeOnly, boolean forceSrcMode) {
+        Graphics2D originalGraphics = layer.getGraphics();
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D configuredGraphics2D =
+            paint != null ?
+                    createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) :
+                        (Graphics2D) originalGraphics.create();
+
+        try {
+            drawable.draw(configuredGraphics2D, paint);
+            layer.change();
+        } finally {
+            // dispose Graphics2D object
+            configuredGraphics2D.dispose();
+        }
+    }
+
     private GcSnapshot doRestore() {
-        // if this snapshot does not save everything, then set the previous snapshot
-        // to this snapshot content
         if (mPrevious != null) {
+            boolean forceAllSave = false;
+            if (mLocalLayer != null) {
+                forceAllSave = true; // layers always save both clip and transform
+
+                // prepare to draw the current layer in the previous layers, ie all layers but
+                // the last one, since the last one is the local layer
+                for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
+                    Layer layer = mLayers.get(i);
+
+                    Graphics2D baseGfx = layer.getImage().createGraphics();
+
+                    // if the layer contains an original copy this means the flags
+                    // didn't restrict drawing to the local layer and we need to make sure the
+                    // layer bounds in the layer beneath didn't receive any drawing.
+                    // so we use the originalCopy to erase the new drawings in there.
+                    BufferedImage originalCopy = layer.getOriginalCopy();
+                    if (originalCopy != null) {
+                        Graphics2D g = (Graphics2D) baseGfx.create();
+                        g.setComposite(AlphaComposite.Src);
+
+                        g.drawImage(originalCopy,
+                                mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                        mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                                0, 0, mLocalLayerRegion.width(), mLocalLayerRegion.height(),
+                                null);
+                        g.dispose();
+                    }
+
+                    // now draw put the content of the local layer onto the layer, using the paint
+                    // information
+                    Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
+                            true /*alphaOnly*/, false /*forceSrcMode*/);
+
+                    g.drawImage(mLocalLayer.getImage(),
+                            mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                    mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                            mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                    mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                            null);
+                    g.dispose();
+
+                    baseGfx.dispose();
+                }
+            }
+
+            // if this snapshot does not save everything, then set the previous snapshot
+            // to this snapshot content
+
             // didn't save the matrix? set the current matrix on the previous snapshot
-            if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
-                mPrevious.mGraphics2D.setTransform(getTransform());
+            if (forceAllSave == false && (mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
+                AffineTransform mtx = getTransform();
+                for (Layer layer : mPrevious.mLayers) {
+                    layer.getGraphics().setTransform(mtx);
+                }
             }
 
             // didn't save the clip? set the current clip on the previous snapshot
-            if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
-                mPrevious.mGraphics2D.setClip(mGraphics2D.getClip());
+            if (forceAllSave == false && (mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
+                Shape clip = getClip();
+                for (Layer layer : mPrevious.mLayers) {
+                    layer.getGraphics().setClip(clip);
+                }
             }
         }
 
-        if (mGraphics2D != null) {
-            mGraphics2D.dispose();
+        for (Layer layer : mLayers) {
+            layer.getGraphics().dispose();
         }
 
         return mPrevious;
     }
 
+    /**
+     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
+     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
+     */
+    private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
+            boolean compositeOnly, boolean forceSrcMode) {
+        // make new one graphics
+        Graphics2D g = (Graphics2D) original.create();
+
+        // configure it
+
+        if (paint.isAntiAliased()) {
+            g.setRenderingHint(
+                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            g.setRenderingHint(
+                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        }
+
+        boolean customShader = false;
+
+        // get the shader first, as it'll replace the color if it can be used it.
+        if (compositeOnly == false) {
+            int nativeShader = paint.getShader();
+            if (nativeShader > 0) {
+                Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
+                assert shaderDelegate != null;
+                if (shaderDelegate != null) {
+                    if (shaderDelegate.isSupported()) {
+                        java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
+                        assert shaderPaint != null;
+                        if (shaderPaint != null) {
+                            g.setPaint(shaderPaint);
+                            customShader = true;
+                        }
+                    } else {
+                        Bridge.getLog().fidelityWarning(null,
+                                shaderDelegate.getSupportMessage(),
+                                null);
+                    }
+                }
+            }
+
+            // if no shader, use the paint color
+            if (customShader == false) {
+                g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
+            }
+
+            boolean customStroke = false;
+            int pathEffect = paint.getPathEffect();
+            if (pathEffect > 0) {
+                PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
+                assert effectDelegate != null;
+                if (effectDelegate != null) {
+                    if (effectDelegate.isSupported()) {
+                        Stroke stroke = effectDelegate.getStroke(paint);
+                        assert stroke != null;
+                        if (stroke != null) {
+                            g.setStroke(stroke);
+                            customStroke = true;
+                        }
+                    } else {
+                        Bridge.getLog().fidelityWarning(null,
+                                effectDelegate.getSupportMessage(),
+                                null);
+                    }
+                }
+            }
+
+            // if no custom stroke as been set, set the default one.
+            if (customStroke == false) {
+                g.setStroke(new BasicStroke(
+                        paint.getStrokeWidth(),
+                        paint.getJavaCap(),
+                        paint.getJavaJoin(),
+                        paint.getJavaStrokeMiter()));
+            }
+        }
+
+        // the alpha for the composite. Always opaque if the normal paint color is used since
+        // it contains the alpha
+        int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF;
+
+        if (forceSrcMode) {
+            g.setComposite(AlphaComposite.getInstance(
+                    AlphaComposite.SRC, (float) alpha / 255.f));
+        } else {
+            boolean customXfermode = false;
+            int xfermode = paint.getXfermode();
+            if (xfermode > 0) {
+                Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(xfermode);
+                assert xfermodeDelegate != null;
+                if (xfermodeDelegate != null) {
+                    if (xfermodeDelegate.isSupported()) {
+                        Composite composite = xfermodeDelegate.getComposite(alpha);
+                        assert composite != null;
+                        if (composite != null) {
+                            g.setComposite(composite);
+                            customXfermode = true;
+                        }
+                    } else {
+                        Bridge.getLog().fidelityWarning(null,
+                                xfermodeDelegate.getSupportMessage(),
+                                null);
+                    }
+                }
+            }
+
+            // if there was no custom xfermode, but we have alpha (due to a shader and a non
+            // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
+            // that will handle the alpha.
+            if (customXfermode == false && alpha != 0xFF) {
+                g.setComposite(AlphaComposite.getInstance(
+                        AlphaComposite.SRC_OVER, (float) alpha / 255.f));
+            }
+        }
+
+        return g;
+    }
+
+    private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
+        // array with 4 corners
+        float[] corners = new float[] {
+                src.left, src.top,
+                src.right, src.top,
+                src.right, src.bottom,
+                src.left, src.bottom,
+        };
+
+        // apply the transform to them.
+        matrix.transform(corners, 0, corners, 0, 4);
+
+        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+    }
+
 }
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 51236fe9..ce18ec5 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -519,10 +519,15 @@
      * @param session the session that receives the incoming call
      * @param sessionDescription the session description of the incoming call
      * @throws SipException if the SIP service fails to attach this object to
-     *        the session
+     *        the session or VOIP API is not supported by the device
+     * @see SipManager#isVoipSupported
      */
     public void attachCall(SipSession session, String sessionDescription)
             throws SipException {
+        if (!SipManager.isVoipSupported(mContext)) {
+            throw new SipException("VOIP API is not supported");
+        }
+
         synchronized (this) {
             mSipSession = session;
             mPeerSd = sessionDescription;
@@ -548,10 +553,15 @@
      *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @see Listener#onError
      * @throws SipException if the SIP service fails to create a session for the
-     *        call
+     *        call or VOIP API is not supported by the device
+     * @see SipManager#isVoipSupported
      */
     public void makeCall(SipProfile peerProfile, SipSession sipSession,
             int timeout) throws SipException {
+        if (!SipManager.isVoipSupported(mContext)) {
+            throw new SipException("VOIP API is not supported");
+        }
+
         synchronized (this) {
             mSipSession = sipSession;
             try {
@@ -595,6 +605,9 @@
     public void holdCall(int timeout) throws SipException {
         synchronized (this) {
             if (mHold) return;
+            if (mSipSession == null) {
+                throw new SipException("Not in a call to hold call");
+            }
             mSipSession.changeCall(createHoldOffer().encode(), timeout);
             mHold = true;
             setAudioGroupMode();
@@ -614,6 +627,9 @@
      */
     public void answerCall(int timeout) throws SipException {
         synchronized (this) {
+            if (mSipSession == null) {
+                throw new SipException("No call to answer");
+            }
             try {
                 mAudioStream = new AudioStream(InetAddress.getByName(
                         getLocalIp()));
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 2e386620..dce46fe 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -133,7 +133,7 @@
     }
 
     /**
-     * Returns true if the system supports SIP-based VoIP.
+     * Returns true if the system supports SIP-based VOIP API.
      */
     public static boolean isVoipSupported(Context context) {
         return context.getPackageManager().hasSystemFeature(
@@ -305,12 +305,17 @@
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @return a {@link SipAudioCall} object
-     * @throws SipException if calling the SIP service results in an error
+     * @throws SipException if calling the SIP service results in an error or
+     *      VOIP API is not supported by the device
      * @see SipAudioCall.Listener#onError
+     * @see #isVoipSupported
      */
     public SipAudioCall makeAudioCall(SipProfile localProfile,
             SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
             throws SipException {
+        if (!isVoipSupported(mContext)) {
+            throw new SipException("VOIP API is not supported");
+        }
         SipAudioCall call = new SipAudioCall(mContext, localProfile);
         call.setListener(listener);
         SipSession s = createSipSession(localProfile, null);
@@ -332,12 +337,17 @@
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @return a {@link SipAudioCall} object
-     * @throws SipException if calling the SIP service results in an error
+     * @throws SipException if calling the SIP service results in an error or
+     *      VOIP API is not supported by the device
      * @see SipAudioCall.Listener#onError
+     * @see #isVoipSupported
      */
     public SipAudioCall makeAudioCall(String localProfileUri,
             String peerProfileUri, SipAudioCall.Listener listener, int timeout)
             throws SipException {
+        if (!isVoipSupported(mContext)) {
+            throw new SipException("VOIP API is not supported");
+        }
         try {
             return makeAudioCall(
                     new SipProfile.Builder(localProfileUri).build(),