Merge "Import Romansh translations."
diff --git a/Android.mk b/Android.mk
index 39bbbddc..4fab976 100644
--- a/Android.mk
+++ b/Android.mk
@@ -39,6 +39,9 @@
        core/java/android/webkit/EventLogTags.logtags \
        telephony/java/com/android/internal/telephony/EventLogTags.logtags \
 
+# RenderScript files for internal widgets
+LOCAL_SRC_FILES += $(call all-renderscript-files-under, core/java/com/android/internal/widget)
+
 # The following filters out code we are temporarily not including at all.
 # TODO: Move AWT and beans (and associated harmony code) back into libcore.
 # TODO: Maybe remove javax.microedition entirely?
diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java
index 05eed0e..7415721 100644
--- a/core/java/android/bluetooth/BluetoothDevicePicker.java
+++ b/core/java/android/bluetooth/BluetoothDevicePicker.java
@@ -63,4 +63,9 @@
     public static final int FILTER_TYPE_AUDIO = 1;
     /** Ask device picker to show BT devices that support Object Transfer */
     public static final int FILTER_TYPE_TRANSFER = 2;
+    /** Ask device picker to show BT devices that support
+     * Personal Area Networking User (PANU) profile*/
+    public static final int FILTER_TYPE_PANU = 3;
+    /** Ask device picker to show BT devices that support Network Access Point (NAP) profile */
+    public static final int FILTER_TYPE_NAP = 4;
 }
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
new file mode 100644
index 0000000..952765d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class BluetoothPan {
+    private static final String TAG = "BluetoothPan";
+    private static final boolean DBG = false;
+
+    /** int extra for ACTION_PAN_STATE_CHANGED */
+    public static final String EXTRA_PAN_STATE =
+        "android.bluetooth.pan.extra.STATE";
+    /** int extra for ACTION_PAN_STATE_CHANGED */
+    public static final String EXTRA_PREVIOUS_PAN_STATE =
+        "android.bluetooth.pan.extra.PREVIOUS_STATE";
+
+    /** Indicates the state of an PAN device has changed.
+     * This intent will always contain EXTRA_DEVICE_STATE,
+     * EXTRA_PREVIOUS_DEVICE_STATE and BluetoothDevice.EXTRA_DEVICE
+     * extras.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PAN_STATE_CHANGED =
+        "android.bluetooth.pan.action.STATE_CHANGED";
+
+    public static final String NAP_ROLE = "nap";
+    public static final String NAP_BRIDGE = "pan1";
+
+    public static final int MAX_CONNECTIONS = 7;
+
+    public static final int STATE_DISCONNECTED = 0;
+    public static final int STATE_CONNECTING   = 1;
+    public static final int STATE_CONNECTED    = 2;
+    public static final int STATE_DISCONNECTING = 3;
+
+    private final IBluetooth mService;
+    private final Context mContext;
+
+    /**
+     * Create a BluetoothPan proxy object for interacting with the local
+     * Bluetooth Pan service.
+     * @param c Context
+     */
+    public BluetoothPan(Context c) {
+        mContext = c;
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+        if (b != null) {
+            mService = IBluetooth.Stub.asInterface(b);
+        } else {
+            Log.w(TAG, "Bluetooth Service not available!");
+
+            // Instead of throwing an exception which prevents people from going
+            // into Wireless settings in the emulator. Let it crash later
+            // when it is actually used.
+            mService = null;
+        }
+    }
+
+    /**
+     * Initiate a PAN connection.
+     *
+     * This function returns false on error and true if the connection
+     * attempt is being made.
+     *
+     * Listen for {@link #ACTION_PAN_STATE_CHANGED} to find out when the
+     * connection is completed.
+     *
+     * @param device Remote BT device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        try {
+            return mService.connectPanDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Initiate disconnect from PAN.
+     *
+     * This function return false on error and true if the disconnection
+     * attempt is being made.
+     *
+     * Listen for {@link #ACTION_PAN_STATE_CHANGED} to find out when
+     * disconnect is completed.
+     *
+     * @param device Remote BT device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        try {
+            return mService.disconnectPanDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /** Get the state of a PAN Device.
+    *
+    * This function returns an int representing the state of the PAN connection
+    *
+    *  @param device Remote BT device.
+    *  @return The current state of the PAN Device
+    *  @hide
+    */
+   public int getPanDeviceState(BluetoothDevice device) {
+       if (DBG) log("getPanDeviceState(" + device + ")");
+       try {
+           return mService.getPanDeviceState(device);
+       } catch (RemoteException e) {
+           Log.e(TAG, "", e);
+           return STATE_DISCONNECTED;
+       }
+   }
+
+   /** Returns a set of all the connected PAN Devices
+   *
+   * Does not include devices that are currently connecting or disconnecting
+   *
+   * @return a unmodifiable set of connected PAN Devices, or null on error.
+   * @hide
+   */
+   public Set<BluetoothDevice> getConnectedDevices() {
+      if (DBG) log("getConnectedDevices");
+      try {
+          return Collections.unmodifiableSet(
+                  new HashSet<BluetoothDevice>(
+                      Arrays.asList(mService.getConnectedPanDevices())));
+      } catch (RemoteException e) {
+          Log.e(TAG, "", e);
+          return null;
+      }
+   }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    public void setBluetoothTethering(boolean value, String uuid, String bridge) {
+        try {
+            mService.setBluetoothTethering(value, uuid, bridge);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    public boolean isTetheringOn() {
+        try {
+            return mService.isTetheringOn();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index f1ee907..fb3dfe4 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -51,10 +51,14 @@
             ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
     public static final ParcelUuid Hid =
             ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
+    public static final ParcelUuid PANU =
+            ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid NAP =
+            ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
 
     public static final ParcelUuid[] RESERVED_UUIDS = {
         AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
-        ObexObjectPush};
+        ObexObjectPush, PANU, NAP};
 
     public static boolean isAudioSource(ParcelUuid uuid) {
         return uuid.equals(AudioSource);
@@ -88,6 +92,13 @@
         return uuid.equals(Hid);
     }
 
+    public static boolean isPANU(ParcelUuid uuid) {
+        return uuid.equals(PANU);
+    }
+
+    public static boolean isNAP(ParcelUuid uuid) {
+        return uuid.equals(NAP);
+    }
     /**
      * Returns true if ParcelUuid is present in uuidArray
      *
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 75f093c..f8f678b 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -81,4 +81,11 @@
     int getInputDeviceState(in BluetoothDevice device);
     boolean setInputDevicePriority(in BluetoothDevice device, int priority);
     int getInputDevicePriority(in BluetoothDevice device);
+
+    boolean isTetheringOn();
+    void setBluetoothTethering(boolean value, String uuid, String bridge);
+    int getPanDeviceState(in BluetoothDevice device);
+    BluetoothDevice[] getConnectedPanDevices();
+    boolean connectPanDevice(in BluetoothDevice device);
+    boolean disconnectPanDevice(in BluetoothDevice device);
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6335296..8d1a04c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -324,14 +324,14 @@
      * <p>
      * All applications that have background services that use the network
      * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
-     * 
+     *
      * @return Whether background data usage is allowed.
      */
     public boolean getBackgroundDataSetting() {
         try {
             return mService.getBackgroundDataSetting();
         } catch (RemoteException e) {
-            // Err on the side of safety 
+            // Err on the side of safety
             return false;
         }
     }
@@ -489,6 +489,17 @@
         }
     }
 
+    /**
+     * {@hide}
+     */
+    public String[] getTetherableBluetoothRegexs() {
+        try {
+            return mService.getTetherableBluetoothRegexs();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
+
     /** {@hide} */
     public static final int TETHER_ERROR_NO_ERROR           = 0;
     /** {@hide} */
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 5a14cc9..1d57019 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -1,16 +1,16 @@
 /**
  * Copyright (c) 2008, The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); 
- * you may not use this file except in compliance with the License. 
- * You may obtain a copy of the License at 
+ * 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 
+ *     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 
+ * 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.
  */
 
@@ -73,5 +73,7 @@
 
     String[] getTetherableWifiRegexs();
 
+    String[] getTetherableBluetoothRegexs();
+
     void requestNetworkTransitionWakelock(in String forWhom);
 }
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index eb9b62b..9948060 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.content.Intent;
@@ -365,7 +366,8 @@
             return;
         }
         if (DBG) {
-            log("Device property changed:" + address + "property:" + name);
+            log("Device property changed: " + address + " property: "
+                    + name + " value: " + propValues[1]);
         }
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         if (name.equals("Name")) {
@@ -442,6 +444,25 @@
         mBluetoothService.handleInputDevicePropertyChange(address, state);
     }
 
+    private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
+        String name = propValues[0];
+        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+        if (address == null) {
+            Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null");
+            return;
+        }
+        if (DBG) {
+            log("Pan Device property changed: " + address + "  property: "
+                    + name + " value: "+ propValues[1]);
+        }
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        if (name.equals("Connected")) {
+            int state = propValues[1].equals("true") ? BluetoothInputDevice.STATE_CONNECTED :
+                BluetoothInputDevice.STATE_DISCONNECTED;
+            mBluetoothService.handlePanDeviceStateChange(device, state);
+        }
+    }
+
     private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
@@ -623,6 +644,8 @@
              } else {
                  Log.i(TAG, "Rejecting incoming HID connection from " + address);
              }
+        } else if (BluetoothUuid.isNAP(uuid)){
+            authorized = true;
         } else {
             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
         }
@@ -713,6 +736,30 @@
         }
     }
 
+    private void onPanDeviceConnectionResult(String path, boolean result) {
+        log ("onPanDeviceConnectionResult " + path + " " + result);
+        // Success case gets handled by Property Change signal
+        if (!result) {
+            String address = mBluetoothService.getAddressFromObjectPath(path);
+            if (address == null) return;
+
+            boolean connected = false;
+            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            int state = mBluetoothService.getPanDeviceState(device);
+            if (state == BluetoothPan.STATE_CONNECTING) {
+                connected = false;
+            } else if (state == BluetoothPan.STATE_DISCONNECTING) {
+                connected = true;
+            } else {
+                Log.e(TAG, "Error onPanDeviceConnectionResult. State is: "
+                        + state + " result: "+ result);
+            }
+            int newState = connected? BluetoothPan.STATE_CONNECTED :
+                BluetoothPan.STATE_DISCONNECTED;
+            mBluetoothService.handlePanDeviceStateChange(device, newState);
+        }
+    }
+
     private void onRestartRequired() {
         if (mBluetoothService.isEnabled()) {
             Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index acfc0d7..ab78aeb 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -24,28 +24,30 @@
 
 package android.server;
 
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfileState;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
-import android.bluetooth.IBluetoothHeadset;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.InterfaceConfiguration;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -89,6 +91,7 @@
     private int mBluetoothState;
     private boolean mRestart = false;  // need to call enable() after disable()
     private boolean mIsDiscovering;
+    private boolean mTetheringOn;
 
     private BluetoothAdapter mAdapter;  // constant after init()
     private final BondState mBondState = new BondState();  // local cache of bondings
@@ -109,6 +112,10 @@
     private static final int MESSAGE_UUID_INTENT = 3;
     private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
 
+    private ArrayList<String> mBluetoothIfaceAddresses;
+    private static final String BLUETOOTH_NEAR_IFACE_ADDR_PREFIX= "192.168.44.";
+    private static final String BLUETOOTH_NETMASK        = "255.255.255.0";
+
     // The timeout used to sent the UUIDs Intent
     // This timeout should be greater than the page timeout
     private static final int UUID_INTENT_DELAY = 6000;
@@ -136,6 +143,7 @@
 
     private BluetoothA2dpService mA2dpService;
     private final HashMap<BluetoothDevice, Integer> mInputDevices;
+    private final HashMap<BluetoothDevice, Integer> mPanDevices;
 
     private static String mDockAddress;
     private String mDockPin;
@@ -187,6 +195,7 @@
 
         mBluetoothState = BluetoothAdapter.STATE_OFF;
         mIsDiscovering = false;
+        mTetheringOn = false;
         mAdapterProperties = new HashMap<String, String>();
         mDeviceProperties = new HashMap<String, Map<String,String>>();
 
@@ -199,6 +208,12 @@
         mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
         mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
 
+        // Can tether to up to 7 devices
+        mBluetoothIfaceAddresses = new ArrayList<String>(BluetoothPan.MAX_CONNECTIONS);
+        for (int i=1; i <= BluetoothPan.MAX_CONNECTIONS; i++) {
+            mBluetoothIfaceAddresses.add(BLUETOOTH_NEAR_IFACE_ADDR_PREFIX + i);
+        }
+
         mHfpProfileState.start();
         mA2dpProfileState.start();
         mHidProfileState.start();
@@ -209,6 +224,7 @@
         filter.addAction(Intent.ACTION_DOCK_EVENT);
         mContext.registerReceiver(mReceiver, filter);
         mInputDevices = new HashMap<BluetoothDevice, Integer>();
+        mPanDevices = new HashMap<BluetoothDevice, Integer>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -336,6 +352,7 @@
         }
         setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
         mHandler.removeMessages(MESSAGE_REGISTER_SDP_RECORDS);
+        setBluetoothTethering(false, BluetoothPan.NAP_ROLE, BluetoothPan.NAP_BRIDGE);
 
         // Allow 3 seconds for profiles to gracefully disconnect
         // TODO: Introduce a callback mechanism so that each profile can notify
@@ -1235,6 +1252,194 @@
         return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
     }
 
+    public synchronized boolean isTetheringOn() {
+        return mTetheringOn;
+    }
+
+    public synchronized void setBluetoothTethering(boolean value, String uuid, String bridge) {
+        mTetheringOn = value;
+        if (!value) {
+            disconnectPan();
+        }
+        setBluetoothTetheringNative(value, uuid, bridge);
+    }
+
+    public synchronized int getPanDeviceState(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        if (mPanDevices.get(device) == null) {
+            return BluetoothPan.STATE_DISCONNECTED;
+        }
+        return mPanDevices.get(device);
+    }
+
+    public synchronized boolean connectPanDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (DBG) log("connect PAN(" + objectPath + ")");
+        if (getPanDeviceState(device) != BluetoothPan.STATE_DISCONNECTED) {
+            log (device + " already connected to PAN");
+        }
+
+        int connectedCount = 0;
+        for (BluetoothDevice BTdevice: mPanDevices.keySet()) {
+            if (getPanDeviceState(BTdevice) == BluetoothPan.STATE_CONNECTED) {
+                connectedCount ++;
+            }
+        }
+        if (connectedCount > 8) {
+            log (device + " could not connect to PAN because 8 other devices are already connected");
+            return false;
+        }
+
+        handlePanDeviceStateChange(device, BluetoothPan.STATE_CONNECTING);
+        if (connectPanDeviceNative(objectPath, "nap", "panu")) {
+            log ("connecting to PAN");
+            return true;
+        } else {
+            handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED);
+            log ("could not connect to PAN");
+            return false;
+        }
+    }
+
+    private synchronized boolean disconnectPan() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (DBG) log("disconnect all PAN devices");
+
+        for (BluetoothDevice device: mPanDevices.keySet()) {
+            if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
+                if (!disconnectPanDevice(device)) {
+                    log ("could not disconnect Pan Device "+device.getAddress());
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public synchronized BluetoothDevice[] getConnectedPanDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>();
+        for (BluetoothDevice device: mPanDevices.keySet()) {
+            if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
+                devices.add(device);
+            }
+        }
+        return devices.toArray(new BluetoothDevice[devices.size()]);
+    }
+
+    public synchronized boolean disconnectPanDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (DBG) log("disconnect PAN(" + objectPath + ")");
+        if (getPanDeviceState(device) != BluetoothPan.STATE_CONNECTED) {
+            log (device + " already disconnected from PAN");
+        }
+        handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTING);
+        return disconnectPanDeviceNative(objectPath);
+    }
+
+    /*package*/ void handlePanDeviceStateChange(BluetoothDevice device, int state) {
+        int prevState;
+        if (mPanDevices.get(device) == null) {
+            prevState = BluetoothPan.STATE_DISCONNECTED;
+        } else {
+            prevState = mPanDevices.get(device);
+        }
+        if (prevState == state) return;
+
+        mPanDevices.put(device, state);
+
+        if (state == BluetoothPan.STATE_CONNECTED) {
+            updateTetherState(true);
+        }
+
+        Intent intent = new Intent(BluetoothPan.ACTION_PAN_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_PAN_STATE, prevState);
+        intent.putExtra(BluetoothPan.EXTRA_PAN_STATE, state);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+        if (DBG) log("Pan Device state : device: " + device + " State:" + prevState + "->" + state);
+
+    }
+
+    // configured when we start tethering and unconfig'd on error or conclusion
+    private boolean updateTetherState(boolean enabled) {
+        Log.d(TAG, "configureBluetoothIface(" + enabled + ")");
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+        ConnectivityManager cm =
+            (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
+
+        // bring toggle the interfaces
+        String[] ifaces = new String[0];
+        try {
+            ifaces = service.listInterfaces();
+        } catch (Exception e) {
+            Log.e(TAG, "Error listing Interfaces :" + e);
+            return false;
+        }
+
+        ArrayList<String> ifaceAddresses = (ArrayList<String>) mBluetoothIfaceAddresses.clone();
+        for (String iface : ifaces) {
+            for (String regex : bluetoothRegexs) {
+                if (iface.matches(regex)) {
+                    InterfaceConfiguration ifcg = null;
+                    try {
+                        ifcg = service.getInterfaceConfig(iface);
+                        if (ifcg != null) {
+                            if (enabled) {
+                                String[] addr = BLUETOOTH_NETMASK.split("\\.");
+                                ifcg.netmask = (Integer.parseInt(addr[0]) << 24) +
+                                        (Integer.parseInt(addr[1]) << 16) +
+                                        (Integer.parseInt(addr[2]) << 8) +
+                                        (Integer.parseInt(addr[3]));
+                                if (ifcg.ipAddr == 0 && !ifaceAddresses.isEmpty()) {
+                                    addr = ifaceAddresses.remove(0).split("\\.");
+                                    ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) +
+                                            (Integer.parseInt(addr[1]) << 16) +
+                                            (Integer.parseInt(addr[2]) << 8) +
+                                            (Integer.parseInt(addr[3]));
+                                } else {
+                                    String IfaceAddress =
+                                            String.valueOf(ifcg.ipAddr >>> 24) + "." +
+                                            String.valueOf((ifcg.ipAddr & 0x00FF0000) >>> 16) + "." +
+                                            String.valueOf((ifcg.ipAddr & 0x0000FF00) >>> 8) + "." +
+                                            String.valueOf(ifcg.ipAddr & 0x000000FF);
+                                    ifaceAddresses.remove(IfaceAddress);
+                                }
+                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
+                            } else {
+                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
+                            }
+                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
+                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
+                            service.setInterfaceConfig(iface, ifcg);
+                            if (enabled) {
+                                if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                                    Log.e(TAG, "Error tethering "+ifaces);
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
     public synchronized boolean connectInputDevice(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
@@ -1418,7 +1623,7 @@
             if (updateRemoteDevicePropertiesCache(address))
                 return getRemoteDeviceProperty(address, property);
         }
-        Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
+        Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
         return null;
     }
 
@@ -2281,4 +2486,8 @@
     private native boolean setLinkTimeoutNative(String path, int num_slots);
     private native boolean connectInputDeviceNative(String path);
     private native boolean disconnectInputDeviceNative(String path);
+
+    private native boolean setBluetoothTetheringNative(boolean value, String nap, String bridge);
+    private native boolean connectPanDeviceNative(String path, String srcRole, String dstRole);
+    private native boolean disconnectPanDeviceNative(String path);
 }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 0466c69..54ac906 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1729,7 +1729,7 @@
     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
     private float mSpacingMult;
     private float mSpacingAdd;
-    private static Rect sTempRect = new Rect();
+    private static final Rect sTempRect = new Rect();
     private boolean mSpannedText;
 
     public static final int DIR_LEFT_TO_RIGHT = 1;
diff --git a/core/java/android/webkit/DeviceOrientationManager.java b/core/java/android/webkit/DeviceOrientationManager.java
index f65dccf..aac2f43 100644
--- a/core/java/android/webkit/DeviceOrientationManager.java
+++ b/core/java/android/webkit/DeviceOrientationManager.java
@@ -17,8 +17,8 @@
 package android.webkit;
 
 /**
- * This class is simply a container for the methods used to configure WebKit's
- * mock DeviceOrientationClient for use in LayoutTests.
+ * This class is simply a container for the methods used to implement DeviceOrientation,
+ * including the mock DeviceOrientationClient for use in LayoutTests.
  *
  * This could be part of WebViewCore, but have moved it to its own class to
  * avoid bloat there.
@@ -26,6 +26,7 @@
  */
 public final class DeviceOrientationManager {
     private WebViewCore mWebViewCore;
+    private DeviceOrientationService mService;
 
     public DeviceOrientationManager(WebViewCore webViewCore) {
         mWebViewCore = webViewCore;
@@ -50,9 +51,19 @@
                 canProvideGamma, gamma);
     }
 
+    public void onOrientationChange(Double alpha, Double beta, Double gamma) {
+        nativeOnOrientationChange(mWebViewCore,
+                alpha != null, alpha != null ? alpha.doubleValue() : 0.0,
+                beta != null, beta != null ? beta.doubleValue() : 0.0,
+                gamma != null, gamma != null ? gamma.doubleValue() : 0.0);
+    }
+
     // Native functions
     private static native void nativeUseMock(WebViewCore webViewCore);
     private static native void nativeSetMockOrientation(WebViewCore webViewCore,
             boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
             boolean canProvideGamma, double gamma);
+    private static native void nativeOnOrientationChange(WebViewCore webViewCore,
+            boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
+            boolean canProvideGamma, double gamma);
 }
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
new file mode 100755
index 0000000..07d3d2f
--- /dev/null
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.webkit;
+
+import android.os.Handler;
+import android.webkit.DeviceOrientationManager;
+import java.lang.Runnable;
+
+
+final class DeviceOrientationService {
+    private DeviceOrientationManager mManager;
+    private boolean mIsRunning;
+    private Handler mHandler;
+
+    public DeviceOrientationService(DeviceOrientationManager manager) {
+        mManager = manager;
+        assert(mManager != null);
+     }
+
+    public void start() {
+        mIsRunning = true;
+        registerForSensors();
+    }
+
+    public void stop() {
+        mIsRunning = false;
+        unregisterFromSensors();
+    }
+
+    public void suspend() {
+        if (mIsRunning) {
+            unregisterFromSensors();
+        }
+    }
+
+    public void resume() {
+        if (mIsRunning) {
+            registerForSensors();
+        }
+    }
+
+    private void sendErrorEvent() {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        if (mHandler == null) {
+            mHandler = new Handler();
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+                if (mIsRunning) {
+                    mManager.onOrientationChange(null, null, null);
+                }
+            }
+        });
+    }
+
+    private void registerForSensors() {
+        // Send the error event for now.
+        // FIXME: Implement.
+        sendErrorEvent();
+    }
+
+    private void unregisterFromSensors() {
+        // FIXME: Implement.
+    }
+}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 4bb65e1..860edf2 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -34,6 +34,7 @@
 import android.view.SurfaceView;
 import android.view.View;
 import android.webkit.DeviceOrientationManager;
+import android.webkit.DeviceOrientationService;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -118,6 +119,7 @@
     private int mWebkitScrollY = 0;
 
     private DeviceOrientationManager mDeviceOrientationManager = new DeviceOrientationManager(this);
+    private DeviceOrientationService mDeviceOrientationService;
 
     // The thread name used to identify the WebCore thread and for use in
     // debugging other classes that require operation within the WebCore thread.
@@ -2500,6 +2502,13 @@
                 canProvideGamma, gamma);
     }
 
+    protected DeviceOrientationService getDeviceOrientationService() {
+        if (mDeviceOrientationService == null) {
+            mDeviceOrientationService = new DeviceOrientationService(mDeviceOrientationManager);
+        }
+        return mDeviceOrientationService;
+    }
+
     private native void nativePause();
     private native void nativeResume();
     private native void nativeFreeMemory();
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index ea975c2..33ebcf5 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -727,7 +727,8 @@
             if (mWebView.getWebViewCore() != null) {
                 // we always force, in case our height changed, in which case we
                 // still want to send the notification over to webkit.
-                refreshZoomScale(mUpdateTextWrap);
+                setZoomScale(Math.max(mActualScale, getZoomOverviewScale()),
+                    mUpdateTextWrap, true);
                 // update the zoom buttons as the scale can be changed
                 updateZoomPicker();
             }
@@ -787,8 +788,6 @@
                 && Math.abs((viewWidth * mInvActualScale) - mZoomOverviewWidth) > 1))) {
             mInitialZoomOverview = false;
             setZoomScale(zoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale));
-        } else {
-            mInZoomOverview = !exceedsMinScaleIncrement(mActualScale, zoomOverviewScale);
         }
     }
 
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index f11c5c6..b6c8e47 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -340,9 +340,14 @@
         for (int i = 0; i < mPreviousViews.size(); i++) {
             View viewToRemove = mPreviousViews.get(i);
             viewToRemove.clearAnimation();
+            if (viewToRemove instanceof ViewGroup) {
+                ViewGroup vg = (ViewGroup) viewToRemove;
+                vg.removeAllViewsInLayout();
+            }
             // applyTransformForChildAtIndex here just allows for any cleanup
             // associated with this view that may need to be done by a subclass
             applyTransformForChildAtIndex(viewToRemove, -1);
+
             removeViewInLayout(viewToRemove);
         }
         mPreviousViews.clear();
@@ -405,10 +410,14 @@
                     // and apply any transform / animation
                     View newView = mAdapter.getView(i, null, this);
                     if (newView != null) {
-                        mActiveViews[index] = newView;
-                        addViewInLayout(newView, -1, createOrReuseLayoutParams(newView));
-                        applyTransformForChildAtIndex(newView, newRelativeIndex);
-                        animateViewForTransition(-1, newRelativeIndex, newView);
+                        // We wrap the new view in a FrameLayout so as to respect the contract
+                        // with the adapter, that is, that we don't modify this view directly
+                        FrameLayout fl = new FrameLayout(mContext);
+                        fl.addView(newView);
+                        mActiveViews[index] = fl;
+                        addViewInLayout(fl, -1, createOrReuseLayoutParams(fl));
+                        applyTransformForChildAtIndex(fl, newRelativeIndex);
+                        animateViewForTransition(-1, newRelativeIndex, fl);
                     }
                 }
                 mActiveViews[index].bringToFront();
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 5797cbb..c3e8838 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -48,7 +48,7 @@
     /**
      * Default animation parameters
      */
-    private final int DEFAULT_ANIMATION_DURATION = 500;
+    private final int DEFAULT_ANIMATION_DURATION = 400;
     private final int MINIMUM_ANIMATION_DURATION = 50;
 
     /**
@@ -141,9 +141,9 @@
             view.setVisibility(VISIBLE);
 
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            int largestDuration =
+                Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
 
-            int largestDuration = Math.round(
-                    (lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION);
             int duration = largestDuration;
             if (mYVelocity != 0) {
                 duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity);
@@ -165,7 +165,8 @@
             // Slide item out
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
 
-            int largestDuration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION);
+            int largestDuration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
+                    DEFAULT_ANIMATION_DURATION);
             int duration = largestDuration;
             if (mYVelocity != 0) {
                 duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity);
@@ -245,6 +246,7 @@
             // ClipChildren and ClipToPadding. We're probably going  to want to reset
             // these flags as well.
             setClipChildren(false);
+            setClipToPadding(false);
             ViewGroup view = this;
             while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
                 view = (ViewGroup) view.getParent();
@@ -297,22 +299,37 @@
 
     private void beginGestureIfNeeded(float deltaY) {
         if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
-            mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
+            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
             cancelLongPress();
             requestDisallowInterceptTouchEvent(true);
 
-            int activeIndex = mSwipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1
+            int activeIndex = swipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1
                     : mNumActiveViews - 2;
 
-            View v = getViewAtRelativeIndex(activeIndex);
-            if (v != null) {
-                mHighlight.setImageBitmap(createOutline(v));
-                mHighlight.bringToFront();
-                v.bringToFront();
-                mStackSlider.setView(v);
-                if (mSwipeGestureType == GESTURE_SLIDE_DOWN)
-                    v.setVisibility(VISIBLE);
+            if (mAdapter == null) return;
+
+            if (mCurrentWindowStartUnbounded + activeIndex == 0) {
+                mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
+            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
+                activeIndex--;
+                mStackSlider.setMode(StackSlider.END_OF_STACK_MODE);
+            } else {
+                mStackSlider.setMode(StackSlider.NORMAL_MODE);
             }
+
+            View v = getViewAtRelativeIndex(activeIndex);
+            if (v == null) return;
+
+            mHighlight.setImageBitmap(createOutline(v));
+            mHighlight.bringToFront();
+            v.bringToFront();
+            mStackSlider.setView(v);
+
+            if (swipeGestureType == GESTURE_SLIDE_DOWN)
+                v.setVisibility(VISIBLE);
+
+            // We only register this gesture if we've made it this far without a problem
+            mSwipeGestureType = swipeGestureType;
         }
     }
 
@@ -339,7 +356,7 @@
             case MotionEvent.ACTION_MOVE: {
                 beginGestureIfNeeded(deltaY);
 
-                float rx = 0.3f*deltaX/(mViewHeight*1.0f);
+                float rx = deltaX/(mViewHeight*1.0f);
                 if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
                     float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f;
                     mStackSlider.setYProgress(1 - r);
@@ -351,7 +368,6 @@
                     mStackSlider.setXProgress(rx);
                     return true;
                 }
-
                 break;
             }
             case MotionEvent.ACTION_UP: {
@@ -412,7 +428,7 @@
                 }
             }
             // if we made it this far, it means we didn't find a satisfactory new pointer :(,
-            // so end the
+            // so end the gesture
             handlePointerUp(ev);
         }
     }
@@ -422,25 +438,30 @@
         float newY = ev.getY(pointerIndex);
         int deltaY = (int) (newY - mInitialY);
 
-        mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-        mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+        if (mVelocityTracker != null) {
+            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+        }
 
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
 
-        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN) {
+        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
+                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
             // Swipe threshold exceeded, swipe down
             showNext();
             mHighlight.bringToFront();
-        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP) {
+        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
+                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
             // Swipe threshold exceeded, swipe up
             showPrevious();
             mHighlight.bringToFront();
         } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
             // Didn't swipe up far enough, snap back down
-            int duration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION);
+            int duration =
+                Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
 
             StackSlider animationSlider = new StackSlider(mStackSlider);
             PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
@@ -453,8 +474,9 @@
             snapBackX.start();
         } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
             // Didn't swipe down far enough, snap back up
-            int duration = Math.round((1 -
-                    mStackSlider.getYProgress())*DEFAULT_ANIMATION_DURATION);
+            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
+                    DEFAULT_ANIMATION_DURATION);
+
             StackSlider animationSlider = new StackSlider(mStackSlider);
             PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
                     "YProgress", mStackSlider.getYProgress(), 1);
@@ -475,6 +497,12 @@
         float mYProgress;
         float mXProgress;
 
+        static final int NORMAL_MODE = 0;
+        static final int BEGINNING_OF_STACK_MODE = 1;
+        static final int END_OF_STACK_MODE = 2;
+
+        int mMode = NORMAL_MODE;
+
         public StackSlider() {
         }
 
@@ -482,6 +510,7 @@
             mView = copy.mView;
             mYProgress = copy.mYProgress;
             mXProgress = copy.mXProgress;
+            mMode = copy.mMode;
         }
 
         private float cubic(float r) {
@@ -525,43 +554,85 @@
             r = Math.max(0, r);
 
             mYProgress = r;
-
             final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
             final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
 
-            viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
-            highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
-            mHighlight.setAlpha(highlightAlphaInterpolator(r));
+            switch (mMode) {
+                case NORMAL_MODE:
+                    viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
+                    highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
+                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
 
-            float alpha = viewAlphaInterpolator(1-r);
+                    float alpha = viewAlphaInterpolator(1-r);
 
-            // We make sure that views which can't be seen (have 0 alpha) are also invisible
-            // so that they don't interfere with click events.
-            if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
-                mView.setVisibility(VISIBLE);
-            } else if (alpha == 0 && mView.getAlpha() != 0 && mView.getVisibility() == VISIBLE) {
-                mView.setVisibility(INVISIBLE);
+                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
+                    // so that they don't interfere with click events.
+                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
+                        mView.setVisibility(VISIBLE);
+                    } else if (alpha == 0 && mView.getAlpha() != 0
+                            && mView.getVisibility() == VISIBLE) {
+                        mView.setVisibility(INVISIBLE);
+                    }
+
+                    mView.setAlpha(alpha);
+                    mView.setRotationX(90.0f*rotationInterpolator(r));
+                    mHighlight.setRotationX(90.0f*rotationInterpolator(r));
+                    break;
+                case BEGINNING_OF_STACK_MODE:
+                    r = r*0.2f;
+                    viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
+                    highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
+                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
+                    break;
+                case END_OF_STACK_MODE:
+                    r = (1-r)*0.2f;
+                    viewLp.setVerticalOffset(Math.round(r*mViewHeight));
+                    highlightLp.setVerticalOffset(Math.round(r*mViewHeight));
+                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
+                    break;
             }
-
-            mView.setAlpha(viewAlphaInterpolator(1-r));
-            mView.setRotationX(90.0f*rotationInterpolator(r));
-            mHighlight.setRotationX(90.0f*rotationInterpolator(r));
         }
 
         public void setXProgress(float r) {
             // enforce r between 0 and 1
-            r = Math.min(1.0f, r);
-            r = Math.max(-1.0f, r);
+            r = Math.min(2.0f, r);
+            r = Math.max(-2.0f, r);
 
             mXProgress = r;
 
             final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
             final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
 
+            r *= 0.2f;
             viewLp.setHorizontalOffset(Math.round(r*mViewHeight));
             highlightLp.setHorizontalOffset(Math.round(r*mViewHeight));
         }
 
+        void setMode(int mode) {
+            mMode = mode;
+        }
+
+        float getDurationForNeutralPosition() {
+            return getDuration(false);
+        }
+
+        float getDurationForOffscreenPosition() {
+            return getDuration(mMode == END_OF_STACK_MODE ? false : true);
+        }
+
+        private float getDuration(boolean invert) {
+            if (mView != null) {
+                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
+
+                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset,2) +
+                        Math.pow(viewLp.verticalOffset,2));
+                float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) +
+                        Math.pow(0.4f*mViewHeight, 2));
+                return invert ? (1-d/maxd) : d/maxd;
+            }
+            return 0;
+        }
+
         float getYProgress() {
             return mYProgress;
         }
@@ -569,6 +640,7 @@
         float getXProgress() {
             return mXProgress;
         }
+
     }
 
     @Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 825de25..25c5b24 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -195,8 +195,9 @@
     static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
     
-    private static int PRIORITY = 100;
+    private static final int PRIORITY = 100;
 
+    private int mCurrentAlpha = 255;    
     private ColorStateList mTextColor;
     private int mCurTextColor;
     private ColorStateList mHintTextColor;
@@ -3857,6 +3858,22 @@
     }
 
     @Override
+    protected boolean onSetAlpha(int alpha) {
+        if (mMovement == null && getBackground() == null) {
+            mCurrentAlpha = alpha;
+            final Drawables dr = mDrawables;
+            if (dr != null) {
+                if (dr.mDrawableLeft != null) dr.mDrawableLeft.setAlpha(alpha);
+                if (dr.mDrawableTop != null) dr.mDrawableTop.setAlpha(alpha);
+                if (dr.mDrawableRight != null) dr.mDrawableRight.setAlpha(alpha);
+                if (dr.mDrawableBottom != null) dr.mDrawableBottom.setAlpha(alpha);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         restartMarqueeIfNeeded();
 
@@ -3953,6 +3970,7 @@
         }
 
         mTextPaint.setColor(color);
+        mTextPaint.setAlpha(mCurrentAlpha);
         mTextPaint.drawableState = getDrawableState();
 
         canvas.save();
@@ -6566,7 +6584,7 @@
             onEndBatchEdit();
 
             hideInsertionPointCursorController();
-            stopSelectionActionMode();
+            terminateSelectionActionMode();
         }
 
         startStopMarquee(focused);
@@ -6662,7 +6680,7 @@
                         return;
                     } else {
                         // Tapping outside stops selection mode, if any
-                        finishSelectionActionMode();
+                        stopSelectionActionMode();
                     }
                 }
 
@@ -6764,9 +6782,9 @@
                 mSelectionModifierCursorController = new SelectionModifierCursorController();
             }
         } else {
-            mSelectionModifierCursorController = null;
             // Stop selection mode if the controller becomes unavailable.
-            finishSelectionActionMode();
+            stopSelectionActionMode();
+            mSelectionModifierCursorController = null;
         }
     }
 
@@ -7011,7 +7029,7 @@
     }
 
     private boolean canSelectAll() {
-        return canSelectText();
+        return canSelectText() && mText.length() != 0;
     }
 
     private boolean canSelectText() {
@@ -7019,7 +7037,6 @@
         // If you change this condition, make sure prepareCursorController is called anywhere
         // the value of this condition might be changed.
         return (mText instanceof Spannable &&
-                mText.length() != 0 &&
                 mMovement != null &&
                 mMovement.canSelectArbitrarily());
     }
@@ -7370,13 +7387,13 @@
     }
 
     /**
-     * Same as {@link #finishSelectionActionMode()}, except that there is no cursor controller
+     * Same as {@link #stopSelectionActionMode()}, except that there is no cursor controller
      * fade out animation. Needed since the drawable and their alpha values are shared by all
      * TextViews. Switching from one TextView to another would fade the cursor controllers in the
      * new one otherwise.
      */
-    private void stopSelectionActionMode() {
-        finishSelectionActionMode();
+    private void terminateSelectionActionMode() {
+        stopSelectionActionMode();
         if (mSelectionModifierCursorController != null) {
             SelectionModifierCursorController selectionModifierCursorController =
                 (SelectionModifierCursorController) mSelectionModifierCursorController;
@@ -7384,7 +7401,7 @@
         }
     }
 
-    private void finishSelectionActionMode() {
+    private void stopSelectionActionMode() {
         if (mSelectionActionMode != null) {
             mSelectionActionMode.finish();
         }
@@ -7526,7 +7543,7 @@
                                 }
                             }
                         }
-                        finishSelectionActionMode();
+                        stopSelectionActionMode();
                     }
 
                     return true;
@@ -7535,13 +7552,13 @@
                     clipboard.setPrimaryClip(new ClippedData(null, null,
                             new ClippedData.Item(mTransformed.subSequence(min, max))));
                     ((Editable) mText).delete(min, max);
-                    finishSelectionActionMode();
+                    stopSelectionActionMode();
                     return true;
 
                 case ID_COPY:
                     clipboard.setPrimaryClip(new ClippedData(null, null,
                             new ClippedData.Item(mTransformed.subSequence(min, max))));
-                    finishSelectionActionMode();
+                    stopSelectionActionMode();
                     return true;
             }
 
@@ -8040,7 +8057,7 @@
 
     private void hideControllers() {
         hideInsertionPointCursorController();
-        finishSelectionActionMode();
+        stopSelectionActionMode();
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/CarouselRS.java b/core/java/com/android/internal/widget/CarouselRS.java
new file mode 100644
index 0000000..7589531
--- /dev/null
+++ b/core/java/com/android/internal/widget/CarouselRS.java
@@ -0,0 +1,372 @@
+/*
+ * 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 com.android.internal.widget;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.renderscript.*;
+import android.renderscript.RenderScript.RSMessage;
+import android.renderscript.Sampler.Value;
+import android.renderscript.ProgramRaster.CullMode;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import static android.renderscript.Element.*;
+import static android.renderscript.Sampler.Value.LINEAR;
+import static android.renderscript.Sampler.Value.WRAP;
+import static android.renderscript.Sampler.Value.CLAMP;
+
+public class CarouselRS  {
+    private static final int DEFAULT_VISIBLE_SLOTS = 1;
+    private static final int DEFAULT_CARD_COUNT = 1;
+    
+    // Client messages *** THIS LIST MUST MATCH THOSE IN carousel.rs ***
+    public static final int CMD_CARD_SELECTED = 100;
+    public static final int CMD_REQUEST_TEXTURE = 200;
+    public static final int CMD_INVALIDATE_TEXTURE = 210;
+    public static final int CMD_REQUEST_GEOMETRY = 300;
+    public static final int CMD_INVALIDATE_GEOMETRY = 310;
+    public static final int CMD_ANIMATION_STARTED = 400;
+    public static final int CMD_ANIMATION_FINISHED = 500;
+    public static final int CMD_PING = 600; // for debugging
+    
+    private static final String TAG = "CarouselRS";
+    private static final int DEFAULT_SLOT_COUNT = 10;
+    private static final boolean MIPMAP = false;
+    
+    private RenderScriptGL mRS;
+    private Resources mRes;
+    private ScriptC_Carousel mScript;
+    private ScriptField_Card mCards;
+    private Sampler mSampler;
+    private ProgramRaster mProgramRaster;
+    private ProgramStore mProgramStore;
+    private ProgramFragment mFragmentProgram;
+    private ProgramVertex mVertexProgram;
+    private ProgramRaster mRasterProgram;
+    private CarouselCallback mCallback;
+    private float[] mEyePoint = new float[3];
+    private float[] mAtPoint = new float[3];
+    private float[] mUp = new float[3];
+    
+    public static interface CarouselCallback {
+        /**
+         * Called when a card is selected
+         * @param n the id of the card
+         */
+        void onCardSelected(int n);
+        
+        /**
+         * Called when texture is needed for card n.  This happens when the given card becomes
+         * visible.
+         * @param n the id of the card
+         */
+        void onRequestTexture(int n);
+        
+        /**
+         * Called when a texture is no longer needed for card n.  This happens when the card
+         * goes out of view.
+         * @param n the id of the card
+         */
+        void onInvalidateTexture(int n);
+        
+        /**
+         * Called when geometry is needed for card n.
+         * @param n the id of the card.
+         */
+        void onRequestGeometry(int n);
+        
+        /**
+         * Called when geometry is no longer needed for card n. This happens when the card goes 
+         * out of view.
+         * @param n the id of the card
+         */
+        void onInvalidateGeometry(int n);
+        
+        /**
+         * Called when card animation (e.g. a fling) has started.
+         */
+        void onAnimationStarted();
+        
+        /**
+         * Called when card animation has stopped.
+         */
+        void onAnimationFinished();
+    };
+    
+    private RSMessage mRsMessage = new RSMessage() {
+        public void run() {
+            if (mCallback == null) return;
+            switch (mID) {
+                case CMD_CARD_SELECTED:
+                    mCallback.onCardSelected(mData[0]);
+                    break;
+                    
+                case CMD_REQUEST_TEXTURE:
+                    mCallback.onRequestTexture(mData[0]);
+                    break;
+                   
+                case CMD_INVALIDATE_TEXTURE:
+                    mCallback.onInvalidateTexture(mData[0]);
+                    break;
+                    
+                case CMD_REQUEST_GEOMETRY:
+                    mCallback.onRequestGeometry(mData[0]);
+                    break;
+                    
+                case CMD_INVALIDATE_GEOMETRY:
+                    mCallback.onInvalidateGeometry(mData[0]);
+                    break;
+                    
+                case CMD_ANIMATION_STARTED:
+                    mCallback.onAnimationStarted();
+                    break;
+                    
+                case CMD_ANIMATION_FINISHED:
+                    mCallback.onAnimationFinished();
+                    break;
+                    
+                case CMD_PING:
+                    Log.v(TAG, "PING...");
+                    break;
+                    
+                default:
+                    Log.e(TAG, "Unknown RSMessage: " + mID);
+            }
+        }
+    };
+    
+    public void init(RenderScriptGL rs, Resources res) {
+        mRS = rs;
+        mRes = res;
+
+        // create the script object
+        mScript = new ScriptC_Carousel(mRS, mRes, R.raw.carousel, true);
+        mRS.mMessageCallback = mRsMessage;
+
+        initProgramStore();
+        initFragmentProgram();
+        initRasterProgram();
+        initVertexProgram();
+        
+        setSlotCount(DEFAULT_SLOT_COUNT);
+        setVisibleSlots(DEFAULT_VISIBLE_SLOTS);
+        createCards(DEFAULT_CARD_COUNT);
+        
+        setStartAngle(0.0f);
+        setRadius(1.0f);
+        
+        // update the camera
+        boolean pcam = true;
+        if (pcam) {
+            float eye[] = { 20.6829f, 2.77081f, 16.7314f };
+            float at[] = { 14.7255f, -3.40001f, -1.30184f };
+            float up[] = { 0.0f, 1.0f, 0.0f };
+            setLookAt(eye, at, up);
+            setRadius(20.0f);
+            // Fov: 25
+        } else {
+            mScript.invoke_lookAt(2.5f, 2.0f, 2.5f, 0.0f, -0.75f, 0.0f,  0.0f, 1.0f, 0.0f);
+            mScript.set_cardRotation(0.0f);
+            setRadius(1.5f);
+        }
+
+        resumeRendering();
+    }
+
+    public void setLookAt(float[] eye, float[] at, float[] up) {
+        for (int i = 0; i < 3; i++) {
+            mEyePoint[i] = eye[i];
+            mAtPoint[i] = at[i];
+            mUp[i] = up[i];
+        }
+        mScript.invoke_lookAt(eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]);
+    }
+
+    public void setRadius(float radius) {
+        mScript.set_radius(radius);
+    }
+
+    private void initVertexProgram() {
+        ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
+        mVertexProgram = pvb.create();
+        ProgramVertex.MatrixAllocation pva = new ProgramVertex.MatrixAllocation(mRS);
+        mVertexProgram.bindAllocation(pva);
+        pva.setupProjectionNormalized(1, 1);
+        mScript.set_vertexProgram(mVertexProgram);
+    }
+
+    private void initRasterProgram() {
+        ProgramRaster.Builder programRasterBuilder = new ProgramRaster.Builder(mRS);
+        mRasterProgram = programRasterBuilder.create();
+        //mRasterProgram.setCullMode(CullMode.NONE);
+        mScript.set_rasterProgram(mRasterProgram);
+    }
+
+    private void initFragmentProgram() {
+        Sampler.Builder sampleBuilder = new Sampler.Builder(mRS);
+        sampleBuilder.setMin(Value.LINEAR_MIP_LINEAR);
+        sampleBuilder.setMag(LINEAR);
+        sampleBuilder.setWrapS(CLAMP);
+        sampleBuilder.setWrapT(CLAMP);
+        mSampler = sampleBuilder.create();
+        ProgramFragment.Builder fragmentBuilder = new ProgramFragment.Builder(mRS);
+        fragmentBuilder.setTexture(ProgramFragment.Builder.EnvMode.DECAL,
+                           ProgramFragment.Builder.Format.RGBA, 0);
+        mFragmentProgram = fragmentBuilder.create();
+        mFragmentProgram.bindSampler(mSampler, 0);
+        mScript.set_fragmentProgram(mFragmentProgram);
+    }
+
+    private void initProgramStore() {
+        ProgramStore.Builder programStoreBuilder = new ProgramStore.Builder(mRS, null, null);
+        programStoreBuilder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+        programStoreBuilder.setBlendFunc(ProgramStore.BlendSrcFunc.SRC_ALPHA, 
+                ProgramStore.BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+        programStoreBuilder.setDitherEnable(false);
+        programStoreBuilder.setDepthMask(true);
+        mProgramStore = programStoreBuilder.create();
+        mScript.set_programStore(mProgramStore);
+    }
+    
+    public void createCards(int count)
+    {
+        mCards = count > 0 ? new ScriptField_Card(mRS, count) : null;
+        mScript.bind_cards(mCards);
+        mScript.invoke_createCards(count);
+    }
+    
+    public void setVisibleSlots(int count)
+    {
+        mScript.set_visibleSlotCount(count);
+    }
+    
+    public void setDefaultBitmap(Bitmap bitmap)
+    {
+        mScript.set_defaultTexture(allocationFromBitmap(bitmap, MIPMAP));
+    }
+    
+    public void setLoadingBitmap(Bitmap bitmap)
+    {
+        mScript.set_loadingTexture(allocationFromBitmap(bitmap, MIPMAP));
+    }
+    
+    public void setDefaultGeometry(Mesh mesh)
+    {
+        mScript.set_defaultGeometry(mesh);
+    }
+    
+    public void setLoadingGeometry(Mesh mesh)
+    {
+        mScript.set_loadingGeometry(mesh);
+    }
+    
+    public void setStartAngle(float theta)
+    {
+        mScript.set_startAngle(theta);
+    }
+    
+    public void setCallback(CarouselCallback callback)
+    {
+        mCallback = callback;
+    }
+    
+    private Allocation allocationFromBitmap(Bitmap bitmap, boolean mipmap)
+    {
+        if (bitmap == null) return null;
+        Allocation allocation = Allocation.createFromBitmap(mRS, bitmap, RGB_565(mRS), mipmap);
+        allocation.uploadToTexture(0);
+        return allocation;
+    }
+    
+    public void setTexture(int n, Bitmap bitmap)
+    {
+        ScriptField_Card.Item item = mCards.get(n);
+        if (item == null) {
+            Log.v(TAG, "setTexture(): no item at index " + n);
+            item = new ScriptField_Card.Item();
+        }
+        if (bitmap != null) {
+            Log.v(TAG, "creating new bitmap");
+            item.texture = Allocation.createFromBitmap(mRS, bitmap, RGB_565(mRS), MIPMAP);
+            Log.v(TAG, "uploadToTexture(" + n + ")");
+            item.texture.uploadToTexture(0);
+            Log.v(TAG, "done...");
+        } else {
+            if (item.texture != null) {
+                Log.v(TAG, "unloading texture " + n);
+                // Don't wait for GC to free native memory.
+                // Only works if textures are not shared.
+                item.texture.destroy(); 
+                item.texture = null;
+            }
+        }
+        mCards.set(item, n, false); // This is primarily used for reference counting.
+        mScript.invoke_setTexture(n, item.texture);
+    }
+    
+    public void setGeometry(int n, Mesh geometry)
+    {
+        final boolean mipmap = false;
+        ScriptField_Card.Item item = mCards.get(n);
+        if (item == null) {
+            Log.v(TAG, "setGeometry(): no item at index " + n);
+            item = new ScriptField_Card.Item();
+        }
+        if (geometry != null) {
+            item.geometry = geometry;
+        } else {
+            Log.v(TAG, "unloading geometry " + n);
+            if (item.geometry != null) {
+                // item.geometry.destroy(); 
+                item.geometry = null;
+            }
+        }
+        mCards.set(item, n, false);
+        mScript.invoke_setGeometry(n, item.geometry);
+    }
+
+    public void pauseRendering() {
+        // Used to update multiple states at once w/o redrawing for each.
+        mRS.contextBindRootScript(null);
+    }
+    
+    public void resumeRendering() {
+        mRS.contextBindRootScript(mScript);
+    }
+    
+    public void doMotion(float x, float y) {
+        mScript.invoke_doMotion(x,y);
+    }
+    
+    public void doSelection(float x, float y) {
+        mScript.invoke_doSelection(x, y);
+    }
+
+    public void doStart(float x, float y) {
+        mScript.invoke_doStart(x, y);
+    }
+
+    public void doStop(float x, float y) {
+        mScript.invoke_doStop(x, y);
+    }
+
+    public void setSlotCount(int n) {
+        mScript.set_slotCount(n);
+    }
+}
diff --git a/core/java/com/android/internal/widget/CarouselView.java b/core/java/com/android/internal/widget/CarouselView.java
new file mode 100644
index 0000000..e0c65dc
--- /dev/null
+++ b/core/java/com/android/internal/widget/CarouselView.java
@@ -0,0 +1,244 @@
+/*
+ * 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 com.android.internal.widget;
+
+import com.android.internal.widget.CarouselRS.CarouselCallback;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.renderscript.FileA3D;
+import android.renderscript.Mesh;
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScriptGL;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+
+public class CarouselView extends RSSurfaceView {
+    private final int DEFAULT_SLOT_COUNT = 10;
+    private final Bitmap DEFAULT_BITMAP = Bitmap.createBitmap(1, 1, Config.RGB_565);
+    private static final String TAG = "CarouselView";
+    private CarouselRS mRenderScript;
+    private RenderScriptGL mRS;
+    private Context mContext;
+    private boolean mTracking;
+    private Bitmap mDefaultBitmap;
+    private Bitmap mLoadingBitmap;
+    private Mesh mDefaultGeometry;
+    private Mesh mLoadingGeometry;
+    private int mCardCount = 0;
+    private int mVisibleSlots = 0;
+    private float mStartAngle;
+    private int mSlotCount = DEFAULT_SLOT_COUNT;
+    
+    public CarouselView(Context context) {
+        super(context);
+        mContext = context;
+        boolean useDepthBuffer = true;
+        mRS = createRenderScript(useDepthBuffer);
+        mRenderScript = new CarouselRS();
+        mRenderScript.init(mRS, getResources());
+    }
+    
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        super.surfaceChanged(holder, format, w, h);
+        mRS.contextSetSurface(w, h, holder.getSurface());
+        mRenderScript.init(mRS, getResources());
+        setSlotCount(mSlotCount);
+        createCards(mCardCount);
+        setVisibleSlots(mVisibleSlots);
+        setCallback(mCarouselCallback);
+        setDefaultBitmap(mDefaultBitmap);
+        setLoadingBitmap(mLoadingBitmap);
+        setDefaultGeometry(mDefaultGeometry);
+        setLoadingGeometry(mLoadingGeometry);
+        setStartAngle(mStartAngle);
+    }
+
+    /**
+     * Loads geometry from a resource id.
+     * 
+     * @param resId
+     * @return the loaded mesh or null if it cannot be loaded
+     */
+    public Mesh loadGeometry(int resId) {
+        Resources res = mContext.getResources();
+        FileA3D model = FileA3D.createFromResource(mRS, res, resId);
+        FileA3D.IndexEntry entry = model.getIndexEntry(0);
+        if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
+            return null;
+        }
+        return (Mesh) entry.getObject();
+    }
+    
+    /**
+     * Load A3D file from resource.  If resId == 0, will clear geometry for this item.
+     * @param n
+     * @param resId
+     */
+    public void setGeometryForItem(int n, Mesh mesh) {
+        mRenderScript.setGeometry(n, mesh);
+    }
+    
+    public void setSlotCount(int n) {
+        mSlotCount = n;
+        if (mRenderScript != null) {
+            mRenderScript.setSlotCount(n);
+        }
+    }
+
+    public void setVisibleSlots(int n) {
+        mVisibleSlots = n;
+        if (mRenderScript != null) {
+            mRenderScript.setVisibleSlots(n);
+        }
+    }
+
+    public void createCards(int n) {
+        mCardCount = n;
+        if (mRenderScript != null) {
+            mRenderScript.createCards(n);
+        }
+    }
+    
+    public void setTextureForItem(int n, Bitmap bitmap) {
+        if (mRenderScript != null) {
+            Log.v(TAG, "setTextureForItem(" + n + ")");
+            mRenderScript.setTexture(n, bitmap); 
+            Log.v(TAG, "done");
+        }
+    }
+
+    public void setDefaultBitmap(Bitmap bitmap) {
+        mDefaultBitmap = bitmap; 
+        if (mRenderScript != null) {
+            mRenderScript.setDefaultBitmap(bitmap);
+        }
+    }
+    
+    public void setLoadingBitmap(Bitmap bitmap) {
+        mLoadingBitmap = bitmap;
+        if (mRenderScript != null) {
+            mRenderScript.setLoadingBitmap(bitmap);
+        }
+    }
+    
+    public void setDefaultGeometry(Mesh mesh) {
+        mDefaultGeometry = mesh;
+        if (mRenderScript != null) {
+            mRenderScript.setDefaultGeometry(mesh);
+        }
+    }
+    
+    public void setLoadingGeometry(Mesh mesh) {
+        mLoadingGeometry = mesh;
+        if (mRenderScript != null) {
+            mRenderScript.setLoadingGeometry(mesh);
+        }
+    }
+    
+    public void setCallback(CarouselCallback callback)
+    {
+        mCarouselCallback = callback;
+        if (mRenderScript != null) {
+            mRenderScript.setCallback(callback);
+        }
+    }
+
+    public void setStartAngle(float angle)
+    {
+        mStartAngle = angle;
+        if (mRenderScript != null) {
+            mRenderScript.setStartAngle(angle);
+        }
+    }
+    
+    @Override
+    protected void onDetachedFromWindow() {
+        if(mRS != null) {
+            mRS = null;
+            destroyRenderScript();
+        }
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int action = event.getAction();
+        final float x = event.getX();
+        final float y = event.getY();
+        
+        if (mRenderScript == null) {
+            return true;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mTracking = true;
+                mRenderScript.doStart(x, y);
+                break;
+                
+            case MotionEvent.ACTION_MOVE:
+                if (mTracking) {
+                    mRenderScript.doMotion(x, y);
+                }
+                break;
+                
+            case MotionEvent.ACTION_UP:
+                mRenderScript.doStop(x, y);
+                mTracking = false;
+                break;
+        }
+
+        return true;
+    }
+    
+    private final CarouselCallback DEBUG_CALLBACK = new CarouselCallback() {
+        public void onAnimationStarted() {
+            Log.v(TAG, "onAnimationStarted()");
+        }
+        
+        public void onAnimationFinished() {
+            Log.v(TAG, "onAnimationFinished()");
+        }
+
+        public void onCardSelected(int n) {
+            Log.v(TAG, "onCardSelected(" + n + ")");
+        }
+
+        public void onRequestGeometry(int n) {
+            Log.v(TAG, "onRequestGeometry(" + n + ")");
+        }
+
+        public void onInvalidateGeometry(int n) {
+            Log.v(TAG, "onInvalidateGeometry(" + n + ")");
+        }
+        
+        public void onRequestTexture(final int n) {
+            Log.v(TAG, "onRequestTexture(" + n + ")");
+        }
+
+        public void onInvalidateTexture(int n) {
+            Log.v(TAG, "onInvalidateTexture(" + n + ")");
+        }
+
+    };
+    
+    private CarouselCallback mCarouselCallback = DEBUG_CALLBACK;
+}
diff --git a/core/java/com/android/internal/widget/carousel.rs b/core/java/com/android/internal/widget/carousel.rs
new file mode 100644
index 0000000..4cfcbf4
--- /dev/null
+++ b/core/java/com/android/internal/widget/carousel.rs
@@ -0,0 +1,753 @@
+/*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.internal.widget)
+#pragma rs set_reflect_license()
+
+#include "rs_graphics.rsh"
+
+typedef struct __attribute__((aligned(4))) Card {
+    rs_allocation texture;
+    rs_mesh geometry;
+    //rs_matrix4x4 matrix; // custom transform for this card/geometry
+    int textureState;  // whether or not the texture is loaded.
+    int geometryState; // whether or not geometry is loaded
+    int visible; // not bool because of packing bug?
+} Card_t;
+
+typedef struct Ray_s {
+    float3 position;
+    float3 direction;
+} Ray;
+
+typedef struct PerspectiveCamera_s {
+    float3 from;
+    float3 at;
+    float3 up;
+    float  fov;
+    float  aspect;
+    float  near;
+    float  far;
+} PerspectiveCamera;
+
+// Request states. Used for loading 3D object properties from the Java client.
+// Typical properties: texture, geometry and matrices.
+enum {
+    STATE_INVALID = 0, // item hasn't been loaded
+    STATE_LOADING, // we've requested an item but are waiting for it to load
+    STATE_LOADED // item was delivered
+};
+
+// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
+const int CMD_CARD_SELECTED = 100;
+const int CMD_REQUEST_TEXTURE = 200;
+const int CMD_INVALIDATE_TEXTURE = 210;
+const int CMD_REQUEST_GEOMETRY = 300;
+const int CMD_INVALIDATE_GEOMETRY = 310;
+const int CMD_ANIMATION_STARTED = 400;
+const int CMD_ANIMATION_FINISHED = 500;
+const int CMD_PING = 600;
+
+
+// Debug flags
+bool debugCamera = false;
+bool debugPicking = false;
+
+// Exported variables. These will be reflected to Java set_* variables.
+Card_t *cards; // array of cards to draw
+float startAngle; // position of initial card, in radians
+int slotCount; // number of positions where a card can be
+int cardCount; // number of cards in stack
+int visibleSlotCount; // number of visible slots (for culling)
+float radius; // carousel radius. Cards will be centered on a circle with this radius
+float cardRotation; // rotation of card in XY plane relative to Z=1
+rs_program_store programStore;
+rs_program_fragment fragmentProgram;
+rs_program_vertex vertexProgram;
+rs_program_raster rasterProgram;
+rs_allocation defaultTexture; // shown when no other texture is assigned
+rs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
+rs_mesh defaultGeometry; // shown when no geometry is loaded
+rs_mesh loadingGeometry; // shown when geometry is loading
+rs_matrix4x4 projectionMatrix;
+rs_matrix4x4 modelviewMatrix;
+
+#pragma rs export_var(radius, cards, slotCount, visibleSlotCount, cardRotation)
+#pragma rs export_var(programStore, fragmentProgram, vertexProgram, rasterProgram)
+#pragma rs export_var(startAngle, defaultTexture, loadingTexture, defaultGeometry, loadingGeometry)
+#pragma rs export_func(createCards, lookAt, doStart, doStop, doMotion, doSelection, setTexture)
+#pragma rs export_func(setGeometry, debugCamera, debugPicking)
+
+// Local variables
+static float bias; // rotation bias, in radians. Used for animation and dragging.
+static bool updateCamera;    // force a recompute of projection and lookat matrices
+static bool initialized;
+static float3 backgroundColor = { 0.5f, 0.5f, 0.5f };
+static const float FLT_MAX = 1.0e37;
+
+// Default geometry when card.geometry is not set.
+static const float3 cardVertices[4] = {
+        { -1.0, -1.0, 0.0 },
+        { 1.0, -1.0, 0.0 },
+        { 1.0, 1.0, 0.0 },
+        {-1.0, 1.0, 0.0 }
+};
+
+// Default camera
+static PerspectiveCamera camera = {
+        {2,2,2}, // from
+        {0,0,0}, // at
+        {0,1,0}, // up
+        25.0f,   // field of view
+        1.0f,    // aspect
+        0.1f,    // near
+        100.0f   // far
+};
+
+// Forward references
+static int intersectGeometry(Ray* ray, float *bestTime);
+static bool makeRayForPixelAt(Ray* ray, float x, float y);
+
+void init() {
+    // initializers currently have a problem when the variables are exported, so initialize
+    // globals here.
+    startAngle = 0.0f;
+    slotCount = 10;
+    visibleSlotCount = 1;
+    bias = 0.0f;
+    radius = 1.0f;
+    cardRotation = 0.0f;
+    updateCamera = true;
+    initialized = false;
+}
+
+static void updateAllocationVars()
+{
+    // Cards
+    rs_allocation cardAlloc = rsGetAllocation(cards);
+    // TODO: use new rsIsObject()
+    cardCount = cardAlloc.p != 0 ? rsAllocationGetDimX(cardAlloc) : 0;
+}
+
+void createCards(int n)
+{
+    rsDebug("CreateCards: ", n);
+    initialized = false;
+    updateAllocationVars();
+}
+
+// Return angle for position p. Typically p will be an integer position, but can be fractional.
+static float cardPosition(float p)
+{
+    return startAngle + bias + 2.0f * M_PI * p / slotCount;
+}
+
+// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional.
+static float slotPosition(float p)
+{
+    return startAngle + 2.0f * M_PI * p / slotCount;
+}
+
+// Return the lowest slot number for a given angular position.
+static int cardIndex(float angle)
+{
+    return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI);
+}
+
+// Set basic camera properties:
+//    from - position of the camera in x,y,z
+//    at - target we're looking at - used to compute view direction
+//    up - a normalized vector indicating up (typically { 0, 1, 0})
+//
+// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
+void lookAt(float fromX, float fromY, float fromZ,
+        float atX, float atY, float atZ,
+        float upX, float upY, float upZ)
+{
+    camera.from.x = fromX;
+    camera.from.y = fromY;
+    camera.from.z = fromZ;
+    camera.at.x = atX;
+    camera.at.y = atY;
+    camera.at.z = atZ;
+    camera.up.x = upX;
+    camera.up.y = upY;
+    camera.up.z = upZ;
+    updateCamera = true;
+}
+
+// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
+static void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
+{
+    rsMatrixLoadIdentity(matrix);
+    float top = near * tan((float) (fovy * M_PI / 360.0f));
+    float bottom = -top;
+    float left = bottom * aspect;
+    float right = top * aspect;
+    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
+}
+
+// Construct a matrix based on eye point, center and up direction. Based on the
+// man page for gluLookat(). Up must be normalized.
+static void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
+{
+    float3 f = normalize(center - eye);
+    float3 s = normalize(cross(f, up));
+    float3 u = cross(s, f);
+    float m[16];
+    m[0] = s.x;
+    m[4] = s.y;
+    m[8] = s.z;
+    m[12] = 0.0f;
+    m[1] = u.x;
+    m[5] = u.y;
+    m[9] = u.z;
+    m[13] = 0.0f;
+    m[2] = -f.x;
+    m[6] = -f.y;
+    m[10] = -f.z;
+    m[14] = 0.0f;
+    m[3] = m[7] = m[11] = 0.0f;
+    m[15] = 1.0f;
+    rsMatrixLoad(matrix, m);
+    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
+}
+
+void setTexture(int n, rs_allocation texture)
+{
+    cards[n].texture = texture;
+    if (cards[n].texture.p != 0)
+        cards[n].textureState = STATE_LOADED;
+    else
+        cards[n].textureState = STATE_INVALID;
+}
+
+void setGeometry(int n, rs_mesh geometry)
+{
+    cards[n].geometry = geometry;
+    if (cards[n].geometry.p != 0)
+        cards[n].geometryState = STATE_LOADED;
+    else
+        cards[n].geometryState = STATE_INVALID;
+}
+
+static void getMatrixForCard(rs_matrix4x4* matrix, int i)
+{
+    float theta = cardPosition(i);
+    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
+    rsMatrixTranslate(matrix, radius, 0, 0);
+    rsMatrixRotate(matrix, degrees(-theta + cardRotation), 0, 1, 0);
+    // TODO: apply custom matrix for cards[i].geometry
+}
+
+static void drawCards()
+{
+    float depth = 1.0f;
+    for (int i = 0; i < cardCount; i++) {
+        if (cards[i].visible) {
+            // Bind texture
+            if (cards[i].textureState == STATE_LOADED) {
+                rsgBindTexture(fragmentProgram, 0, cards[i].texture);
+            } else if (cards[i].textureState == STATE_LOADING) {
+                rsgBindTexture(fragmentProgram, 0, loadingTexture);
+            } else {
+                rsgBindTexture(fragmentProgram, 0, defaultTexture);
+            }
+
+            // Draw geometry
+            rs_matrix4x4 matrix = modelviewMatrix;
+            getMatrixForCard(&matrix, i);
+            rsgProgramVertexLoadModelMatrix(&matrix);
+            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
+                rsgDrawMesh(cards[i].geometry);
+            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
+                rsgDrawMesh(loadingGeometry);
+            } else if (defaultGeometry.p != 0) {
+                rsgDrawMesh(defaultGeometry);
+            } else {
+                // Draw place-holder geometry
+                rsgDrawQuad(
+                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
+                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
+                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
+                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
+            }
+        }
+    }
+}
+
+static void updateCameraMatrix(float width, float height)
+{
+    float aspect = width / height;
+    if (aspect != camera.aspect || updateCamera) {
+        camera.aspect = aspect;
+        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
+        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
+
+        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
+        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
+        updateCamera = false;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Behavior/Physics
+////////////////////////////////////////////////////////////////////////////////////////////////////
+static float velocity = 0.0f;  // angular velocity in radians/s
+static bool isDragging;
+static int64_t lastTime = 0L; // keep track of how much time has passed between frames
+static float2 lastPosition;
+static bool animating = false;
+static float velocityThreshold = 0.1f * M_PI / 180.0f;
+static float velocityTracker;
+static int velocityTrackerCount;
+static float mass = 5.0f; // kg
+
+static const float G = 9.80f; // gravity constant, in m/s
+static const float springConstant = 0.0f;
+static const float frictionCoeff = 10.0f;
+static const float dragFactor = 0.25f;
+
+static float dragFunction(float x, float y)
+{
+    return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
+}
+
+static float deltaTimeInSeconds(int64_t current)
+{
+    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
+}
+
+int doSelection(float x, float y)
+{
+    Ray ray;
+    if (makeRayForPixelAt(&ray, x, y)) {
+        float bestTime = FLT_MAX;
+        return intersectGeometry(&ray, &bestTime);
+    }
+    return -1;
+}
+
+void doStart(float x, float y)
+{
+    lastPosition.x = x;
+    lastPosition.y = y;
+    velocity = 0.0f;
+    if (animating) {
+        rsSendToClient(CMD_ANIMATION_FINISHED);
+        animating = false;
+    }
+    velocityTracker = 0.0f;
+    velocityTrackerCount = 0;
+}
+
+
+void doStop(float x, float y)
+{
+    updateAllocationVars();
+
+    velocity = velocityTrackerCount > 0 ?
+            (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
+    if (fabs(velocity) > velocityThreshold) {
+        animating = true;
+        rsSendToClient(CMD_ANIMATION_STARTED);
+    } else {
+        const int selection = doSelection(x, y);  // velocity too small; treat as a tap
+        if (selection != -1) {
+            rsDebug("HIT!", selection);
+            int data[1];
+            data[0] = selection;
+            rsSendToClient(CMD_CARD_SELECTED, data, sizeof(data));
+        }
+    }
+    lastTime = rsUptimeMillis();
+}
+
+void doMotion(float x, float y)
+{
+    int64_t currentTime = rsUptimeMillis();
+    float deltaOmega = dragFunction(x, y);
+    bias += deltaOmega;
+    lastPosition.x = x;
+    lastPosition.y = y;
+    float dt = deltaTimeInSeconds(currentTime);
+    if (dt > 0.0f) {
+        float v = deltaOmega / dt;
+        //if ((velocityTracker > 0.0f) == (v > 0.0f)) {
+            velocityTracker += v;
+            velocityTrackerCount++;
+        //} else {
+        //    velocityTracker = v;
+        //    velocityTrackerCount = 1;
+        //}
+    }
+    lastTime = currentTime;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Hit detection using ray casting.
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool
+rayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float *tout)
+{
+    static const float tmin = 0.0f;
+
+    float3 e1 = p1 - p0;
+    float3 e2 = p2 - p0;
+    float3 s1 = cross(ray->direction, e2);
+
+    float div = dot(s1, e1);
+    if (div == 0.0f) return false;  // ray is parallel to plane.
+
+    float3 d = ray->position - p0;
+    float invDiv = 1.0f / div;
+
+    float u = dot(d, s1) * invDiv;
+    if (u < 0.0f || u > 1.0f) return false;
+
+    float3 s2 = cross(d, e1);
+    float v = dot(ray->direction, s2) * invDiv;
+    if ( v < 0.0f || (u+v) > 1.0f) return false;
+
+    float t = dot(e2, s2) * invDiv;
+    if (t < tmin || t > *tout)
+        return false;
+    *tout = t;
+    return true;
+}
+
+static bool makeRayForPixelAt(Ray* ray, float x, float y)
+{
+    if (debugCamera) {
+        rsDebug("------ makeRay() -------", 0);
+        rsDebug("Camera.from:", camera.from);
+        rsDebug("Camera.at:", camera.at);
+        rsDebug("Camera.dir:", normalize(camera.at - camera.from));
+    }
+
+    // Vector math.  This has the potential to be much faster.
+    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
+    if (true) {
+        const float u = x / rsgGetWidth();
+        const float v = (y / rsgGetHeight());
+        const float aspect = (float) rsgGetWidth() / rsgGetHeight();
+        const float tanfov2 = 2.0f * tan(radians(camera.fov / 2.0f));
+        float3 dir = normalize(camera.at - camera.from);
+        float3 du = tanfov2 * normalize(cross(dir, camera.up));
+        float3 dv = tanfov2 * normalize(cross(du, dir));
+        du *= aspect;
+        float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
+        const float3 rayPoint = camera.from;
+        const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
+        if (debugCamera) {
+            rsDebug("Ray direction (vector math) = ", rayDir);
+        }
+
+        ray->position =  rayPoint;
+        ray->direction = rayDir;
+    }
+
+    // Matrix math.  This is more generic if we allow setting model view and projection matrices
+    // directly
+    else {
+        rs_matrix4x4 pm = modelviewMatrix;
+        rsMatrixLoadMultiply(&pm, &projectionMatrix, &modelviewMatrix);
+        if (!rsMatrixInverse(&pm)) {
+            rsDebug("ERROR: SINGULAR PM MATRIX", 0);
+            return false;
+        }
+        const float width = rsgGetWidth();
+        const float height = rsgGetHeight();
+        const float winx = 2.0f * x / width - 1.0f;
+        const float winy = 2.0f * y / height - 1.0f;
+
+        float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
+        float4 at = { winx, winy, 1.0f, 1.0f };
+
+        eye = rsMatrixMultiply(&pm, eye);
+        eye *= 1.0f / eye.w;
+
+        at = rsMatrixMultiply(&pm, at);
+        at *= 1.0f / at.w;
+
+        const float3 rayPoint = { eye.x, eye.y, eye.z };
+        const float3 atPoint = { at.x, at.y, at.z };
+        const float3 rayDir = normalize(atPoint - rayPoint);
+        if (debugCamera) {
+            rsDebug("winx: ", winx);
+            rsDebug("winy: ", winy);
+            rsDebug("Ray position (transformed) = ", eye);
+            rsDebug("Ray direction (transformed) = ", rayDir);
+        }
+        ray->position =  rayPoint;
+        ray->direction = rayDir;
+    }
+
+    return true;
+}
+
+static int intersectGeometry(Ray* ray, float *bestTime)
+{
+    int hit = -1;
+    for (int id = 0; id < cardCount; id++) {
+        if (cards[id].visible) {
+            rs_matrix4x4 matrix;
+            float3 p[4];
+
+            // Transform card vertices to world space
+            rsMatrixLoadIdentity(&matrix);
+            getMatrixForCard(&matrix, id);
+            for (int vertex = 0; vertex < 4; vertex++) {
+                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
+                if (tmp.w != 0.0f) {
+                    p[vertex].x = tmp.x;
+                    p[vertex].y = tmp.y;
+                    p[vertex].z = tmp.z;
+                    p[vertex] *= 1.0f / tmp.w;
+                } else {
+                    rsDebug("Bad w coord: ", tmp);
+                }
+            }
+
+            // Intersect card geometry
+            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
+                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
+                hit = id;
+            }
+        }
+    }
+    return hit;
+}
+
+// This method computes the position of all the cards by updating bias based on a
+// simple physics model.
+// If the cards are still in motion, returns true.
+static bool updateNextPosition()
+{
+    int64_t currentTime = rsUptimeMillis();
+    if (animating) {
+        float dt = deltaTimeInSeconds(currentTime);
+        if (dt <= 0.0f)
+            return animating;
+        const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
+        const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
+        dt /= N;
+        for (int i = 0; i < N; i++) {
+            // Force friction - always opposes motion
+            const float Ff = -frictionCoeff * velocity;
+
+            // Restoring force to match cards with slots
+            const float theta = startAngle + bias;
+            const float dtheta = 2.0f * M_PI / slotCount;
+            const float position = theta / dtheta;
+            const float fraction = position - floor(position); // fractional position between slots
+            float x;
+            if (fraction > 0.5f) {
+                x = - (1.0f - fraction);
+            } else {
+                x = fraction;
+            }
+            const float Fr = - springConstant * x;
+
+            // compute velocity
+            const float momentum = mass * velocity + (Ff + Fr)*dt;
+            velocity = momentum / mass;
+            bias += velocity * dt;
+        }
+
+        // TODO: Add animation to smoothly move back to slots. Currently snaps to location.
+        if (cardCount <= visibleSlotCount) {
+            // TODO: this aligns the cards to the first slot (theta = startAngle) when there aren't
+            // enough visible cards. It should be generalized to allow alignment to front,
+            // middle or back of the stack.
+            if (cardPosition(0) != slotPosition(0)) {
+                bias = 0.0f;
+            }
+        } else {
+            if (cardPosition(cardCount) < 0.0f) {
+                bias = -slotPosition(cardCount);
+            } else if (cardPosition(0) > slotPosition(0)) {
+                bias = 0.0f;
+            }
+        }
+
+        animating = fabs(velocity) > velocityThreshold;
+        if (!animating) {
+            const float dtheta = 2.0f * M_PI / slotCount;
+            bias = round((startAngle + bias) / dtheta) * dtheta - startAngle;
+            rsSendToClient(CMD_ANIMATION_FINISHED);
+        }
+    }
+    lastTime = currentTime;
+
+    return animating;
+}
+
+// Cull cards based on visibility and visibleSlotCount.
+// If visibleSlotCount is > 0, then only show those slots and cull the rest.
+// Otherwise, it should cull based on bounds of geometry.
+static int cullCards()
+{
+    const float thetaFirst = slotPosition(-1); // -1 keeps the card in front around a bit longer
+    const float thetaLast = slotPosition(visibleSlotCount);
+
+    int count = 0;
+    for (int i = 0; i < cardCount; i++) {
+        if (visibleSlotCount > 0) {
+            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
+            float p = cardPosition(i);
+            if (p >= thetaFirst && p < thetaLast) {
+                cards[i].visible = true;
+                count++;
+            } else {
+                cards[i].visible = false;
+            }
+        } else {
+            // Cull the rest of the cards using bounding box of geometry.
+            // TODO
+            cards[i].visible = true;
+            count++;
+        }
+    }
+    return count;
+}
+
+// Request texture/geometry for items that have come into view
+// or doesn't have a texture yet.
+static void updateCardResources()
+{
+    for (int i = 0; i < cardCount; i++) {
+        int data[1];
+        if (cards[i].visible) {
+            // request texture from client if not loaded
+            if (cards[i].textureState == STATE_INVALID) {
+                data[0] = i;
+                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
+                if (enqueued) {
+                    cards[i].textureState = STATE_LOADING;
+                } else {
+                    rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
+                }
+            }
+            // request geometry from client if not loaded
+            if (cards[i].geometryState == STATE_INVALID) {
+                data[0] = i;
+                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
+                if (enqueued) {
+                    cards[i].geometryState = STATE_LOADING;
+                } else {
+                    rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
+                }
+            }
+        } else {
+            // ask the host to remove the texture
+            if (cards[i].textureState == STATE_LOADED) {
+                data[0] = i;
+                bool enqueued = true;
+                rsSendToClientBlocking(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
+                if (enqueued) {
+                    cards[i].textureState = STATE_INVALID;
+                } else {
+                    rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
+                }
+            }
+            // ask the host to remove the geometry
+            if (cards[i].geometryState == STATE_LOADED) {
+                data[0] = i;
+                bool enqueued = true;
+                rsSendToClientBlocking(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
+                if (enqueued) {
+                    cards[i].geometryState = STATE_INVALID;
+                } else {
+                    rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
+                }
+            }
+
+        }
+    }
+}
+
+// Places dots on geometry to visually inspect that objects can be seen by rays.
+// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
+// card.
+static void renderWithRays()
+{
+    const float w = rsgGetWidth();
+    const float h = rsgGetHeight();
+    const int skip = 8;
+    color(1.0f, 0.0f, 0.0f, 1.0f);
+    for (int j = 0; j < (int) h; j+=skip) {
+        float posY = (float) j;
+        for (int i = 0; i < (int) w; i+=skip) {
+            float posX = (float) i;
+            Ray ray;
+            if (makeRayForPixelAt(&ray, posX, posY)) {
+                float bestTime = FLT_MAX;
+                if (intersectGeometry(&ray, &bestTime) != -1) {
+                    rsgDrawSpriteScreenspace(posX, posY, 0.0f, 2.0f, 2.0f);
+                }
+            }
+        }
+    }
+}
+
+int root() {
+    rsgClearDepth(1.0f);
+
+    rsgBindProgramVertex(vertexProgram);
+    rsgBindProgramFragment(fragmentProgram);
+    rsgBindProgramStore(programStore);
+    rsgBindProgramRaster(rasterProgram);
+
+    updateAllocationVars();
+
+    if (!initialized) {
+        for (int i = 0; i < cardCount; i++)
+            cards[i].textureState = STATE_INVALID;
+        initialized = true;
+    }
+
+    if (false) { // for debugging - flash the screen so we know we're still rendering
+        static bool toggle;
+        if (toggle)
+            rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0);
+        else
+            rsgClearColor(1.0f, 0.0f, 0.0f, 1.f);
+        toggle = !toggle;
+    } else {
+        rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0);
+    }
+
+    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
+
+    bool stillAnimating = updateNextPosition();
+
+    cullCards();
+
+    updateCardResources();
+
+    drawCards();
+
+    if (debugPicking) {
+        renderWithRays();
+    }
+
+    //rsSendToClient(CMD_PING);
+
+    return stillAnimating ? 1 : 0;
+}
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
index a38d3b2..aae0f21 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -73,6 +73,12 @@
     {"Connected", DBUS_TYPE_BOOLEAN},
 };
 
+static Properties pan_properties[] = {
+    {"Connected", DBUS_TYPE_BOOLEAN},
+    {"Interface", DBUS_TYPE_STRING},
+    {"UUID", DBUS_TYPE_STRING},
+};
+
 typedef union {
     char *str_val;
     int int_val;
@@ -191,6 +197,7 @@
     dbus_bool_t ret;
     va_list lst;
     va_start(lst, first_arg_type);
+
     ret = dbus_func_args_async_valist(env, conn,
                                       timeout_ms,
                                       reply, user, nat,
@@ -708,6 +715,11 @@
                     sizeof(input_properties) / sizeof(Properties));
 }
 
+jobjectArray parse_pan_property_change(JNIEnv *env, DBusMessage *msg) {
+    return parse_property_change(env, msg, (Properties *) &pan_properties,
+                    sizeof(pan_properties) / sizeof(Properties));
+}
+
 jobjectArray parse_adapter_properties(JNIEnv *env, DBusMessageIter *iter) {
     return parse_properties(env, iter, (Properties *) &adapter_properties,
                             sizeof(adapter_properties) / sizeof(Properties));
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index 27a00ae..9222e1a 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -164,6 +164,7 @@
 jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg);
 jobjectArray parse_input_properties(JNIEnv *env, DBusMessageIter *iter);
 jobjectArray parse_input_property_change(JNIEnv *env, DBusMessage *msg);
+jobjectArray parse_pan_property_change(JNIEnv *env, DBusMessage *msg);
 void append_variant(DBusMessageIter *iter, int type, void *val);
 int get_bdaddr(const char *str, bdaddr_t *ba);
 void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 4dc012c..6ef154e 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -66,6 +66,8 @@
 
 static jmethodID method_onInputDevicePropertyChanged;
 static jmethodID method_onInputDeviceConnectionResult;
+static jmethodID method_onPanDevicePropertyChanged;
+static jmethodID method_onPanDeviceConnectionResult;
 
 typedef event_loop_native_data_t native_data_t;
 
@@ -120,9 +122,13 @@
     method_onDisplayPasskey = env->GetMethodID(clazz, "onDisplayPasskey",
                                                "(Ljava/lang/String;II)V");
     method_onInputDevicePropertyChanged = env->GetMethodID(clazz, "onInputDevicePropertyChanged",
-                                                      "(Ljava/lang/String;[Ljava/lang/String;)V");
+                                               "(Ljava/lang/String;[Ljava/lang/String;)V");
     method_onInputDeviceConnectionResult = env->GetMethodID(clazz, "onInputDeviceConnectionResult",
-                                                      "(Ljava/lang/String;Z)V");
+                                               "(Ljava/lang/String;Z)V");
+    method_onPanDevicePropertyChanged = env->GetMethodID(clazz, "onPanDevicePropertyChanged",
+                                               "(Ljava/lang/String;[Ljava/lang/String;)V");
+    method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult",
+                                               "(Ljava/lang/String;Z)V");
 
     field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
 #endif
@@ -890,7 +896,23 @@
             LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
         }
         goto success;
-    }
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.Network",
+                                     "PropertyChanged")) {
+
+       jobjectArray str_array =
+                   parse_pan_property_change(env, msg);
+       if (str_array != NULL) {
+           const char *c_path = dbus_message_get_path(msg);
+           env->CallVoidMethod(nat->me,
+                               method_onPanDevicePropertyChanged,
+                               env->NewStringUTF(c_path),
+                               str_array);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
+   }
 
     ret = a2dp_event_filter(msg, env);
     env->PopLocalFrame(NULL);
@@ -1273,6 +1295,31 @@
     free(user);
 }
 
+void onPanDeviceConnectionResult(DBusMessage *msg, void *user, void *n) {
+    LOGV(__FUNCTION__);
+
+    native_data_t *nat = (native_data_t *)n;
+    const char *path = (const char *)user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env;
+    nat->vm->GetEnv((void**)&env, nat->envVer);
+
+    bool result = JNI_TRUE;
+    if (dbus_set_error_from_message(&err, msg)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+        result = JNI_FALSE;
+    }
+    LOGV("... Pan Device Path = %s, result = %d", path, result);
+    jstring jPath = env->NewStringUTF(path);
+    env->CallVoidMethod(nat->me,
+                        method_onPanDeviceConnectionResult,
+                        jPath,
+                        result);
+    env->DeleteLocalRef(jPath);
+    free(user);
+}
+
 #endif
 
 static JNINativeMethod sMethods[] = {
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index 69a8003..74127cf 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -17,6 +17,9 @@
 #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"
 #define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device"
 #define DBUS_INPUT_IFACE BLUEZ_DBUS_BASE_IFC ".Input"
+#define DBUS_NETWORK_IFACE BLUEZ_DBUS_BASE_IFC ".Network"
+#define DBUS_NETWORKSERVER_IFACE BLUEZ_DBUS_BASE_IFC ".NetworkServer"
+
 
 #define LOG_TAG "BluetoothService.cpp"
 
@@ -71,6 +74,8 @@
 void onDiscoverServicesResult(DBusMessage *msg, void *user, void *nat);
 void onCreateDeviceResult(DBusMessage *msg, void *user, void *nat);
 void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
+void onConnectPanResult(DBusMessage *msg, void *user, void *n);
+void onPanDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
 
 
 /** Get native data stored in the opaque (Java code maintained) pointer mNativeData
@@ -939,6 +944,105 @@
     return JNI_FALSE;
 }
 
+static jboolean setBluetoothTetheringNative(JNIEnv *env, jobject object, jboolean value,
+                                            jstring src_role, jstring bridge) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply;
+        const char *c_role = env->GetStringUTFChars(src_role, NULL);
+        const char *c_bridge = env->GetStringUTFChars(bridge, NULL);
+        if (value) {
+            LOGE("setBluetoothTetheringNative true");
+            reply = dbus_func_args(env, nat->conn,
+                                  get_adapter_path(env, object),
+                                  DBUS_NETWORKSERVER_IFACE,
+                                  "Register",
+                                  DBUS_TYPE_STRING, &c_role,
+                                  DBUS_TYPE_STRING, &c_bridge,
+                                  DBUS_TYPE_INVALID);
+        } else {
+            LOGE("setBluetoothTetheringNative false");
+            reply = dbus_func_args(env, nat->conn,
+                                  get_adapter_path(env, object),
+                                  DBUS_NETWORKSERVER_IFACE,
+                                  "Unregister",
+                                  DBUS_TYPE_STRING, &c_role,
+                                  DBUS_TYPE_INVALID);
+        }
+        env->ReleaseStringUTFChars(src_role, c_role);
+        env->ReleaseStringUTFChars(bridge, c_bridge);
+        return reply ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean connectPanDeviceNative(JNIEnv *env, jobject object, jstring path,
+                                       jstring srcRole, jstring dstRole) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    LOGE("connectPanDeviceNative");
+    native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
+
+    if (nat && eventLoopNat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        const char *src = env->GetStringUTFChars(srcRole, NULL);
+        const char *dst = env->GetStringUTFChars(dstRole, NULL);
+
+        int len = env->GetStringLength(path) + 1;
+        char *context_path = (char *)calloc(len, sizeof(char));
+        strlcpy(context_path, c_path, len);  // for callback
+
+        bool ret = dbus_func_args_async(env, nat->conn, -1,onPanDeviceConnectionResult,
+                                    context_path, eventLoopNat, c_path,
+                                    DBUS_NETWORK_IFACE, "Connect",
+                                    DBUS_TYPE_STRING, &src,
+                                    DBUS_TYPE_STRING, &dst,
+                                    DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        env->ReleaseStringUTFChars(srcRole, src);
+        env->ReleaseStringUTFChars(dstRole, dst);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean disconnectPanDeviceNative(JNIEnv *env, jobject object,
+                                     jstring path) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    LOGE("disconnectPanDeviceNative");
+    native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
+
+    if (nat && eventLoopNat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+
+        int len = env->GetStringLength(path) + 1;
+        char *context_path = (char *)calloc(len, sizeof(char));
+        strlcpy(context_path, c_path, len);  // for callback
+
+        bool ret = dbus_func_args_async(env, nat->conn, -1,onPanDeviceConnectionResult,
+                                        context_path, eventLoopNat, c_path,
+                                        DBUS_NETWORK_IFACE, "Disconnect",
+                                        DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
 static JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"classInitNative", "()V", (void*)classInitNative},
@@ -987,8 +1091,15 @@
     // HID functions
     {"connectInputDeviceNative", "(Ljava/lang/String;)Z", (void *)connectInputDeviceNative},
     {"disconnectInputDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectInputDeviceNative},
+
+    {"setBluetoothTetheringNative", "(ZLjava/lang/String;Ljava/lang/String;)Z",
+              (void *)setBluetoothTetheringNative},
+    {"connectPanDeviceNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+              (void *)connectPanDeviceNative},
+    {"disconnectPanDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectPanDeviceNative},
 };
 
+
 int register_android_server_BluetoothService(JNIEnv *env) {
     return AndroidRuntime::registerNativeMethods(env,
                 "android/server/BluetoothService", sMethods, NELEM(sMethods));
diff --git a/core/res/Android.mk b/core/res/Android.mk
index 7d11148..9fafc59 100644
--- a/core/res/Android.mk
+++ b/core/res/Android.mk
@@ -33,8 +33,16 @@
 # PRODUCT-agnostic resource data like IDs and type definitions.
 LOCAL_EXPORT_PACKAGE_RESOURCES := true
 
+# Include resources generated by system RenderScript files.
+framework_GENERATED_SOURCE_DIR := $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON)/src
+framework_RenderScript_STAMP_FILE := $(framework_GENERATED_SOURCE_DIR)/RenderScript.stamp
+LOCAL_RESOURCE_DIR := $(framework_GENERATED_SOURCE_DIR)/renderscript/res $(LOCAL_PATH)/res
+
 include $(BUILD_PACKAGE)
 
+# Make sure the system .rs files get compiled before building the package-export.apk.
+$(resource_export_package): $(framework_RenderScript_STAMP_FILE)
+
 # define a global intermediate target that other module may depend on.
 .PHONY: framework-res-package-target
 framework-res-package-target: $(LOCAL_BUILT_MODULE)
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4430ea0..3e3f47c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -121,6 +121,12 @@
     <string-array translatable="false" name="config_tether_wifi_regexs">
     </string-array>
 
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         bluetooth interfaces.  If the device doesn't want to support tethering over bluetooth this
+         should be empty. -->
+    <string-array translatable="false" name="config_tether_bluetooth_regexs">
+    </string-array>
+
     <!-- Dhcp range (min, max) to use for tethering purposes -->
     <string-array translatable="false" name="config_tether_dhcp_range">
     </string-array>
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index d2845cf..afea767 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -69,11 +69,8 @@
          * the decoder will try to pick the best matching config based on the
          * system's screen depth, and characteristics of the original image such
          * as if it has per-pixel alpha (requiring a config that also does).
-         * 
-         * The configuration is set to {@link android.graphics.Bitmap.Config#ARGB_8888}
-         * by default.
          */
-        public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
+        public Bitmap.Config inPreferredConfig;
 
         /**
          * If dither is true, the decoder will attempt to dither the decoded
diff --git a/graphics/java/android/renderscript/Font.java b/graphics/java/android/renderscript/Font.java
index d79909e..de25014 100644
--- a/graphics/java/android/renderscript/Font.java
+++ b/graphics/java/android/renderscript/Font.java
@@ -18,6 +18,8 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Map;
+import java.util.HashMap;
 
 import android.content.res.Resources;
 import android.content.res.AssetManager;
@@ -30,10 +32,100 @@
  **/
 public class Font extends BaseObj {
 
+    //These help us create a font by family name
+    private static final String[] sSansNames = {
+        "sans-serif", "arial", "helvetica", "tahoma", "verdana"
+    };
+
+    private static final String[] sSerifNames = {
+        "serif", "times", "times new roman", "palatino", "georgia", "baskerville",
+        "goudy", "fantasy", "cursive", "ITC Stone Serif"
+    };
+
+    private static final String[] sMonoNames = {
+        "monospace", "courier", "courier new", "monaco"
+    };
+
+    private static class FontFamily {
+        String[] mNames;
+        String mNormalFileName;
+        String mBoldFileName;
+        String mItalicFileName;
+        String mBoldItalicFileName;
+    }
+
+    private static Map<String, FontFamily> sFontFamilyMap;
+
+    public enum Style {
+        NORMAL,
+        BOLD,
+        ITALIC,
+        BOLD_ITALIC;
+    }
+
+    private static void addFamilyToMap(FontFamily family) {
+        for(int i = 0; i < family.mNames.length; i ++) {
+            sFontFamilyMap.put(family.mNames[i], family);
+        }
+    }
+
+    private static void initFontFamilyMap() {
+        sFontFamilyMap = new HashMap<String, FontFamily>();
+
+        FontFamily sansFamily = new FontFamily();
+        sansFamily.mNames = sSansNames;
+        sansFamily.mNormalFileName = "DroidSans.ttf";
+        sansFamily.mBoldFileName = "DroidSans-Bold.ttf";
+        sansFamily.mItalicFileName = "DroidSans.ttf";
+        sansFamily.mBoldItalicFileName = "DroidSans-Bold.ttf";
+        addFamilyToMap(sansFamily);
+
+        FontFamily serifFamily = new FontFamily();
+        serifFamily.mNames = sSerifNames;
+        serifFamily.mNormalFileName = "DroidSerif-Regular.ttf";
+        serifFamily.mBoldFileName = "DroidSerif-Bold.ttf";
+        serifFamily.mItalicFileName = "DroidSerif-Italic.ttf";
+        serifFamily.mBoldItalicFileName = "DroidSerif-BoldItalic.ttf";
+        addFamilyToMap(serifFamily);
+
+        FontFamily monoFamily = new FontFamily();
+        monoFamily.mNames = sMonoNames;
+        monoFamily.mNormalFileName = "DroidSansMono.ttf";
+        monoFamily.mBoldFileName = "DroidSansMono.ttf";
+        monoFamily.mItalicFileName = "DroidSansMono.ttf";
+        monoFamily.mBoldItalicFileName = "DroidSansMono.ttf";
+        addFamilyToMap(monoFamily);
+    }
+
+    static {
+        initFontFamilyMap();
+    }
+
+    static String getFontFileName(String familyName, Style style) {
+        FontFamily family = sFontFamilyMap.get(familyName);
+        if(family != null) {
+            switch(style) {
+                case NORMAL:
+                    return family.mNormalFileName;
+                case BOLD:
+                    return family.mBoldFileName;
+                case ITALIC:
+                    return family.mItalicFileName;
+                case BOLD_ITALIC:
+                    return family.mBoldItalicFileName;
+            }
+        }
+        // Fallback if we could not find the desired family
+        return "DroidSans.ttf";
+    }
+
     Font(int id, RenderScript rs) {
         super(id, rs);
     }
 
+    /**
+     * Takes a specific file name as an argument
+     */
     static public Font create(RenderScript rs, Resources res, String fileName, int size)
         throws IllegalArgumentException {
 
@@ -43,7 +135,7 @@
             int fontId = rs.nFontCreateFromFile(fileName, size, dpi);
 
             if(fontId == 0) {
-                throw new IllegalStateException("Load loading a font");
+                throw new IllegalStateException("Failed loading a font");
             }
             Font rsFont = new Font(fontId, rs);
 
@@ -55,4 +147,19 @@
 
         return null;
     }
+
+    /**
+     * Accepts one of the following family names as an argument
+     * and will attemp to produce the best match with a system font
+     * "sans-serif" "arial" "helvetica" "tahoma" "verdana"
+     * "serif" "times" "times new roman" "palatino" "georgia" "baskerville"
+     * "goudy" "fantasy" "cursive" "ITC Stone Serif"
+     * "monospace" "courier" "courier new" "monaco"
+     * Returns default font if no match could be found
+     */
+    static public Font createFromFamily(RenderScript rs, Resources res, String familyName, Style fontStyle, int size)
+    throws IllegalArgumentException {
+        String fileName = getFontFileName(familyName, fontStyle);
+        return create(rs, res, fileName, size);
+    }
 }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 0a5ff57..3440687 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -41,7 +41,7 @@
 #define DEFAULT_PATH_CACHE_SIZE 6.0f
 #define DEFAULT_PATCH_CACHE_SIZE 100
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
-#define DEFAULT_DROP_SHADOW_CACHE_SIZE 1.0f
+#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
 
 #define REQUIRED_TEXTURE_UNITS_COUNT 3
 
@@ -251,13 +251,13 @@
         mOrthoMatrix.load(current->orthoMatrix);
     }
 
+    mSaveCount--;
+    mSnapshot = previous;
+
     if (restoreLayer) {
         composeLayer(current, previous);
     }
 
-    mSaveCount--;
-    mSnapshot = previous;
-
     if (restoreClip) {
         setScissorFromClip();
     }
@@ -287,7 +287,11 @@
         mode = SkXfermode::kSrcOver_Mode;
     }
 
-    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+    if (alpha > 0 && !mSnapshot->invisible) {
+        createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+    } else {
+        mSnapshot->invisible = true;
+    }
 
     return count;
 }
@@ -295,7 +299,11 @@
 int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
         int alpha, int flags) {
     int count = saveSnapshot();
-    createLayer(mSnapshot, left, top, right, bottom, alpha, SkXfermode::kSrcOver_Mode, flags);
+    if (alpha > 0 && !mSnapshot->invisible) {
+        createLayer(mSnapshot, left, top, right, bottom, alpha, SkXfermode::kSrcOver_Mode, flags);
+    } else {
+        mSnapshot->invisible = true;
+    }
     return count;
 }
 
@@ -328,7 +336,6 @@
     snapshot->flags |= Snapshot::kFlagIsLayer;
     snapshot->layer = layer;
     snapshot->fbo = layer->fbo;
-
     snapshot->transform.loadTranslate(-left, -top, 0.0f);
     snapshot->setClip(0.0f, 0.0f, right - left, bottom - top);
     snapshot->viewport.set(0.0f, 0.0f, right - left, bottom - top);
@@ -340,8 +347,7 @@
 
     // Change the ortho projection
     glViewport(0, 0, right - left, bottom - top);
-    // Don't flip the FBO, it will get flipped when drawing back to the framebuffer
-    mOrthoMatrix.loadOrtho(0.0f, right - left, 0.0f, bottom - top, -1.0f, 1.0f);
+    mOrthoMatrix.loadOrtho(0.0f, right - left, bottom - top, 0.0f, -1.0f, 1.0f);
 
     return true;
 }
@@ -363,9 +369,14 @@
     Layer* layer = current->layer;
     const Rect& rect = layer->layer;
 
+    // FBOs are already drawn with a top-left origin, don't flip the texture
+    resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
+
     drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
             layer->texture, layer->alpha, layer->mode, layer->blend);
 
+    resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+
     LayerSize size(rect.getWidth(), rect.getHeight());
     // Failing to add the layer to the cache should happen only if the
     // layer is too large
@@ -422,6 +433,8 @@
 }
 
 bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) {
+    if (mSnapshot->invisible) return true;
+
     Rect r(left, top, right, bottom);
     mSnapshot->transform.mapRect(r);
     return !mSnapshot->clipRect.intersects(r);
@@ -527,6 +540,7 @@
 }
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
+    if (mSnapshot->invisible) return;
     const Rect& clip = mSnapshot->clipRect;
     drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
 }
@@ -556,7 +570,8 @@
 
 void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
         float x, float y, SkPaint* paint) {
-    if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+    if (mSnapshot->invisible || text == NULL || count == 0 ||
+            (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
         return;
     }
 
@@ -614,6 +629,8 @@
 }
 
 void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
+    if (mSnapshot->invisible) return;
+
     GLuint textureUnit = 0;
     glActiveTexture(gTextureUnits[textureUnit]);
 
@@ -621,6 +638,13 @@
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
+    const float x = texture->left - texture->offset;
+    const float y = texture->top - texture->offset;
+
+    if (quickReject(x, y, x + texture->width, y + texture->height)) {
+        return;
+    }
+
     int alpha;
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
@@ -631,9 +655,6 @@
     const GLfloat g = a * ((color >>  8) & 0xFF) / 255.0f;
     const GLfloat b = a * ((color      ) & 0xFF) / 255.0f;
 
-    const float x = texture->left - texture->offset;
-    const float y = texture->top - texture->offset;
-
     setupTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true, true);
 
     // Draw the mesh
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 21b2bef..97e7cae 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -42,7 +42,7 @@
  */
 class Snapshot: public LightRefBase<Snapshot> {
 public:
-    Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) { }
+    Snapshot(): invisible(false), flags(0), previous(NULL), layer(NULL), fbo(0) { }
 
     /**
      * Copies the specified snapshot. Only the transform and clip rectangle
@@ -54,6 +54,7 @@
             height(s->height),
             transform(s->transform),
             clipRect(s->clipRect),
+            invisible(s->invisible),
             flags(0),
             previous(s),
             layer(NULL),
@@ -165,6 +166,11 @@
     Rect clipRect;
 
     /**
+     * If true, the layer won't be rendered.
+     */
+    bool invisible;
+
+    /**
      * Dirty flags.
      */
     int flags;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index 5fbe9e5..c3be483 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -29,13 +29,18 @@
 namespace uirenderer {
 
 struct ShadowText {
-    ShadowText() { }
+    ShadowText() {
+        text = NULL;
+    }
 
     ShadowText(SkPaint* paint, uint32_t radius, uint32_t len, const char* srcText):
-            paint(paint), radius(radius), len(len) {
+            radius(radius), len(len) {
         text = new char[len];
         memcpy(text, srcText, len);
 
+        textSize = paint->getTextSize();
+        typeface = paint->getTypeface();
+
         hash = 0;
         uint32_t multiplier = 1;
         for (uint32_t i = 0; i < len; i++) {
@@ -46,7 +51,8 @@
     }
 
     ShadowText(const ShadowText& shadow):
-            paint(shadow.paint), radius(shadow.radius), len(shadow.len), hash(shadow.hash) {
+            radius(shadow.radius), len(shadow.len), hash(shadow.hash),
+            textSize(shadow.textSize), typeface(shadow.typeface) {
         text = new char[shadow.len];
         memcpy(text, shadow.text, shadow.len);
     }
@@ -55,10 +61,11 @@
         delete[] text;
     }
 
-    SkPaint* paint;
     uint32_t radius;
     uint32_t len;
     uint32_t hash;
+    float textSize;
+    SkTypeface* typeface;
     char *text;
 
     bool operator<(const ShadowText& rhs) const {
@@ -66,11 +73,14 @@
         else if (len == rhs.len) {
             if (radius < rhs.radius) return true;
             else if (radius == rhs.radius) {
-                if (paint < rhs.paint) return true;
-                else if (paint == rhs.paint) {
-                    if (hash < rhs.hash) return true;
-                    if (hash == rhs.hash) {
-                        return strncmp(text, rhs.text, len) < 0;
+                if (textSize < rhs.textSize) return true;
+                else if (textSize == rhs.textSize) {
+                    if (typeface < rhs.typeface) return true;
+                    else if (typeface == rhs.typeface) {
+                        if (hash < rhs.hash) return true;
+                        if (hash == rhs.hash) {
+                            return strncmp(text, rhs.text, len) < 0;
+                        }
                     }
                 }
             }
diff --git a/libs/rs/java/ModelViewer/AndroidManifest.xml b/libs/rs/java/ModelViewer/AndroidManifest.xml
index ebbe743..39976d2 100644
--- a/libs/rs/java/ModelViewer/AndroidManifest.xml
+++ b/libs/rs/java/ModelViewer/AndroidManifest.xml
@@ -10,5 +10,13 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name="SceneGraph"
+                  android:label="SceneGraph"
+                  android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraph.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraph.java
similarity index 97%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraph.java
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraph.java
index 5daa4ac..557e0cc 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraph.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraph.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.scenegraph;
+package com.android.modelviewer;
 
 import android.renderscript.RSSurfaceView;
 import android.renderscript.RenderScript;
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
similarity index 99%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphRS.java
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
index 3db4a2b6..3f4d930 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphRS.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.scenegraph;
+package com.android.modelviewer;
 
 import java.io.Writer;
 import java.util.Map;
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphView.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphView.java
similarity index 98%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphView.java
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphView.java
index ae94869..44a59b2 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphView.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.scenegraph;
+package com.android.modelviewer;
 
 import java.io.Writer;
 import java.util.ArrayList;
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SgTransform.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SgTransform.java
similarity index 98%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/SgTransform.java
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/SgTransform.java
index e81f1a7..bfc9bb7 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SgTransform.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SgTransform.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.scenegraph;
+package com.android.modelviewer;
 
 import java.io.Writer;
 import java.util.Map;
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/scenegraph.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
similarity index 97%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/scenegraph.rs
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
index e6ae6df..8053306 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/scenegraph.rs
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
@@ -14,7 +14,7 @@
 
 #pragma version(1)
 
-#pragma rs java_package_name(com.android.scenegraph)
+#pragma rs java_package_name(com.android.modelviewer)
 
 #include "rs_graphics.rsh"
 #include "transform_def.rsh"
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/transform.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform.rs
similarity index 97%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/transform.rs
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/transform.rs
index a62d12b..7b9cd1c 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/transform.rs
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform.rs
@@ -14,7 +14,7 @@
 
 #pragma version(1)
 
-#pragma rs java_package_name(com.android.scenegraph)
+#pragma rs java_package_name(com.android.modelviewer)
 
 #include "transform_def.rsh"
 
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/transform_def.rsh b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform_def.rsh
similarity index 96%
rename from libs/rs/java/SceneGraph/src/com/android/scenegraph/transform_def.rsh
rename to libs/rs/java/ModelViewer/src/com/android/modelviewer/transform_def.rsh
index 10aac37..a66e6c5 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/transform_def.rsh
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform_def.rsh
@@ -14,7 +14,7 @@
 
 #pragma version(1)
 
-#pragma rs java_package_name(com.android.scenegraph)
+#pragma rs java_package_name(com.android.modelviewer)
 
 #define TRANSFORM_NONE 0
 #define TRANSFORM_TRANSLATE 1
diff --git a/libs/rs/java/SceneGraph/Android.mk b/libs/rs/java/Samples/Android.mk
similarity index 96%
rename from libs/rs/java/SceneGraph/Android.mk
rename to libs/rs/java/Samples/Android.mk
index 5520446..65ae734 100644
--- a/libs/rs/java/SceneGraph/Android.mk
+++ b/libs/rs/java/Samples/Android.mk
@@ -24,7 +24,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 #LOCAL_STATIC_JAVA_LIBRARIES := android.renderscript
 
-LOCAL_PACKAGE_NAME := SceneGraph
+LOCAL_PACKAGE_NAME := Samples
 
 include $(BUILD_PACKAGE)
 
diff --git a/libs/rs/java/SceneGraph/AndroidManifest.xml b/libs/rs/java/Samples/AndroidManifest.xml
similarity index 66%
rename from libs/rs/java/SceneGraph/AndroidManifest.xml
rename to libs/rs/java/Samples/AndroidManifest.xml
index 8a8f87a..17c6b16 100644
--- a/libs/rs/java/SceneGraph/AndroidManifest.xml
+++ b/libs/rs/java/Samples/AndroidManifest.xml
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.scenegraph">
-    <application android:label="SceneGraph">
-        <activity android:name="SceneGraph"
+    package="com.android.samples">
+    <application android:label="Samples"
+    android:icon="@drawable/test_pattern">
+        <activity android:name="RsList"
+                  android:label="RsList"                  
                   android:theme="@android:style/Theme.Black.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/libs/rs/java/Samples/res/drawable/test_pattern.png b/libs/rs/java/Samples/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/test_pattern.png
Binary files differ
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraph.java b/libs/rs/java/Samples/src/com/android/samples/RsList.java
similarity index 91%
copy from libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraph.java
copy to libs/rs/java/Samples/src/com/android/samples/RsList.java
index 5daa4ac..d8c733d 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraph.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsList.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.scenegraph;
+package com.android.samples;
 
 import android.renderscript.RSSurfaceView;
 import android.renderscript.RenderScript;
@@ -37,9 +37,9 @@
 
 import java.lang.Runtime;
 
-public class SceneGraph extends Activity {
+public class RsList extends Activity {
 
-    private SceneGraphView mView;
+    private RsListView mView;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -47,7 +47,7 @@
 
         // Create our Preview view and set it as the content of our
         // Activity
-        mView = new SceneGraphView(this);
+        mView = new RsListView(this);
         setContentView(mView);
     }
 
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsListRS.java b/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
new file mode 100644
index 0000000..a782e0e
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.samples;
+
+import java.io.Writer;
+import java.util.Vector;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.util.Log;
+
+
+public class RsListRS {
+
+    private final int STATE_LAST_FOCUS = 1;
+
+    private static final String[] DATA_LIST = {
+    "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
+    "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
+    "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
+    "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
+    "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
+    "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
+    "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria",
+    "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
+    "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+    "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+    "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+    "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
+    "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
+    "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
+    "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
+    "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
+    "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
+    "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
+    "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
+    "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
+    "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
+    "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+    "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
+    "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
+    "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
+    "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
+    "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
+    "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
+    "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
+    "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
+    "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
+    "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
+    "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
+    "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
+    "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
+    "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
+    "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
+    "Ukraine", "United Arab Emirates", "United Kingdom",
+    "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
+    "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
+    "Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
+    };
+
+    int mWidth;
+    int mHeight;
+
+    public RsListRS() {
+    }
+
+    public void init(RenderScriptGL rs, Resources res, int width, int height) {
+        mRS = rs;
+        mRes = res;
+        mWidth = width;
+        mHeight = height;
+        initRS();
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+    private Font mItalic;
+
+    ScriptField_ListAllocs_s mListAllocs;
+
+    private ScriptC_Rslist mScript;
+
+    int mLastX;
+    int mLastY;
+
+    public void onActionDown(int x, int y) {
+        mScript.set_gDY(0.0f);
+
+        mLastX = x;
+        mLastY = y;
+    }
+
+    public void onActionMove(int x, int y) {
+        int dx = mLastX - x;
+        int dy = mLastY - y;
+
+        if(Math.abs(dy) <= 2) {
+            dy = 0;
+        }
+
+        mScript.set_gDY(dy);
+
+        mLastX = x;
+        mLastY = y;
+    }
+
+    private void initRS() {
+
+        mScript = new ScriptC_Rslist(mRS, mRes, R.raw.rslist, true);
+
+        mListAllocs = new ScriptField_ListAllocs_s(mRS, DATA_LIST.length);
+        for(int i = 0; i < DATA_LIST.length; i ++) {
+            ScriptField_ListAllocs_s.Item listElem = new ScriptField_ListAllocs_s.Item();
+            listElem.text = Allocation.createFromString(mRS, DATA_LIST[i]);
+            mListAllocs.set(listElem, i, false);
+        }
+
+        mListAllocs.copyAll();
+
+        mScript.bind_gList(mListAllocs);
+
+        mItalic = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD_ITALIC, 8);
+        mScript.set_gItalic(mItalic);
+
+        mRS.contextBindRootScript(mScript);
+    }
+}
+
+
+
diff --git a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphView.java b/libs/rs/java/Samples/src/com/android/samples/RsListView.java
similarity index 82%
copy from libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphView.java
copy to libs/rs/java/Samples/src/com/android/samples/RsListView.java
index ae94869..b98ea08 100644
--- a/libs/rs/java/SceneGraph/src/com/android/scenegraph/SceneGraphView.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsListView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.scenegraph;
+package com.android.samples;
 
 import java.io.Writer;
 import java.util.ArrayList;
@@ -39,15 +39,15 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
-public class SceneGraphView extends RSSurfaceView {
+public class RsListView extends RSSurfaceView {
 
-    public SceneGraphView(Context context) {
+    public RsListView(Context context) {
         super(context);
         //setFocusable(true);
     }
 
     private RenderScriptGL mRS;
-    private SceneGraphRS mRender;
+    private RsListRS mRender;
 
 
     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
@@ -55,7 +55,7 @@
         if (mRS == null) {
             mRS = createRenderScript(true);
             mRS.contextSetSurface(w, h, holder.getSurface());
-            mRender = new SceneGraphRS();
+            mRender = new RsListRS();
             mRender.init(mRS, getResources(), w, h);
         }
     }
@@ -80,13 +80,17 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev)
     {
-        boolean ret = true;
+        boolean ret = false;
         int act = ev.getAction();
-        if (act == ev.ACTION_UP) {
-            ret = false;
+        if (act == ev.ACTION_DOWN) {
+            mRender.onActionDown((int)ev.getX(), (int)ev.getY());
+            ret = true;
+        }
+        else if (act == ev.ACTION_MOVE) {
+            mRender.onActionMove((int)ev.getX(), (int)ev.getY());
+            ret = true;
         }
 
-        mRender.touchEvent((int)ev.getX(), (int)ev.getY());
         return ret;
     }
 }
diff --git a/libs/rs/java/Samples/src/com/android/samples/rslist.rs b/libs/rs/java/Samples/src/com/android/samples/rslist.rs
new file mode 100644
index 0000000..3c3f463
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/rslist.rs
@@ -0,0 +1,73 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.samples)
+
+#include "rs_graphics.rsh"
+
+float gDY;
+
+rs_font gItalic;
+
+typedef struct ListAllocs_s {
+    rs_allocation text;
+} ListAllocs;
+
+ListAllocs *gList;
+
+#pragma rs export_var(gDY, gItalic, gList)
+
+void init() {
+    gDY = 0.0f;
+}
+
+int textPos = 0;
+
+int root(int launchID) {
+
+    rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    rsgClearDepth(1.0f);
+
+    textPos -= (int)gDY*2;
+    gDY *= 0.95;
+
+    rsgFontColor(0.9f, 0.9f, 0.9f, 1.0f);
+    rsgBindFont(gItalic);
+    color(0.2, 0.2, 0.2, 0);
+
+    rs_allocation listAlloc = rsGetAllocation(gList);
+    int allocSize = rsAllocationGetDimX(listAlloc);
+
+    int width = rsgGetWidth();
+    int height = rsgGetHeight();
+
+    int itemHeight = 80;
+    int currentYPos = itemHeight + textPos;
+
+    for(int i = 0; i < allocSize; i ++) {
+        if(currentYPos - itemHeight > height) {
+            break;
+        }
+
+        if(currentYPos > 0) {
+            rsgDrawRect(0, currentYPos - 1, width, currentYPos, 0);
+            rsgDrawText(gList[i].text, 30, currentYPos - 32);
+        }
+        currentYPos += itemHeight;
+    }
+
+    return 10;
+}
diff --git a/libs/rs/java/SceneGraph/res/drawable/robot.png b/libs/rs/java/SceneGraph/res/drawable/robot.png
deleted file mode 100644
index f7353fd..0000000
--- a/libs/rs/java/SceneGraph/res/drawable/robot.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/SceneGraph/res/raw/robot.a3d b/libs/rs/java/SceneGraph/res/raw/robot.a3d
deleted file mode 100644
index 2d7d32b..0000000
--- a/libs/rs/java/SceneGraph/res/raw/robot.a3d
+++ /dev/null
Binary files differ
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 1ef9c93..7661d499 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -218,6 +218,7 @@
 
 Font * Font::create(Context *rsc, const char *name, uint32_t fontSize, uint32_t dpi)
 {
+    rsc->mStateFont.checkInit();
     Vector<Font*> &activeFonts = rsc->mStateFont.mActiveFonts;
 
     for(uint32_t i = 0; i < activeFonts.size(); i ++) {
@@ -513,6 +514,12 @@
 
     initVertexArrayBuffers();
 
+    // We store a string with letters in a rough frequency of occurrence
+    mLatinPrecache = String8(" eisarntolcdugpmhbyfvkwzxjq");
+    mLatinPrecache += String8("EISARNTOLCDUGPMHBYFVKWZXJQ");
+    mLatinPrecache += String8(",.?!()-+@;:`'");
+    mLatinPrecache += String8("0123456789");
+
     mInitialized = true;
 }
 
@@ -622,7 +629,7 @@
 
 uint32_t FontState::getRemainingCacheCapacity() {
     uint32_t remainingCapacity = 0;
-    float totalPixels = 0;
+    uint32_t totalPixels = 0;
     for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
          remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol);
          totalPixels += mCacheLines[i]->mMaxWidth;
@@ -666,12 +673,6 @@
         issueDrawCommand();
         mCurrentQuadIndex = 0;
     }
-
-    // We store a string with letters in a rough frequency of occurrence
-    mLatinPrecache = String8(" eisarntolcdugpmhbyfvkwzxjq");
-    mLatinPrecache += String8("EISARNTOLCDUGPMHBYFVKWZXJQ");
-    mLatinPrecache += String8(",.?!()-+@;:`'");
-    mLatinPrecache += String8("0123456789");
 }
 
 void FontState::renderText(const char *text, int x, int y)
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml
index e5b2f53..273674f 100644
--- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml
@@ -86,19 +86,24 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         >
-        <ImageView android:id="@+id/battery_meter"
-            android:layout_width="wrap_content"
+        <RelativeLayout
+            android:layout_width="120dip"
             android:layout_height="wrap_content"
             android:layout_alignParentLeft="true"
-            android:src="@drawable/dots_empty"
-            />
+            >
+            <ImageView android:id="@+id/battery_meter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/battery"
+                />
 
-        <TextView android:id="@+id/battery_info"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:layout_below="@id/battery_meter"
-            />
+            <TextView android:id="@+id/battery_info"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:layout_below="@id/battery_meter"
+                />
+        </RelativeLayout>
 
         <com.android.systemui.statusbar.Clock
             style="@*android:style/TextAppearance.StatusBar.Icon"
@@ -118,19 +123,24 @@
             android:layout_below="@id/clock"
             />
 
-        <ImageView android:id="@+id/signal_meter"
-            android:layout_width="wrap_content"
+        <RelativeLayout
+            android:layout_width="120dip"
             android:layout_height="wrap_content"
             android:layout_alignParentRight="true"
-            android:src="@drawable/signal"
-            />
+            >
+            <ImageView android:id="@+id/signal_meter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/signal"
+                />
 
-        <TextView android:id="@+id/signal_info"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:layout_below="@id/signal_meter"
-            />
+            <TextView android:id="@+id/signal_info"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:layout_below="@id/signal_meter"
+                />
+        </RelativeLayout>
 
     </RelativeLayout>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
index c864daa..a03393b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
@@ -29,6 +29,11 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.media.AudioManager;
+import android.net.NetworkInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.IBinder;
@@ -59,6 +64,8 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import java.util.List;
+
 import com.android.systemui.statusbar.*;
 import com.android.systemui.R;
 
@@ -85,6 +92,7 @@
     private TextView mSignalText;
 
     private final AudioManager mAudioManager;
+    private final WifiManager mWifiManager;
 
 
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -94,21 +102,100 @@
             if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
                 mSoundButton.setAlpha(getSilentMode() ? 0x7F : 0xFF);
             } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                // hack for now
-                mBar.updateBatteryDisplay(intent.getIntExtra("level", 0), 
-                        (intent.getIntExtra("plugged", 0) != 0));
+                updateBattery(intent);
+            } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)
+                    || action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)
+                    || action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)
+                    || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                updateWifi(intent);
             }
         }
     };
 
+    boolean mWifiEnabled, mWifiConnected;
+    int mWifiLevel;
+    String mWifiSsid;
+
+    private void updateWifi(Intent intent) {
+        if (TabletStatusBarService.DEBUG)
+            Slog.d(TabletStatusBarService.TAG, "updateWifi: " + intent);
+
+        final String action = intent.getAction();
+        final boolean wasConnected = mWifiConnected;
+
+        if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+            mWifiEnabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                    WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
+        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            final NetworkInfo networkInfo = (NetworkInfo)
+                    intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            mWifiConnected = networkInfo != null && networkInfo.isConnected();
+        } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
+            final NetworkInfo.DetailedState detailedState = WifiInfo.getDetailedStateOf(
+                    (SupplicantState)intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE));
+            mWifiConnected = detailedState == NetworkInfo.DetailedState.CONNECTED;
+        } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+            final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
+            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 6) * 20;
+            mWifiLevel = mWifiConnected ? newSignalLevel : 0;
+        }
+
+        if (mWifiConnected && !wasConnected) {
+            WifiInfo info = mWifiManager.getConnectionInfo();
+            if (TabletStatusBarService.DEBUG)
+                Slog.d(TabletStatusBarService.TAG, "updateWifi: just connected: info=" + info);
+
+            if (info != null) {
+                // grab the initial signal strength
+                mWifiLevel = WifiManager.calculateSignalLevel(info.getRssi(), 101);
+
+                // find the SSID
+                mWifiSsid = info.getSSID();
+                if (mWifiSsid == null) {
+                    // OK, it's not in the connectionInfo; we have to go hunting for it
+                    List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
+                    for (WifiConfiguration net : networks) {
+                        if (net.networkId == info.getNetworkId()) {
+                            mWifiSsid = net.SSID;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!mWifiEnabled) {
+            mWifiSsid = "disabled";
+            mWifiLevel = 0;
+        } else if (!mWifiConnected) {
+            mWifiSsid = "disconnected";
+            mWifiLevel = 0;
+        } else if (mWifiSsid == null) {
+            mWifiSsid = "unknown";
+        }
+
+        mSignalMeter.setImageResource(R.drawable.signal);
+        mSignalMeter.setImageLevel(mWifiLevel);
+        mSignalText.setText(String.format("Wi-Fi: %s", mWifiSsid)); // XXX: localize
+
+        // hack for now
+        mBar.setWifiMeter(mWifiLevel);
+    }
+
     public void setBar(TabletStatusBarService bar) {
         mBar = bar;
     }
 
-    public void setBatteryLevel(int level, boolean plugged) {
+    public void updateBattery(Intent intent) {
+        final int level = intent.getIntExtra("level", 0);
+        final boolean plugged = intent.getIntExtra("plugged", 0) != 0;
+
         mBatteryMeter.setImageResource(plugged ? R.drawable.battery_charging : R.drawable.battery);
         mBatteryMeter.setImageLevel(level);
         mBatteryText.setText(String.format("Battery: %d%%", level));
+
+        // hack for now
+        mBar.setBatteryMeter(level, plugged);
     }
     
     public SystemPanel(Context context, AttributeSet attrs) {
@@ -123,6 +210,10 @@
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
 
+        // wifi status info
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        
+        // audio status notifications
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
     }
 
@@ -171,7 +262,10 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-        filter.addAction(Intent.ACTION_POWER_CONNECTED);
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
         getContext().registerReceiver(mReceiver, filter);
 
         mBatteryMeter = (ImageView)findViewById(R.id.battery_meter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
index b0ffaa5..022470e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
@@ -54,7 +54,7 @@
 import com.android.systemui.R;
 
 public class TabletStatusBarService extends StatusBarService {
-    public static final boolean DEBUG = false;
+    public static final boolean DEBUG = true;
     public static final String TAG = "TabletStatusBar";
 
 
@@ -206,13 +206,16 @@
         }
     }
     
-    StatusBarIcon mBatterySBI;
-    StatusBarIcon mSignalSBI;
-    public void updateBatteryDisplay(int level, boolean plugged) {
+    public void setBatteryMeter(int level, boolean plugged) {
         if (DEBUG) Slog.d(TAG, "battery=" + level + (plugged ? " - plugged" : " - unplugged"));
         mBatteryMeter.setImageResource(plugged ? R.drawable.battery_charging : R.drawable.battery);
         mBatteryMeter.setImageLevel(level);
-        mSystemPanel.setBatteryLevel(level, plugged);
+    }
+
+    public void setWifiMeter(int level) {
+        if (DEBUG) Slog.d(TAG, "wifi=" + level);
+        mSignalMeter.setImageResource(R.drawable.signal);
+        mSignalMeter.setImageLevel(level);
     }
 
     public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 859353a..4da0bac 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -344,7 +344,8 @@
         mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) ||
                                   !mTethering.isDunRequired()) &&
                                  (mTethering.getTetherableUsbRegexs().length != 0 ||
-                                  mTethering.getTetherableWifiRegexs().length != 0) &&
+                                  mTethering.getTetherableWifiRegexs().length != 0 ||
+                                  mTethering.getTetherableBluetoothRegexs().length != 0) &&
                                  mTethering.getUpstreamIfaceRegexs().length != 0);
 
     }
@@ -1676,6 +1677,15 @@
         }
     }
 
+    public String[] getTetherableBluetoothRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableBluetoothRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
     // TODO - move iface listing, queries, etc to new module
     // javadoc from interface
     public String[] getTetherableIfaces() {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index c452590..249c32b 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -128,11 +128,12 @@
                                     Slog.e(TAG, String.format(
                                             "Error handling '%s'", event), ex);
                                 }
-                            }
-                            try {
-                                mResponseQueue.put(event);
-                            } catch (InterruptedException ex) {
-                                Slog.e(TAG, "Failed to put response onto queue", ex);
+                            } else {
+                                try {
+                                    mResponseQueue.put(event);
+                                } catch (InterruptedException ex) {
+                                    Slog.e(TAG, "Failed to put response onto queue", ex);
+                                }
                             }
                         } catch (NumberFormatException nfe) {
                             Slog.w(TAG, String.format("Bad msg (%s)", event));
@@ -209,6 +210,7 @@
      */
     public synchronized ArrayList<String> doCommand(String cmd)
             throws NativeDaemonConnectorException  {
+        mResponseQueue.clear();
         sendCommand(cmd);
 
         ArrayList<String> response = new ArrayList<String>();
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index c156150..cb8fa35 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -203,10 +203,10 @@
                 throw new UnknownHostException(addrString);
             }
 
-            int a = Integer.parseInt(parts[0])      ;
-            int b = Integer.parseInt(parts[1]) <<  8;
-            int c = Integer.parseInt(parts[2]) << 16;
-            int d = Integer.parseInt(parts[3]) << 24;
+            int a = Integer.parseInt(parts[0]) << 24;
+            int b = Integer.parseInt(parts[1]) << 16;
+            int c = Integer.parseInt(parts[2]) <<  8;
+            int d = Integer.parseInt(parts[3])      ;
 
             return a | b | c | d;
         } catch (NumberFormatException ex) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 064d47f..59846b9 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -405,7 +405,14 @@
         }
 
         mWifiStateMachine.setWifiEnabled(enable);
+
+        /*
+         * Caller might not have WRITE_SECURE_SETTINGS,
+         * only CHANGE_WIFI_STATE is enforced
+         */
+        long ident = Binder.clearCallingIdentity();
         persistWifiEnabled(enable);
+        Binder.restoreCallingIdentity(ident);
 
         if (enable) {
             if (!mIsReceiverRegistered) {
@@ -451,7 +458,13 @@
                 wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
                 wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
             }
+            /*
+             * Caller might not have WRITE_SECURE_SETTINGS,
+             * only CHANGE_WIFI_STATE is enforced
+             */
+            long ident = Binder.clearCallingIdentity();
             setWifiApConfiguration(wifiConfig);
+            Binder.restoreCallingIdentity(ident);
         }
 
         mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index acedd26..949b874 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,8 +19,8 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothPan;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,7 +32,6 @@
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.NetworkInfo;
-import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.HandlerThread;
@@ -53,6 +52,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Set;
 /**
  * @hide
@@ -75,6 +75,7 @@
     // TODO - remove both of these - should be part of interface inspection/selection stuff
     private String[] mTetherableUsbRegexs;
     private String[] mTetherableWifiRegexs;
+    private String[] mTetherableBluetoothRegexs;
     private String[] mUpstreamIfaceRegexs;
 
     private Looper mLooper;
@@ -94,6 +95,8 @@
     private static final String DHCP_DEFAULT_RANGE1_STOP  = "192.168.42.254";
     private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2";
     private static final String DHCP_DEFAULT_RANGE2_STOP  = "192.168.43.254";
+    private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.9";
+    private static final String DHCP_DEFAULT_RANGE3_STOP  = "192.168.44.254";
 
     private String[] mDnsServers;
     private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
@@ -153,11 +156,13 @@
         mDhcpRange = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_dhcp_range);
         if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
-            mDhcpRange = new String[4];
+            mDhcpRange = new String[6];
             mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START;
             mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP;
             mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START;
             mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP;
+            mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START;
+            mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP;
         }
         mDunRequired = false; // resample when we turn on
 
@@ -165,6 +170,8 @@
                 com.android.internal.R.array.config_tether_usb_regexs);
         mTetherableWifiRegexs = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_wifi_regexs);
+        mTetherableBluetoothRegexs = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_bluetooth_regexs);
         mUpstreamIfaceRegexs = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_upstream_regexs);
 
@@ -183,6 +190,8 @@
         } else if (isUsb(iface)) {
             found = true;
             usb = true;
+        } else if (isBluetooth(iface)) {
+            found = true;
         }
         if (found == false) return;
 
@@ -217,6 +226,12 @@
         return false;
     }
 
+    public boolean isBluetooth(String iface) {
+        for (String regex : mTetherableBluetoothRegexs) {
+            if (iface.matches(regex)) return true;
+        }
+        return false;
+    }
     public void interfaceAdded(String iface) {
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -229,6 +244,9 @@
             found = true;
             usb = true;
         }
+        if (isBluetooth(iface)) {
+            found = true;
+        }
         if (found == false) {
             if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
             return;
@@ -324,13 +342,14 @@
 
         boolean wifiTethered = false;
         boolean usbTethered = false;
+        boolean bluetoothTethered = false;
 
         synchronized (mIfaces) {
             Set ifaces = mIfaces.keySet();
             for (Object iface : ifaces) {
                 TetherInterfaceSM sm = mIfaces.get(iface);
                 if (sm != null) {
-                    if(sm.isErrored()) {
+                    if (sm.isErrored()) {
                         erroredList.add((String)iface);
                     } else if (sm.isAvailable()) {
                         availableList.add((String)iface);
@@ -339,6 +358,8 @@
                             usbTethered = true;
                         } else if (isWifi((String)iface)) {
                             wifiTethered = true;
+                      } else if (isBluetooth((String)iface)) {
+                            bluetoothTethered = true;
                         }
                         activeList.add((String)iface);
                     }
@@ -359,13 +380,19 @@
         }
 
         if (usbTethered) {
-            if (wifiTethered) {
+            if (wifiTethered || bluetoothTethered) {
                 showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
             } else {
                 showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb);
             }
         } else if (wifiTethered) {
-            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            if (bluetoothTethered) {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+            } else {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            }
+        } else if (bluetoothTethered) {
+            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth);
         } else {
             clearTetheredNotification();
         }
@@ -396,7 +423,7 @@
         CharSequence message = r.getText(com.android.internal.R.string.
                 tethered_notification_message);
 
-        if(mTetheredNotification == null) {
+        if (mTetheredNotification == null) {
             mTetheredNotification = new Notification();
             mTetheredNotification.when = 0;
         }
@@ -471,7 +498,7 @@
         }
     }
 
-    // toggled when we enter/leave the fully teathered state
+    // toggled when we enter/leave the fully tethered state
     private boolean enableUsbRndis(boolean enabled) {
         if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")");
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
@@ -554,6 +581,10 @@
         return mTetherableWifiRegexs;
     }
 
+    public String[] getTetherableBluetoothRegexs() {
+        return mTetherableBluetoothRegexs;
+    }
+
     public String[] getUpstreamIfaceRegexs() {
         return mUpstreamIfaceRegexs;
     }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index a774c12..9156358 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -765,8 +765,9 @@
         } else if (rssi >= MAX_RSSI) {
             return numLevels - 1;
         } else {
-            int partitionSize = (MAX_RSSI - MIN_RSSI) / (numLevels - 1);
-            return (rssi - MIN_RSSI) / partitionSize;
+            float inputRange = (MAX_RSSI - MIN_RSSI);
+            float outputRange = (numLevels - 1);
+            return (int)((float)(rssi - MIN_RSSI) * outputRange / inputRange);
         }
     }